diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h b/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h index d98946a67..10bffc5f6 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h @@ -833,6 +833,15 @@ public: virtual void purgeExpiredLegacyBackups() { } + /** @brief Creates the database interface a worker connection pool runs on; nullptr when unsupported. */ + virtual Server_DatabaseInterface *createWorkerInterface(int /* instanceId */) + { + return nullptr; + } + /** @brief Opens a worker interface's connection on its own thread; a no-op when unsupported. */ + virtual void initialiseWorkerDatabase() + { + } virtual void clearSessionTables() { diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index edd5ebe05..5f810fd62 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -49,7 +49,6 @@ #include #include #include -#include #include #include #include @@ -83,12 +82,16 @@ static constexpr qint64 kLegacyBackupSweepIntervalMs = 6 * 60 * 60 * 1000; Servatrice_GameServer::Servatrice_GameServer(Servatrice *_server, int _numberPools, - const QSqlDatabase &_sqlDatabase, + Server_DatabaseInterface *_databaseInterface, QObject *parent) : QTcpServer(parent), server(_server) { for (int i = 0; i < _numberPools; ++i) { - auto newDatabaseInterface = new Servatrice_DatabaseInterface(i, server); + auto *newDatabaseInterface = _databaseInterface->createWorkerInterface(i); + if (!newDatabaseInterface) { + qCCritical(ServatriceLog) << "Cannot create the database interface for pool" << i; + continue; + } auto newPool = new Servatrice_ConnectionPool(newDatabaseInterface); auto newThread = new QThread; @@ -100,8 +103,7 @@ Servatrice_GameServer::Servatrice_GameServer(Servatrice *_server, newThread->start(); server->watchWorkerThread(newThread); QMetaObject::invokeMethod( - newDatabaseInterface, - [newDatabaseInterface, _sqlDatabase] { newDatabaseInterface->initDatabase(_sqlDatabase); }, + newDatabaseInterface, [newDatabaseInterface] { newDatabaseInterface->initialiseWorkerDatabase(); }, Qt::BlockingQueuedConnection); connectionPools.append(newPool); @@ -122,6 +124,10 @@ Servatrice_GameServer::~Servatrice_GameServer() void Servatrice_GameServer::incomingConnection(qintptr socketDescriptor) { Servatrice_ConnectionPool *pool = findLeastUsedConnectionPool(); + if (!pool) { + qCCritical(ServatriceLog) << "No database pool available; refusing connection"; + return; + } auto ssi = new TcpServerSocketInterface(server, pool->getDatabaseInterface()); ssi->moveToThread(pool->thread()); @@ -134,6 +140,10 @@ void Servatrice_GameServer::incomingConnection(qintptr socketDescriptor) Servatrice_ConnectionPool *Servatrice_GameServer::findLeastUsedConnectionPool() { + if (connectionPools.isEmpty()) { + return nullptr; + } + int minClientCount = -1; int poolIndex = -1; QStringList debugStr; @@ -153,13 +163,17 @@ Servatrice_ConnectionPool *Servatrice_GameServer::findLeastUsedConnectionPool() Servatrice_WebsocketGameServer::Servatrice_WebsocketGameServer(Servatrice *_server, int _numberPools, - const QSqlDatabase &_sqlDatabase, + Server_DatabaseInterface *_databaseInterface, QObject *parent) : QWebSocketServer("Servatrice", QWebSocketServer::NonSecureMode, parent), server(_server) { for (int i = 0; i < _numberPools; ++i) { int poolNumber = WEBSOCKET_POOL_NUMBER + i; - auto newDatabaseInterface = new Servatrice_DatabaseInterface(poolNumber, server); + auto *newDatabaseInterface = _databaseInterface->createWorkerInterface(poolNumber); + if (!newDatabaseInterface) { + qCCritical(ServatriceLog) << "Cannot create the database interface for pool" << poolNumber; + continue; + } auto newPool = new Servatrice_ConnectionPool(newDatabaseInterface); auto newThread = new QThread; @@ -171,8 +185,7 @@ Servatrice_WebsocketGameServer::Servatrice_WebsocketGameServer(Servatrice *_serv newThread->start(); server->watchWorkerThread(newThread); QMetaObject::invokeMethod( - newDatabaseInterface, - [newDatabaseInterface, _sqlDatabase] { newDatabaseInterface->initDatabase(_sqlDatabase); }, + newDatabaseInterface, [newDatabaseInterface] { newDatabaseInterface->initialiseWorkerDatabase(); }, Qt::BlockingQueuedConnection); connectionPools.append(newPool); @@ -195,6 +208,10 @@ Servatrice_WebsocketGameServer::~Servatrice_WebsocketGameServer() void Servatrice_WebsocketGameServer::onNewConnection() { Servatrice_ConnectionPool *pool = findLeastUsedConnectionPool(); + if (!pool) { + qCCritical(ServatriceLog) << "No database pool available; refusing connection"; + return; + } auto ssi = new WebsocketServerSocketInterface(server, pool->getDatabaseInterface()); /* @@ -211,6 +228,10 @@ void Servatrice_WebsocketGameServer::onNewConnection() Servatrice_ConnectionPool *Servatrice_WebsocketGameServer::findLeastUsedConnectionPool() { + if (connectionPools.isEmpty()) { + return nullptr; + } + int minClientCount = -1; int poolIndex = -1; QStringList debugStr; @@ -243,7 +264,6 @@ Servatrice::Servatrice(QObject *parent) : Server(parent), authenticationMethod(AuthenticationNone), uptime(0), txBytes(0), rxBytes(0), shutdownTimer(nullptr) { - qRegisterMetaType("QSqlDatabase"); } Servatrice::~Servatrice() @@ -618,8 +638,7 @@ bool Servatrice::initServer() // SOCKET SERVER if (getNumberOfTCPPools() > 0) { - gameServer = - new Servatrice_GameServer(this, getNumberOfTCPPools(), servatriceDatabaseInterface->getDatabase(), this); + gameServer = new Servatrice_GameServer(this, getNumberOfTCPPools(), servatriceDatabaseInterface, this); gameServer->setMaxPendingConnections(1000); QHostAddress tcpHost = getServerTCPHost(); qDebug() << "Starting server on host" << tcpHost.toString() << "port" << getServerTCPPort(); @@ -633,8 +652,8 @@ bool Servatrice::initServer() // WEBSOCKET SERVER if (getNumberOfWebSocketPools() > 0) { - websocketGameServer = new Servatrice_WebsocketGameServer(this, getNumberOfWebSocketPools(), - servatriceDatabaseInterface->getDatabase(), this); + websocketGameServer = + new Servatrice_WebsocketGameServer(this, getNumberOfWebSocketPools(), servatriceDatabaseInterface, this); websocketGameServer->setMaxPendingConnections(1000); QHostAddress webSocketHost = getServerWebSocketHost(); qDebug() << "Starting websocket server on host" << webSocketHost.toString() << "port" @@ -708,7 +727,7 @@ qint64 Servatrice::getCardsInGamesTotal() const return total; } -void Servatrice::addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterface *databaseInterface) +void Servatrice::addDatabaseInterface(QThread *thread, Server_DatabaseInterface *databaseInterface) { databaseInterfaces.insert(thread, databaseInterface); } diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index 3dba23234..076bb026e 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -36,10 +36,6 @@ #include #include #include - -// IWYU pragma: keep -// Q_DECLARE_METATYPE below needs the complete type. -#include #include #include #include @@ -54,8 +50,6 @@ class QObject; class QThread; class SessionEvent; -Q_DECLARE_METATYPE(QSqlDatabase) - class QTimer; class Servatrice; class Servatrice_ConnectionPool; @@ -73,7 +67,7 @@ private: public: Servatrice_GameServer(Servatrice *_server, int _numberPools, - const QSqlDatabase &_sqlDatabase, + Server_DatabaseInterface *_databaseInterface, QObject *parent = nullptr); ~Servatrice_GameServer() override; @@ -92,7 +86,7 @@ private: public: Servatrice_WebsocketGameServer(Servatrice *_server, int _numberPools, - const QSqlDatabase &_sqlDatabase, + Server_DatabaseInterface *_databaseInterface, QObject *parent = nullptr); ~Servatrice_WebsocketGameServer() override; @@ -190,7 +184,7 @@ private: QString dbPrefix; QMap serverRequiredFeatureList; QString officialWarnings; - Servatrice_DatabaseInterface *servatriceDatabaseInterface; + Server_DatabaseInterface *servatriceDatabaseInterface; bool cleanupDatabaseForEmail(const QString &userName, EmailType type, const QString &token, qint64 rowId); int serverId; int uptime; @@ -339,7 +333,7 @@ public: QList getUsersWithAddressAsList(const QHostAddress &address) const; void incTxBytes(quint64 num); void incRxBytes(quint64 num); - void addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterface *databaseInterface); + void addDatabaseInterface(QThread *thread, Server_DatabaseInterface *databaseInterface); // Metrics (see [metrics] section in servatrice.ini.example) MetricsRegistry &getMetricsRegistry() diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index 3bca34c15..f2c310334 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -81,6 +81,18 @@ void Servatrice_DatabaseInterface::initDatabase(const QSqlDatabase &_sqlDatabase } } +Server_DatabaseInterface *Servatrice_DatabaseInterface::createWorkerInterface(int instanceId) +{ + auto *worker = new Servatrice_DatabaseInterface(instanceId, server); + worker->inheritedDatabase = sqlDatabase; + return worker; +} + +void Servatrice_DatabaseInterface::initialiseWorkerDatabase() +{ + initDatabase(inheritedDatabase); +} + bool Servatrice_DatabaseInterface::openDatabase(const QString &type, const QString &hostName, const QString &databaseName, diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index 628d29d1d..f9e4ec864 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -33,6 +33,7 @@ class Servatrice_DatabaseInterface : public Server_DatabaseInterface private: int instanceId; QSqlDatabase sqlDatabase; + QSqlDatabase inheritedDatabase; QHash preparedStatements; Servatrice *server; ServerInfo_User evalUserQueryResult(const QSqlQuery *query, bool complete, bool withId = false); @@ -72,13 +73,11 @@ public: const QString &userName, const QString &password) override; bool openDatabase(); + Server_DatabaseInterface *createWorkerInterface(int instanceId) override; + void initialiseWorkerDatabase() override; bool checkSql() override; QSqlQuery *prepareQuery(const QString &queryText); bool execSqlQuery(QSqlQuery *query); - const QSqlDatabase &getDatabase() - { - return sqlDatabase; - } bool activeUserExists(const QString &user) override; bool userExists(const QString &user) override;