Search code examples
c++multithreadingqt5qsqldatabase

How to prevent name collisions when creating QSqlDatabase connection in multiple threads


I have multi-threaded QTcpServer and for each database request, it creates new Thread to keep server Responsive. So in each thread I have to creating new QSqlDatabase connection. But I keep getting name collisions between connections.

here is my sample code to recreate issue.:-

#include <QSqlDatabase>

class DBTask
{
public:
    DBTask(ClientSocket *socket,ConnectionWorker *connectionWorker);
    ~DBTask();

    static void initStatic();    

private:

    static QThreadPool *pool; // all addConnection() call be be called in QtConcurrent::run with this pool
    static QString host, user, type, password, name;
    static quint64 dbConnectionNumber;

    QSqlDatabase db;

    ClientSocket *socket;
    ConnectionWorker *connectionWorker;

    bool addDatabase() ;    
};

quint64 DBTask::dbConnectionNumber=0;

DBTask::DBTask(ClientSocket *socket, ConnectionWorker *connectionWorker):
    socket(socket),
    connectionWorker(connectionWorker)
{
    dbConnectionNumber++;
}

bool DBTask::addDatabase() {

    QSqlDatabase db = QSqlDatabase::addDatabase(type,QString::number(dbConnectionNumber));
    db.setHostName(host);
    db.setDatabaseName(name);
    db.setUserName(user);
    db.setPassword(password);

    if(!db.open()){
        qWarning() << "Error while opening database for socket " << socket << '\n' << db.lastError();
        return false;
    }
    else {
        return true;
    }
}

this works fine when I manually check my application with GUI with human speed But when I run a c++ test code which simulates thousands of requests like this:-

void connectionTest(){

    QThreadPool pool;
    pool.setMaxThreadCount(10);

    for(int i=0;i<10;i++){
        QtConcurrent::run(&pool,[this](){
            for(int i=0;i<1000;i++){
                login(i%2); // login function sends request to QTcpServer
            }
        });
    }
}

I get multiple errors like this:-

QSqlDatabasePrivate::removeDatabase: connection '10' is still in use, all queries will cease to work.
QSqlDatabasePrivate::addDatabase: duplicate connection name '10', old connection removed.
QSqlDatabasePrivate::removeDatabase: connection '10' is still in use, all queries will cease to work.
QSqlDatabasePrivate::addDatabase: duplicate connection name '10', old connection removed.

and Server crashes with segfault


Solution

  • Even if you make the counter atomic, a thread can still get interrupted in the DBTask::addDatabase method (before creating the connection), another one can increment the counter and then they both continue and create 2 connections with the same id. You need to make both operations (increment the counter and the connection creation) in one transaction: inside the DBTask::addDatabase, by making use of a mutex lock.