Qt: Best practice for a single instance app protection
Asked Answered
T

7

50

QSingleApplication? QMutex? QSharedMemory? I'm looking for something that will work smoothly in Windows, OSX and Linux (Ubuntu). Using Qt 4.7.1

Telega answered 15/2, 2011 at 16:40 Comment(3)
#783712Margarito
the links there go to some "mobility" API with camera, roaming etc functions. Are you sure this is where you obtain QtSingleApplication?Telega
@The link in the answer is correct doc.trolltech.com/solutions/4/qtsingleapplication/…, the QtSingleApplication link in the question is wrong.Margarito
D
94

Simple solution, that does what you want. Without network dependency (as QtSingleApplication) and without any overhead.

Usage:

int main()
{
    RunGuard guard( "some_random_key" );
    if ( !guard.tryToRun() )
        return 0;

    QAppplication a(/*...*/);
    // ...
}

RunGuard.h

#ifndef RUNGUARD_H
#define RUNGUARD_H

#include <QObject>
#include <QSharedMemory>
#include <QSystemSemaphore>


class RunGuard
{

public:
    RunGuard( const QString& key );
    ~RunGuard();

    bool isAnotherRunning();
    bool tryToRun();
    void release();

private:
    const QString key;
    const QString memLockKey;
    const QString sharedmemKey;

    QSharedMemory sharedMem;
    QSystemSemaphore memLock;

    Q_DISABLE_COPY( RunGuard )
};


#endif // RUNGUARD_H

RunGuard.cpp

#include "RunGuard.h"

#include <QCryptographicHash>


namespace
{

QString generateKeyHash( const QString& key, const QString& salt )
{
    QByteArray data;

    data.append( key.toUtf8() );
    data.append( salt.toUtf8() );
    data = QCryptographicHash::hash( data, QCryptographicHash::Sha1 ).toHex();

    return data;
}

}


RunGuard::RunGuard( const QString& key )
    : key( key )
    , memLockKey( generateKeyHash( key, "_memLockKey" ) )
    , sharedmemKey( generateKeyHash( key, "_sharedmemKey" ) )
    , sharedMem( sharedmemKey )
    , memLock( memLockKey, 1 )
{
    memLock.acquire();
    {
        QSharedMemory fix( sharedmemKey );    // Fix for *nix: http://habrahabr.ru/post/173281/
        fix.attach();
    }
    memLock.release();
}

RunGuard::~RunGuard()
{
    release();
}

bool RunGuard::isAnotherRunning()
{
    if ( sharedMem.isAttached() )
        return false;

    memLock.acquire();
    const bool isRunning = sharedMem.attach();
    if ( isRunning )
        sharedMem.detach();
    memLock.release();

    return isRunning;
}

bool RunGuard::tryToRun()
{
    if ( isAnotherRunning() )   // Extra check
        return false;

    memLock.acquire();
    const bool result = sharedMem.create( sizeof( quint64 ) );
    memLock.release();
    if ( !result )
    {
        release();
        return false;
    }

    return true;
}

void RunGuard::release()
{
    memLock.acquire();
    if ( sharedMem.isAttached() )
        sharedMem.detach();
    memLock.release();
}
Dauphine answered 27/1, 2015 at 13:53 Comment(26)
This works for me, if the application crashes, it will allow to restart(that was my problem), thanks @SaZThai
@SaZ thanks for posting this code - I just tried it in my app - it works first time :)Gemmation
Wow. Worked perfectly! Thanks so much :) Just had to include RunGuard.h in main.Petroglyph
Is it also possible to communicate something to the other instance running? For example when the user tries to open a file but another instance of the application is already running, then I want that file to be opened by the existing instance. And what's that fix for in the constructor?Calendre
Another question: couldn't you just use QSharedMemory::lock() instead of a separate QSystemSemaphore?Calendre
@user1488118 1) you are talking about IPC, it is not related to current question. QtSingleApplication can communicate. Or you can create your own mechanism, based on QLocalSocket. 2) No, QSharedMemory::lock() is not enough, but it requeres a long topic, to describe, why.Dauphine
Awesome, what license does your snippet? Am I free to use it in my GPLv2 thingy with this header: /* Author: Dmitry Sazonov * StackOverflow: https://mcmap.net/q/347654/-qt-best-practice-for-a-single-instance-app-protection * Used with permission from original author. * / ??Laurencelaurene
@AoeAoe it's a code sample, there are no restrictions. Feel free to use it even without any remarks.Dauphine
There is LNK4042: object specified more than once; extras ignored warning in runguard.obj. Any idea why and how to remove it?Uro
@MustafaChelik it's a linker warning. It couldn't be related to .obj file. Use verbose linker output for deep diagnostic.Dauphine
You are right Dmitry. After run qmake and rebuild the warning went. Thank you.Uro
Why exactly are you using a hash for the keys?Kone
@Kone it doesn't matter what to use. I use it for preventing possible glitches with other global kernel objectsDauphine
Some have asked (and I wondered) why not just use QSharedMemory::lock() and not bother with the system semaphore. I think it has to do with handling the fact that there could be multiple users on the system. The shared memory is available to all users so we need the system semaphore to ensure only one user accesses it at a time. This article explains it fairly well: blog.aeguana.com/2015/10/15/…Custodial
not working for me with Windows 10 and Qt 5.5. it blocks multiple instances with the same Windows login, but not with multiple Windows logins.Bisque
@PatrickParker I think that it is not possible to answer your question with a cross-platform solutions or with Qt only code. I can recommend you to use platform-specific things. There is a link to an article (russian) that will solve your question: RSDN. You can take code samples from here or use a translator (article is very simple). Or you may ask your question with "WinAPI" tag.Dauphine
@DmitrySazonov thanks for the link. I considered using a Global Windows Mutex, in the end I went with a CreateFile lock similar to the one here: https://mcmap.net/q/355463/-use-win-api-to-determine-if-an-instance-of-an-executable-is-already-running since I only care about preventing instances which would write to the same resource folder.Bisque
@PatrickParker FYI. But be careful with situations when application may crash and lock file may stay alive.Dauphine
Too bad QT doesn't have a reliable implementation for the QLockFile: it's somewhat useless if you need to hold it indefinitely, without staleness checks; because it easily gets orphaned if the program exits abnormally. Handles from CreateFile at least are reliably closed by the OS.Bisque
@PatrickParker I think that is it not an issue of Qt. It is very difficult to implement such stuff for all platforms that are supported by Qt. So in your case it's better to use platform-specific code.Dauphine
Is it required to hash the keys? For me it works even without hashing them or am I missing something?Najera
@MaheshJagtap no, it doesn't. You can generate any string. But the best way is to have unique string. If another application will use kernel object with same name - you can't start your app. It will think that it's already started. Anyway, hashing is done only once, it should not affect performance at all. Resources to start a process is much higher.Dauphine
This is an awesome implementation! Thanks so much, just what I needed!Hyla
Not worked on Win10 + Qt 5.15.2 –Infanticide
@VincentSit could you provide some additional debug information?Dauphine
@Dmitry Sazonov It's working. It's my problem. I passed in a different key each time.Infanticide
H
13

As QtSingleApplication is relatively obsolete and unmaintained anymore, I wrote a replacement, called SingleApplication.

It is based on QSharedMemory and uses a QLocalServer to notify the parent process of the new instance being spawn. It works on all platforms and is compatible supports Qt 5 and Qt 6.

The full code and documentation are available here.

Basic Example:

int main(int argc, char *argv[])
{
    SingleApplication app( argc, argv );

    return app.exec();
}

Advanced Example

Among other things it supports sending messages between the newly spawned instance and the primary instance, for example:

int main(int argc, char *argv[])
{
    SingleApplication app( argc, argv, true );

    if( app.isSecondary() ) {
        app.sendMessage( app.arguments().join(' ')).toUtf8() );
        app.exit( 0 );
    }

    return app.exec();
}
Hammered answered 14/1, 2016 at 20:56 Comment(6)
Thanks for posting this. Would you be open to a pull request that adds a "command" to be sent with the connection? I would like to optionally execute a command like "open file".Gaytan
signal( SIGILL, SingleApplicationPrivate::terminate ); // 4 LOL, did you even try this? I suppose no. This thing doesn't work.Thus
@Thus What exactly is the problem here?Hammered
Ah sorry I misread it as SIGKILL. Anyway it's broken because in case of sigkill it doesn't do any cleanup.Thus
I know that I cannot handle SIGKILL, but I used another hack to handle that case. By initializing and then explicitly deleting the QSharedMemory instance the kernel clears the block if there aren't any active processes attached to it.Hammered
github.com/itay-grudev/SingleApplication/blob/master/…Hammered
C
2

You can use QSharedMemory with a specific key and check if the shared memory with that key could be created or not. If it is nor able to create it, then an instance is already run:

QSharedMemory sharedMemory;
sharedMemory.setKey("MyApplicationKey");

if (!sharedMemory.create(1))
{
    QMessageBox::warning(this, tr("Warning!"), tr("An instance of this application is running!") );

    exit(0); // Exit already a process running
}
Clovis answered 22/11, 2014 at 6:34 Comment(2)
Solution is not complete. There are a lot of possible glitches. You don't check cases when app is crashed. You may check my answer complete solution.Dauphine
This solution is horribly incomplete indeed. -1Tivoli
B
2

for Windows:

HANDLE g_app_mutex = NULL;

bool check_one_app_instance()
{
    g_app_mutex = ::CreateMutex(NULL, FALSE, L"8BD290769B404A7816985M9E505CF9AD64"); // this any different key as string
    if(GetLastError() == ERROR_ALREADY_EXISTS)
    {
        CloseHandle(g_app_mutex);
        return false;
    }

    return true;
}
Bennybenoit answered 28/2, 2016 at 12:51 Comment(0)
T
0

I am using this solution for now.

However it has the drawback that the program can only be run once by the user, even if they login from multiple locations at the same time.

singleinstance.h

#ifndef SINGLEINSTANCE_H
#define SINGLEINSTANCE_H

typedef enum {
    SYSTEM,
    SESSION,
} scope_t;

class SingleInstance
{
public:
    static bool unique(QString key, scope_t scope);
};

#endif // SINGLEINSTANCE_H

singleinstance.cpp

#include <QLockFile>
#include <QProcessEnvironment>

#include "singleinstance.h"

/**
 * @brief filename
 * @param key
 * @param scope
 * @return a fully qualified filename
 *
 * Generates an appropriate filename for the lock
 */
static QString filename(QString key, scope_t scope) {

    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
    QString tmp = env.value("TEMP", "/tmp") + "/";
    QString user = env.value("USER", "alfio");


    QString r;                                                                                                                                                                         
    switch (scope) {                                                                                                                                                                   
        case SYSTEM:                                                                                                                                                                   
            r = tmp;                                                                                                                                                                   
            break;
        case SESSION:
            //FIXME this will prevent trabucco to run in multiple X11 sessions
            r = env.value("XDG_RUNTIME_DIR", tmp + user) + "/";
            break;
    }
    return r + key + ".lock";
}

/**
 * @brief SingleInstance::unique
 * @param key the unique name of the program
 * @param scope wether it needs to be system-wide or session-wide
 * @return true if this is the only instance
 *
 * Make sure that this instance is unique.
 */
bool SingleInstance::unique(QString key, scope_t scope) {
    QLockFile* lock = new QLockFile(filename(key, scope));
    bool r = lock->tryLock();
    if (!r)
        delete lock;
    return r;
}
Thus answered 24/8, 2016 at 7:31 Comment(4)
What you will do if you have no write access to temp folder? Also it is worse for performance.Dauphine
In a machine that works properly, you do have access to that. If you have no /tmp, you can't even do a graphical login. Performances hardly matter since it's a check done once when starting the program.Thus
It depends on your platform. I created an applications for Windows XP embedded with very restricted access. There where only one writable folder for application needs and path to this folder was provided in runtime. So checking for locked files was very expensive operation.Dauphine
who is the owner of QLockFile* lock? it appears to be a dangling resourceBisque
M
0

for linux:

//----------------------------------

QProcess *m_prSystemCall;
m_prSystemCall = new QProcess();

QString Commnd = "pgrep  " + qApp->applicationDisplayName();
m_prSystemCall->start(Commnd);
m_prSystemCall->waitForFinished(8000);
QString output(m_prSystemCall->readAllStandardOutput());
QStringList AppList = output.split("\n", QString::SkipEmptyParts);
qDebug() <<"pgrep out:"<<AppList;
for(int i=0;i<AppList.size()-1;i++)
{
    Commnd = "kill " + AppList.at(i);
    m_prSystemCall->start(Commnd);
    m_prSystemCall->waitForFinished(8000);
}

//-------------------------------------------------------

and for Windows:

#include <tlhelp32.h>
#include <comdef.h>

QString pName = qApp->applicationDisplayName();
pName += ".exe";
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);

HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

if (Process32First(snapshot, &entry) == TRUE)
{
    DWORD myPID =  GetCurrentProcessId();
    while (Process32Next(snapshot, &entry) == TRUE)
    {
        const WCHAR* wc = entry.szExeFile ;
        _bstr_t b(wc);
        const char* c = b;

        if (stricmp(c, pName.toStdString().c_str()) == 0)
        {
            HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);

            qDebug() <<"myPID: "<< myPID << "entry.th32ProcessID" << entry.th32ProcessID;
            if(myPID != entry.th32ProcessID)
                TerminateProcess(hProcess,0);
            QThread::msleep(10);
            CloseHandle(hProcess);
        }
    }

}

CloseHandle(snapshot);
Maleficent answered 25/9, 2017 at 5:9 Comment(0)
P
-1

According to Qt's doc, an acquired QSystemSemaphore won't be automatically released if the process crashes without calling its destructor under Unix-like OSes. Which could be the cause of a deadlock in another process trying to acquire the same semaphore. If you want to be 100% sure that your program properly handles crashes and if you don't insist on using Qt, you may want to use the other locking mechanisms which the operating systems do automatically release when the process dies - for example, lockf() and the O_EXLOCK flag passed to open() which are mentioned in How do I recover a semaphore when the process that decremented it to zero crashes? or flock(). In fact, creating of shared memory is no longer needed if flock() is used. Simply using flock() is enough to make single instance app protection.

If recovering semaphore from crashes in Unix doesn't matter, I think that RunGuard from Dmitry Sazonov's answer could still be somewhat simplified:

  1. The destructor ~RunGuard() and RunGuard::release() may be taken off since QSharedMemory will automatically detach from the shared memory segment upon its destruction, as in Qt's doc for QSharedMemory::~QSharedMemory(): "The destructor clears the key, which forces the shared memory object to detach from its underlying shared memory segment.".

  2. RunGuard::isAnotherRunning() may also be taken off, too. The goal is exclusive execution. As @Nejat has mentioned, we can merely take advantage of the fact there could be at most one shared memory segment being created for a given key at any time, as in Qt's doc for QSharedMemory::create(): "If a shared memory segment identified by the key already exists, the attach operation is not performed and false is returned."

  3. If I understand correctly, the purpose of "fix" QSharedMemory object in the constructor is to destroy the shared memory segment which survives due to the previous process crash, as in Qt's doc: "Unix: ... When the last thread or process that has an instance of QSharedMemory attached to a particular shared memory segment detaches from the segment by destroying its instance of QSharedMemory, the Unix kernel release the shared memory segment. But if that last thread or process crashes without running the QSharedMemory destructor, the shared memory segment survives the crash.". When "fix" gets destructed, an implicit detach() should be called by its destructor and the surviving shared memory segment, if any, will be released.

  4. Not sure if QSharedMemory is thread-safe/process-safe or not. Otherwise, the code related to memLock may be further removed if thread-safety is handled internally by QSharedMemory. On the other hand, fix should also be protected by memLock if the safety is an issue:

    RunGuard::RunGuard( const QString& key )
        : key( key )
        , memLockKey( generateKeyHash( key, "_memLockKey" ) )
        , sharedMemKey( generateKeyHash( key, "_sharedMemKey" ) )
        , sharedMem( sharedMemKey )
        , memLock( memLockKey, 1 )
    {
        memLock.acquire();
        {
            QSharedMemory fix( sharedMemKey ); // Fix for *nix: http://habrahabr.ru/post/173281/
            fix.attach();
        }
        memLock.release();
    }
    

    because an explicit attach() and an implicit detach() are called around fix.

  5. The simplified version of RunGuard is as follows:

    Usage:

    int main()
    {
        RunGuard guard( "some_random_key" );
        if ( !guard.tryToRun() )
            return 0;
    
        QAppplication a(/*...*/);
        // ...
    }
    

    runGuard.h:

    #ifndef RUNGUARD_H
    #define RUNGUARD_H
    
    #include <QObject>
    #include <QSharedMemory>
    #include <QSystemSemaphore>
    
    class RunGuard
    {
    
    public:
        RunGuard( const QString& key );
        bool tryToRun();
    
    private:
        const QString key;
        const QString memLockKey;
        const QString sharedMemKey;
    
        QSharedMemory sharedMem;
        QSystemSemaphore memLock;
    
        Q_DISABLE_COPY( RunGuard )
    };
    
    
    #endif // RUNGUARD_H
    

    runGuard.cpp:

    #include "runGuard.h"
    #include <QCryptographicHash>
    
    namespace
    {
    
        QString generateKeyHash( const QString& key, const QString& salt )
        {
            QByteArray data;
            data.append( key.toUtf8() );
            data.append( salt.toUtf8() );
            data = QCryptographicHash::hash( data, QCryptographicHash::Sha1 ).toHex();
            return data;
    }
    
    }
    
    RunGuard::RunGuard( const QString& key )
        : key( key )
        , memLockKey( generateKeyHash( key, "_memLockKey" ) )
        , sharedMemKey( generateKeyHash( key, "_sharedMemKey" ) )
        , sharedMem( sharedMemKey )
        , memLock( memLockKey, 1 )
    {
        QSharedMemory fix( sharedMemKey ); // Fix for *nix: http://habrahabr.ru/post/173281/
        fix.attach();
    }
    
    bool RunGuard::tryToRun()
    {
        memLock.acquire();
        const bool result = sharedMem.create( sizeof( quint64 ) );
        memLock.release();
        if ( !result )
            return false;
    
        return true;
    }
    
  6. There is a possible race condition here:

    bool RunGuard::tryToRun()
    {
        if ( isAnotherRunning() )   // Extra check
            return false;
                                                                   // (tag1)
        memLock.acquire();
        const bool result = sharedMem.create( sizeof( quint64 ) ); // (tag2)
        memLock.release();
        if ( !result )
        {
            release();                                             // (tag3)
            return false;
        }
    
        return true;
    }
    

    Consider the scenario:

    When the current process ProcCur runs to (tag1) the following happens: (note that (tag1) is outside of lock protection)

    1. Another process ProcOther using RunGuard starts to run.
    2. ProcOther runs to (tag2) and successfully creates the shared memory.
    3. ProcOther crashes before it calls release() at (tag3).
    4. ProcCur continues running from (tag1).
    5. ProcCur runs to (tag2) and attempts to create shared memory. However, sharedMem.create() will return false because ProcOther have left a created one. As we can see in the doc of QSharedMemory::create(): "If a shared memory segment identified by the key already exists, the attach operation is not performed and false is returned."
    6. Finally, RunGuard::tryToRun() in ProcCur will return false, which is not as expected because ProcCur is the only existing process using RunGuard.
Pharyngoscope answered 25/10, 2015 at 14:1 Comment(9)
1. It is nesessary in case, when you want to do some extra logic and when you need to release RunGuard directly. 2. This check may be used for other instances, if IPC is required. 3. Yes, this is a fix for *nix for your case. 4. QSystemSemaphore is used as cross-process "mutex". It is necessary to protect from some race conditions. It is not a "waiter". RunGuard is not thread-safe, because thread safety is not necessary here. But RunGuard must be proccess safe.Dauphine
It is still unknown if QSharedMemory is process-safe/thread-safe or not. So protecting the member function calls of QSharedMemory by QSystemSemaphore could be trivial. There should be documents somewhere mentioned that QSharedMemory is not process-safe/thread-safe, but I cannot find out any of such documents. Hence, I said "memLock MAY be further removed".Pharyngoscope
Agree with protection of "fix". But I think that you have some misunderstanding what is process-safety and thread-safety. It is not necessary to care about thread safety in RunGuard at all. memLock is used to prevend some races.Dauphine
By the way, "fix" should have been protected by "memLock" too if QSharedMemory had not been process-safe/thread-safe. As I have modified 4. above.Pharyngoscope
According to this: "By extension, a class is said to be reentrant if its member functions can be called safely from multiple threads, as long as each thread uses a different instance of the class. The class is thread-safe if its member functions can be called safely from multiple threads, even if all the threads use the same instance of the class." If RunGuard is intended to be used only once in each app, the QSharedMemory object of it is the only instance which is different from that of all the other RunGuard's in the other apps.Pharyngoscope
you wrong. You are saying, that there are no difference between thread-unsafe and reentrant class. In Qt, reentrant means that you may use one instance in different threads, but you should guard access to object with some guards. You need to improve your skills in understanding multithreading and in understanding difference between thread and process.Dauphine
Btw, you need to read documentation carefully. Read in once again: "A reentrant function can also be called simultaneously from multiple threads, but only if each invocation uses its own data." There are nothing told about instances. Only about invokation.Dauphine
There is a possible race in the code. See 6. above.Pharyngoscope
@Pharyngoscope 6 seems more like the same platform-specific exception safety issue rather than a "race" conditionBisque

© 2022 - 2024 — McMap. All rights reserved.