Drain clientless sessions during normal shutdown with a 60-second grace and recovery audit. Recover invalid bot login states while preserving real transports and taxis. Handle bounded database reconnect failures with controlled service termination and clear OutdoorPvP references before shutdown destruction. Prevent obsolete transport actions from overriding newer movement. Add regression coverage, live validation notes and the updated parallel -j10 build workflow in AGENTS.md. Full build, twelve regression tests and live login/shutdown/database-fault checks passed.
59 lines
3.5 KiB
Python
59 lines
3.5 KiB
Python
"""Exercise the production reconnect branch and Ping without a real database outage."""
|
|
from pathlib import Path
|
|
import subprocess,tempfile
|
|
root=Path(__file__).resolve().parents[2]
|
|
s=(root/'src/server/database/Database/MySQLConnection.cpp').read_text()
|
|
def function(marker):
|
|
a=s.index(marker);b=s.index('{',a);n=1;i=b+1
|
|
while n:n+=(s[i]=='{')-(s[i]=='}');i+=1
|
|
return s[a:i]
|
|
a=s.index(' case CR_SERVER_GONE_ERROR:',s.index('bool MySQLConnection::_HandleMySQLErrno'))
|
|
b=s.index(' case ER_LOCK_DEADLOCK:',a)
|
|
branch='bool MySQLConnection::_HandleMySQLErrno(unsigned errNo, unsigned char attempts){switch(errNo){'+s[a:b]+'default:return false;}}'
|
|
code=r'''
|
|
#include <atomic>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <deque>
|
|
using uint32=unsigned;using uint8=unsigned char;
|
|
#define TC_LOG_FATAL(...) ((void)0)
|
|
#define TC_LOG_WARN(...) ((void)0)
|
|
#define TC_LOG_INFO(...) ((void)0)
|
|
constexpr unsigned CR_SERVER_GONE_ERROR=2006,CR_SERVER_LOST=2013,CR_SERVER_LOST_EXTENDED=2055,CR_CONNECTION_ERROR=2002,CR_CONN_HOST_ERROR=2003,CR_SOCKET_CREATE_ERROR=2001,CR_IPSOCK_ERROR=2004,CR_UNKNOWN_HOST=2005;
|
|
int sleeps=0,closed=0,pings=0,destroyed=0,pingError=0;
|
|
namespace std::this_thread {void sleep_for(std::chrono::seconds){++sleeps;}}
|
|
struct Handle{} handle;
|
|
void mysql_close(Handle*){assert(destroyed>=2);++closed;}
|
|
int mysql_ping(Handle*p){assert(p);++pings;return pingError;}
|
|
unsigned mysql_errno(Handle*){return CR_SERVER_LOST;}
|
|
struct Stmt{~Stmt(){++destroyed;}};
|
|
struct MySQLConnection{
|
|
bool m_failed=false,m_reconnecting=false,m_prepareError=false,prepareOK=true;
|
|
Handle* m_Mysql=&handle;std::vector<std::unique_ptr<Stmt>> m_stmts;
|
|
struct{std::string database="test";}m_connectionInfo;
|
|
static std::atomic<bool> _fatalConnectionError;
|
|
std::deque<unsigned> errors;unsigned opens=0,prepared=0;
|
|
MySQLConnection(){m_stmts.emplace_back(new Stmt);m_stmts.emplace_back(new Stmt);}
|
|
unsigned Open(){++opens;auto e=errors.empty()?0:errors.front();if(!errors.empty())errors.pop_front();m_Mysql=e?nullptr:&handle;return e;}
|
|
bool PrepareStatements(){assert(m_stmts.size()==2);++prepared;return prepareOK;}
|
|
bool _HandleMySQLErrno(unsigned,unsigned char=5);void FailConnection();void Ping();
|
|
};std::atomic<bool>MySQLConnection::_fatalConnectionError{false};
|
|
'''+function('void MySQLConnection::Ping()')+function('void MySQLConnection::FailConnection()')+branch+r'''
|
|
int main(){
|
|
{MySQLConnection c;c.errors={2002,2002,0};assert(c._HandleMySQLErrno(2013));assert(c.opens==3&&c.prepared==1&&!c.m_reconnecting&&c.m_Mysql);assert(sleeps==2&&!c._fatalConnectionError);}
|
|
{MySQLConnection c;c.m_Mysql=nullptr;int n=pings;c.Ping();assert(c.opens==1&&c.prepared==1&&pings==n);}
|
|
{MySQLConnection c;pingError=1;c.Ping();assert(c.opens==1&&c.prepared==1);pingError=0;}
|
|
{MySQLConnection c;c.errors={2002,2002,2002,2002,2002};assert(!c._HandleMySQLErrno(2013));assert(c.opens==5&&c.m_failed&&c._fatalConnectionError&&c.m_Mysql==nullptr);auto n=c.opens;c.Ping();assert(c.opens==n);}
|
|
MySQLConnection::_fatalConnectionError=false;
|
|
{MySQLConnection c;c.prepareOK=false;assert(!c._HandleMySQLErrno(2002));assert(c.m_failed&&c._fatalConnectionError);}
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory(prefix='vcore-db-reconnect-')as folder:
|
|
p=Path(folder);(p/'test.cpp').write_text(code)
|
|
subprocess.run(['c++','-std=c++17',str(p/'test.cpp'),'-o',str(p/'test')],check=True)
|
|
subprocess.run([str(p/'test')],check=True)
|
|
print('Production reconnect/Ping: error 2002 retries, null ping, ping loss, bounded fatal notification and statement lifetime passed.')
|