📜 ⬆️ ⬇️

Single Instance Applications on Qt

Now I am actively writing under Qt. And I got a problem to check when you start the application, and not whether it is already running. Under Linux. I didn’t want to build bicycles, I wanted to give birth to something using ready-made Qt classes, so that it was immediately cross-platform. In Windows, for example, there is a ready-made solution - named mutexes (or semaphores, it doesn't matter, the main thing is named). Began to google. Aha, in Qt there is something similar, called QSystemSemaphore . It works like this:

- create an instance of the QSystemSemaphore class, it creates a global (system) semaphore
- call acquire , reduce the semaphore count by one
- do something
- by calling release , increment the semaphore count by one

In general, the classic semaphore, only global. It is clear that if we create a semaphore with an initial counter value of 1 and call acquire , then until we call release no one else can capture the semaphore. Hurray, the solution is found! The following code of the single-instance application immediately appears:
')
- create semaphore
- capturing
- the application is running
- after shutdown, the application releases the semaphore

Everything is great, everything is beautiful. Documentation promises us that even if you do not call release (for example, the program has crumbled), the system will still return all units captured from it to the semaphore. But there is a problem. If the user starts the second instance of the application - how will he know about it? The call to the acquire blocking method and until the semaphore is released the application will “hang”, and the user is at a loss for something to wait.

It would seem - not a problem. For example, the same QMutex besides calling lock to capture a mutex, has a call and try_lock for a non-blocking capture attempt. There should be something similar in the system semaphore. But - alas - it is not. I have been googling long and unsuccessfully - the forums are full of similar topics and not a single working solution. Problem? Problem. Interesting? Quite. I undertook to solve it.

With the help of one QSystemSemaphore obviously nothing will be resolved. What else is in Qt from global? Still have QSharedMemory . Memory shared between different applications. It works like this:

- create an instance of the QSharedMemory class
- call attach to connect to already created memory
- if the connection failed - call create to create shared memory
- we work with shared memory (for example, we transfer some data to another process)
- the destructor itself causes detach and detachment from shared memory

It would seem - that's it! If the call to attach was successful, then the application is already running. If not, the application is started for the first time, we call create and, thus, we secure our superiority in the issue of ownership of shared memory (i.e., all other attach will already be successful). There are actually two problems here.

The first is racing. Two processes can invoke attachments at once, fail them, both will assume that they are the first and call create . One will create a memory, the other will fail. Not good, but, in general, tolerated. You can get around.

The second problem is more serious. It turns out the QSharedMemory Linux implementation QSharedMemory designed so that if QSharedMemory is not caused to memory, it is not destroyed. Simply put, if a process joins shared memory and then crashes, the memory will remain created. And the next attach call will succeed. Those. the abnormal termination of a single instance of the application will result in no more instances of the application being created. Hmm.

Thus, we have two classes - QSystemSemaphore and QSharedMemory , each of which partially implements the necessary functionality, but does not completely solve the final task. Maybe it will be possible to combine these two "underclass" harmoniously?

And yes! QSystemSemaphore solves the first problem QSharedMemory , the problem of racing. Code:

 QSystemSemaphore sema("<Unique name1>", 1); bool isRunning; sema.acquire(); QSharedMemory shmem("<Unique name2>"); if (shmem.attach()) { isRunning = true; } else { shmem.create(1); isRunning = false; } sema.release(); 


Simply put, the global semaphore synchronizes shared memory management. As they say, elementary, Watson.

The second problem, as it turned out, also has a solution. Pretty funny. In order for the Linux implementation of shared memory to check the counter of references to it and destroy it, if there are no more links, it is enough to join it and disconnect. And if, as a result of the abnormal termination of the previous instance of the application, there remains an unloaded shared memory with a zero (actually) reference counter, connecting to it and immediately disconnecting it will destroy this memory. And if the reference count is non-zero, it will remain the same. What, in general, and required.

The final code looks like this:

 QSystemSemaphore sema("<Unique name1>", 1); bool isRunning; sema.acquire(); { QSharedMemory shmem("<Unique name2>"); shmem.attach(); } QSharedMemory shmem("<Unique name2>"); if (shmem.attach()) { isRunning = true; } else { shmem.create(1); isRunning = false; } sema.release(); 


It is clear that the second instance of the QSharedMemory class QSharedMemory be kept until the end of the application. And then everything works exactly as needed.

Source: https://habr.com/ru/post/173281/


All Articles