Core/Database: Use RAII for resource management in MySQLConnection
* Prevents double deletion of MySQLConnection after errors * The object stays valid after an error and will wait for a reconnect * Also crash the server if 5 reconnects fail * Corrects an issue where the server was crashed after one reconnect because mysql_thread_id was invoked with an invalid handle
This commit is contained in:
1 parent
09fa0ab46a
commit
62815c6e1c
2 files changed
+58
-37
No files matched your search
@@ -37,7 +37,6 @@ MySQLConnection::MySQLConnection(MySQLConnectionInfo& connInfo) :
|
|||||||
m_reconnecting(false),
|
m_reconnecting(false),
|
||||||
m_prepareError(false),
|
m_prepareError(false),
|
||||||
m_queue(NULL),
|
m_queue(NULL),
|
||||||
m_worker(NULL),
|
|
||||||
m_Mysql(NULL),
|
m_Mysql(NULL),
|
||||||
m_connectionInfo(connInfo),
|
m_connectionInfo(connInfo),
|
||||||
m_connectionFlags(CONNECTION_SYNCH) { }
|
m_connectionFlags(CONNECTION_SYNCH) { }
|
||||||
@@ -50,24 +49,26 @@ m_Mysql(NULL),
|
|||||||
m_connectionInfo(connInfo),
|
m_connectionInfo(connInfo),
|
||||||
m_connectionFlags(CONNECTION_ASYNC)
|
m_connectionFlags(CONNECTION_ASYNC)
|
||||||
{
|
{
|
||||||
m_worker = new DatabaseWorker(m_queue, this);
|
m_worker = Trinity::make_unique<DatabaseWorker>(m_queue, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
MySQLConnection::~MySQLConnection()
|
MySQLConnection::~MySQLConnection()
|
||||||
{
|
{
|
||||||
delete m_worker;
|
Close();
|
||||||
|
|
||||||
for (size_t i = 0; i < m_stmts.size(); ++i)
|
|
||||||
delete m_stmts[i];
|
|
||||||
|
|
||||||
if (m_Mysql)
|
|
||||||
mysql_close(m_Mysql);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MySQLConnection::Close()
|
void MySQLConnection::Close()
|
||||||
{
|
{
|
||||||
/// Only close us if we're not operating
|
// Stop the worker thread before the statements are cleared
|
||||||
delete this;
|
m_worker.reset();
|
||||||
|
|
||||||
|
m_stmts.clear();
|
||||||
|
|
||||||
|
if (m_Mysql)
|
||||||
|
{
|
||||||
|
mysql_close(m_Mysql);
|
||||||
|
m_Mysql = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 MySQLConnection::Open()
|
uint32 MySQLConnection::Open()
|
||||||
@@ -412,7 +413,7 @@ int MySQLConnection::ExecuteTransaction(SQLTransaction& transaction)
|
|||||||
MySQLPreparedStatement* MySQLConnection::GetPreparedStatement(uint32 index)
|
MySQLPreparedStatement* MySQLConnection::GetPreparedStatement(uint32 index)
|
||||||
{
|
{
|
||||||
ASSERT(index < m_stmts.size());
|
ASSERT(index < m_stmts.size());
|
||||||
MySQLPreparedStatement* ret = m_stmts[index];
|
MySQLPreparedStatement* ret = m_stmts[index].get();
|
||||||
if (!ret)
|
if (!ret)
|
||||||
TC_LOG_ERROR("sql.sql", "Could not fetch prepared statement %u on database `%s`, connection type: %s.",
|
TC_LOG_ERROR("sql.sql", "Could not fetch prepared statement %u on database `%s`, connection type: %s.",
|
||||||
index, m_connectionInfo.database.c_str(), (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
|
index, m_connectionInfo.database.c_str(), (m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
|
||||||
@@ -424,16 +425,12 @@ void MySQLConnection::PrepareStatement(uint32 index, const char* sql, Connection
|
|||||||
{
|
{
|
||||||
m_queries.insert(PreparedStatementMap::value_type(index, std::make_pair(sql, flags)));
|
m_queries.insert(PreparedStatementMap::value_type(index, std::make_pair(sql, flags)));
|
||||||
|
|
||||||
// For reconnection case
|
|
||||||
if (m_reconnecting)
|
|
||||||
delete m_stmts[index];
|
|
||||||
|
|
||||||
// Check if specified query should be prepared on this connection
|
// Check if specified query should be prepared on this connection
|
||||||
// i.e. don't prepare async statements on synchronous connections
|
// i.e. don't prepare async statements on synchronous connections
|
||||||
// to save memory that will not be used.
|
// to save memory that will not be used.
|
||||||
if (!(m_connectionFlags & flags))
|
if (!(m_connectionFlags & flags))
|
||||||
{
|
{
|
||||||
m_stmts[index] = NULL;
|
m_stmts[index].reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,8 +452,7 @@ void MySQLConnection::PrepareStatement(uint32 index, const char* sql, Connection
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MySQLPreparedStatement* mStmt = new MySQLPreparedStatement(stmt);
|
m_stmts[index] = Trinity::make_unique<MySQLPreparedStatement>(stmt);
|
||||||
m_stmts[index] = mStmt;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -477,7 +473,7 @@ PreparedResultSet* MySQLConnection::Query(PreparedStatement* stmt)
|
|||||||
return new PreparedResultSet(stmt->m_stmt->GetSTMT(), result, rowCount, fieldCount);
|
return new PreparedResultSet(stmt->m_stmt->GetSTMT(), result, rowCount, fieldCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MySQLConnection::_HandleMySQLErrno(uint32 errNo)
|
bool MySQLConnection::_HandleMySQLErrno(uint32 errNo, uint8 attempts /*= 5*/)
|
||||||
{
|
{
|
||||||
switch (errNo)
|
switch (errNo)
|
||||||
{
|
{
|
||||||
@@ -486,9 +482,21 @@ bool MySQLConnection::_HandleMySQLErrno(uint32 errNo)
|
|||||||
case CR_INVALID_CONN_HANDLE:
|
case CR_INVALID_CONN_HANDLE:
|
||||||
case CR_SERVER_LOST_EXTENDED:
|
case CR_SERVER_LOST_EXTENDED:
|
||||||
{
|
{
|
||||||
|
if (m_Mysql)
|
||||||
|
{
|
||||||
|
TC_LOG_ERROR("sql.sql", "Lost the connection to the MySQL server!");
|
||||||
|
|
||||||
|
mysql_close(GetHandle());
|
||||||
|
m_Mysql = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*no break*/
|
||||||
|
}
|
||||||
|
case CR_CONN_HOST_ERROR:
|
||||||
|
{
|
||||||
|
TC_LOG_INFO("sql.sql", "Attempting to reconnect to the MySQL server...");
|
||||||
|
|
||||||
m_reconnecting = true;
|
m_reconnecting = true;
|
||||||
uint64 oldThreadId = mysql_thread_id(GetHandle());
|
|
||||||
mysql_close(GetHandle());
|
|
||||||
|
|
||||||
uint32 const lErrno = Open();
|
uint32 const lErrno = Open();
|
||||||
if (!lErrno)
|
if (!lErrno)
|
||||||
@@ -496,24 +504,37 @@ bool MySQLConnection::_HandleMySQLErrno(uint32 errNo)
|
|||||||
// Don't remove 'this' pointer unless you want to skip loading all prepared statements...
|
// Don't remove 'this' pointer unless you want to skip loading all prepared statements...
|
||||||
if (!this->PrepareStatements())
|
if (!this->PrepareStatements())
|
||||||
{
|
{
|
||||||
TC_LOG_ERROR("sql.sql", "Could not re-prepare statements!");
|
TC_LOG_FATAL("sql.sql", "Could not re-prepare statements!");
|
||||||
Close();
|
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||||
return false;
|
std::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
TC_LOG_INFO("sql.sql", "Connection to the MySQL server is active.");
|
TC_LOG_INFO("sql.sql", "Successfully reconnected to %s @%s:%s (%s).",
|
||||||
if (oldThreadId != mysql_thread_id(GetHandle()))
|
m_connectionInfo.database.c_str(), m_connectionInfo.host.c_str(), m_connectionInfo.port_or_socket.c_str(),
|
||||||
TC_LOG_INFO("sql.sql", "Successfully reconnected to %s @%s:%s (%s).",
|
(m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
|
||||||
m_connectionInfo.database.c_str(), m_connectionInfo.host.c_str(), m_connectionInfo.port_or_socket.c_str(),
|
|
||||||
(m_connectionFlags & CONNECTION_ASYNC) ? "asynchronous" : "synchronous");
|
|
||||||
|
|
||||||
m_reconnecting = false;
|
m_reconnecting = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's possible this attempted reconnect throws 2006 at us. To prevent crazy recursive calls, sleep here.
|
if ((--attempts) == 0)
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(3)); // Sleep 3 seconds
|
{
|
||||||
return _HandleMySQLErrno(lErrno); // Call self (recursive)
|
// Shut down the server when the mysql server isn't
|
||||||
|
// reachable for some time
|
||||||
|
TC_LOG_FATAL("sql.sql", "Failed to reconnect to the MySQL server, "
|
||||||
|
"terminating the server to prevent data corruption!");
|
||||||
|
|
||||||
|
// We could also initiate a shutdown through using std::raise(SIGTERM)
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// It's possible this attempted reconnect throws 2006 at us.
|
||||||
|
// To prevent crazy recursive calls, sleep here.
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(3)); // Sleep 3 seconds
|
||||||
|
return _HandleMySQLErrno(lErrno, attempts); // Call self (recursive)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case ER_LOCK_DEADLOCK:
|
case ER_LOCK_DEADLOCK:
|
||||||
|
|||||||
@@ -116,18 +116,18 @@ class MySQLConnection
|
|||||||
virtual void DoPrepareStatements() = 0;
|
virtual void DoPrepareStatements() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::vector<MySQLPreparedStatement*> m_stmts; //! PreparedStatements storage
|
std::vector<std::unique_ptr<MySQLPreparedStatement>> m_stmts; //! PreparedStatements storage
|
||||||
PreparedStatementMap m_queries; //! Query storage
|
PreparedStatementMap m_queries; //! Query storage
|
||||||
bool m_reconnecting; //! Are we reconnecting?
|
bool m_reconnecting; //! Are we reconnecting?
|
||||||
bool m_prepareError; //! Was there any error while preparing statements?
|
bool m_prepareError; //! Was there any error while preparing statements?
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _HandleMySQLErrno(uint32 errNo);
|
bool _HandleMySQLErrno(uint32 errNo, uint8 attempts = 5);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ProducerConsumerQueue<SQLOperation*>* m_queue; //! Queue shared with other asynchronous connections.
|
ProducerConsumerQueue<SQLOperation*>* m_queue; //! Queue shared with other asynchronous connections.
|
||||||
DatabaseWorker* m_worker; //! Core worker task.
|
std::unique_ptr<DatabaseWorker> m_worker; //! Core worker task.
|
||||||
MYSQL * m_Mysql; //! MySQL Handle.
|
MYSQL* m_Mysql; //! MySQL Handle.
|
||||||
MySQLConnectionInfo& m_connectionInfo; //! Connection info (used for logging)
|
MySQLConnectionInfo& m_connectionInfo; //! Connection info (used for logging)
|
||||||
ConnectionFlags m_connectionFlags; //! Connection flags (for preparing relevant statements)
|
ConnectionFlags m_connectionFlags; //! Connection flags (for preparing relevant statements)
|
||||||
std::mutex m_Mutex;
|
std::mutex m_Mutex;
|
||||||
|
|||||||
Reference in new issue
Block a user