[Server] Create the worker database interfaces through the factory (#7860)

This commit is contained in:
BruebachL 2026-11-10 12:16:34 +01:00 • committed by GitHub
parent 0caacb27c1
commit c4ac80e282
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 62 additions and 29 deletions

View file

@ -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()
{

View file

@ -49,7 +49,6 @@
#include <QRegularExpression>
#include <QSet>
#include <QSharedPointer>
#include <QSqlDatabase>
#include <QSsl>
#include <QString>
#include <QStringList>
@ -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>("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);
}

View file

@ -36,10 +36,6 @@
#include <QSslCertificate>
#include <QSslKey>
#include <QString>
// IWYU pragma: keep
// Q_DECLARE_METATYPE below needs the complete type.
#include <QSqlDatabase>
#include <QTcpServer>
#include <QWebSocketServer>
#include <QtGlobal>
@ -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<QString, bool> 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<AbstractServerSocketInterface *> 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()

View file

@ -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,

View file

@ -33,6 +33,7 @@ class Servatrice_DatabaseInterface : public Server_DatabaseInterface
private:
int instanceId;
QSqlDatabase sqlDatabase;
QSqlDatabase inheritedDatabase;
QHash<QString, QSqlQuery *> 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;