/* * Copyright (C) 2008-2016 TrinityCore * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "DatabaseWorkerPool.h" #include "DatabaseEnv.h" #define MIN_MYSQL_SERVER_VERSION 50100u #define MIN_MYSQL_CLIENT_VERSION 50100u template DatabaseWorkerPool::DatabaseWorkerPool() : _queue(new ProducerConsumerQueue()), _async_threads(0), _synch_threads(0) { WPFatal(mysql_thread_safe(), "Used MySQL library isn't thread-safe."); WPFatal(mysql_get_client_version() >= MIN_MYSQL_CLIENT_VERSION, "TrinityCore does not support MySQL versions below 5.1"); WPFatal(mysql_get_client_version() == MYSQL_VERSION_ID, "Used MySQL library version (%s) does not match the version used to compile TrinityCore (%s).", mysql_get_client_info(), MYSQL_SERVER_VERSION); } template void DatabaseWorkerPool::SetConnectionInfo(std::string const& infoString, uint8 const asyncThreads, uint8 const synchThreads) { _connectionInfo = Trinity::make_unique(infoString); _async_threads = asyncThreads; _synch_threads = synchThreads; } template uint32 DatabaseWorkerPool::Open() { WPFatal(_connectionInfo.get(), "Connection info was not set!"); TC_LOG_INFO("sql.driver", "Opening DatabasePool '%s'. " "Asynchronous connections: %u, synchronous connections: %u.", GetDatabaseName(), _async_threads, _synch_threads); uint32 error = OpenConnections(IDX_ASYNC, _async_threads); if (error) return error; error = OpenConnections(IDX_SYNCH, _synch_threads); if (!error) { TC_LOG_INFO("sql.driver", "DatabasePool '%s' opened successfully. " SZFMTD " total connections running.", GetDatabaseName(), (_connections[IDX_SYNCH].size() + _connections[IDX_ASYNC].size())); } return error; } template void DatabaseWorkerPool::Close() { TC_LOG_INFO("sql.driver", "Closing down DatabasePool '%s'.", GetDatabaseName()); //! Closes the actualy MySQL connection. _connections[IDX_ASYNC].clear(); TC_LOG_INFO("sql.driver", "Asynchronous connections on DatabasePool '%s' terminated. " "Proceeding with synchronous connections.", GetDatabaseName()); //! Shut down the synchronous connections //! There's no need for locking the connection, because DatabaseWorkerPool<>::Close //! should only be called after any other thread tasks in the core have exited, //! meaning there can be no concurrent access at this point. _connections[IDX_SYNCH].clear(); TC_LOG_INFO("sql.driver", "All connections on DatabasePool '%s' closed.", GetDatabaseName()); } template bool DatabaseWorkerPool::PrepareStatements() { for (auto& connections : _connections) for (auto& connection : connections) { connection->LockIfReady(); if (!connection->PrepareStatements()) { connection->Unlock(); Close(); return false; } else connection->Unlock(); } return true; } template QueryResult DatabaseWorkerPool::Query(const char* sql, T* connection /*= nullptr*/) { if (!connection) connection = GetFreeConnection(); ResultSet* result = connection->Query(sql); connection->Unlock(); if (!result || !result->GetRowCount() || !result->NextRow()) { delete result; return QueryResult(NULL); } return QueryResult(result); } template PreparedQueryResult DatabaseWorkerPool::Query(PreparedStatement* stmt) { auto connection = GetFreeConnection(); PreparedResultSet* ret = connection->Query(stmt); connection->Unlock(); //! Delete proxy-class. Not needed anymore delete stmt; if (!ret || !ret->GetRowCount()) { delete ret; return PreparedQueryResult(NULL); } return PreparedQueryResult(ret); } template QueryResultFuture DatabaseWorkerPool::AsyncQuery(const char* sql) { BasicStatementTask* task = new BasicStatementTask(sql, true); // Store future result before enqueueing - task might get already processed and deleted before returning from this method QueryResultFuture result = task->GetFuture(); Enqueue(task); return result; } template PreparedQueryResultFuture DatabaseWorkerPool::AsyncQuery(PreparedStatement* stmt) { PreparedStatementTask* task = new PreparedStatementTask(stmt, true); // Store future result before enqueueing - task might get already processed and deleted before returning from this method PreparedQueryResultFuture result = task->GetFuture(); Enqueue(task); return result; } template QueryResultHolderFuture DatabaseWorkerPool::DelayQueryHolder(SQLQueryHolder* holder) { SQLQueryHolderTask* task = new SQLQueryHolderTask(holder); // Store future result before enqueueing - task might get already processed and deleted before returning from this method QueryResultHolderFuture result = task->GetFuture(); Enqueue(task); return result; } template void DatabaseWorkerPool::CommitTransaction(SQLTransaction transaction) { #ifdef TRINITY_DEBUG //! Only analyze transaction weaknesses in Debug mode. //! Ideally we catch the faults in Debug mode and then correct them, //! so there's no need to waste these CPU cycles in Release mode. switch (transaction->GetSize()) { case 0: TC_LOG_DEBUG("sql.driver", "Transaction contains 0 queries. Not executing."); return; case 1: TC_LOG_DEBUG("sql.driver", "Warning: Transaction only holds 1 query, consider removing Transaction context in code."); break; default: break; } #endif // TRINITY_DEBUG Enqueue(new TransactionTask(transaction)); } template void DatabaseWorkerPool::DirectCommitTransaction(SQLTransaction& transaction) { T* connection = GetFreeConnection(); int errorCode = connection->ExecuteTransaction(transaction); if (!errorCode) { connection->Unlock(); // OK, operation succesful return; } //! Handle MySQL Errno 1213 without extending deadlock to the core itself /// @todo More elegant way if (errorCode == ER_LOCK_DEADLOCK) { uint8 loopBreaker = 5; for (uint8 i = 0; i < loopBreaker; ++i) { if (!connection->ExecuteTransaction(transaction)) break; } } //! Clean up now. transaction->Cleanup(); connection->Unlock(); } template void DatabaseWorkerPool::EscapeString(std::string& str) { if (str.empty()) return; char* buf = new char[str.size() * 2 + 1]; EscapeString(buf, str.c_str(), uint32(str.size())); str = buf; delete[] buf; } template void DatabaseWorkerPool::KeepAlive() { //! Ping synchronous connections for (auto& connection : _connections[IDX_SYNCH]) { if (connection->LockIfReady()) { connection->Ping(); connection->Unlock(); } } //! Assuming all worker threads are free, every worker thread will receive 1 ping operation request //! If one or more worker threads are busy, the ping operations will not be split evenly, but this doesn't matter //! as the sole purpose is to prevent connections from idling. auto const count = _connections[IDX_ASYNC].size(); for (uint8 i = 0; i < count; ++i) Enqueue(new PingOperation); } template uint32 DatabaseWorkerPool::OpenConnections(InternalIndex type, uint8 numConnections) { for (uint8 i = 0; i < numConnections; ++i) { // Create the connection auto connection = [&] { switch (type) { case IDX_ASYNC: return Trinity::make_unique(_queue.get(), *_connectionInfo); case IDX_SYNCH: return Trinity::make_unique(*_connectionInfo); default: ABORT(); } }(); if (uint32 error = connection->Open()) { // Failed to open a connection or invalid version, abort and cleanup _connections[type].clear(); return error; } else if (mysql_get_server_version(connection->GetHandle()) < MIN_MYSQL_SERVER_VERSION) { TC_LOG_ERROR("sql.driver", "TrinityCore does not support MySQL versions below 5.1"); return 1; } else { _connections[type].push_back(std::move(connection)); } } // Everything is fine return 0; } template T* DatabaseWorkerPool::GetFreeConnection() { uint8 i = 0; auto const num_cons = _connections[IDX_SYNCH].size(); T* connection = nullptr; //! Block forever until a connection is free for (;;) { connection = _connections[IDX_SYNCH][++i % num_cons].get(); //! Must be matched with t->Unlock() or you will get deadlocks if (connection->LockIfReady()) break; } return connection; } template class TC_DATABASE_API DatabaseWorkerPool; template class TC_DATABASE_API DatabaseWorkerPool; template class TC_DATABASE_API DatabaseWorkerPool;