Unsolved Limiting a number of processes/instances
-
Hi there,
I have a requirement similar to singleton, but limiting the number of instances to a specific number. In general those different instances are running as different processes on the same machine.
The current implementation is based on QSharedMemory
In the shared memory basically time tags are stored. One time tag per process for its "start", "sign of still living" and when the "process ended". The "sign of still living" may be screened and entries may be removed after some expiration time. Also entries marked as ended may be removed from table.
Obviously the design has some flaws. A crashing application cannot mark its entry with "process ended". Also a process with some blockage for a while cannot send a "sign of still living" may be eliminated after its expiration and suddenly the entry may used by two processes.
I appreciate some alternative designs or extensions. Any ideas around?
-
@koahnig mmh, maybe creating an "Instance Manager" might help. It follows what you do so far with QSharedMemory / RemoteObject.
But you could implement some kind of heartbeat-functionality, the manager uses to communicate with the instances.If a timeout occurs the process is marked tor termination, and a backup process is started.
-
Thanks for suggestion.
I thought also about some instance manager. Basically I have one already implemented and I was thinking about extending it.
It is holding basically a bunch of QTcpSockets. Each process is connecting with a QTcpSocket and it checks once in a while if the socket is still connected. When the socket is dropped, the instance will be stopped.
This could be combined with the QSharedMemory solution. Wasn't sure if I am thinking too complex.
-
@koahnig See https://github.com/itay-grudev/SingleApplication which is based on old QtSingleApplication solution. It implements limit for case N = 1, but you can reuse idea and make it more generic
-
You could try something along these lines:
#include <QThread> #include <QSystemSemaphore> #include <QSemaphore> class ProcessCounter { class ProcessCounterThread : public QThread { public: ProcessCounterThread(qint64 timeout, QSystemSemaphore &); void release(); void cancel(); bool success() const; protected: void run() override; private: bool acquired; qint64 maxWaitTime; QSemaphore semaphore; QSystemSemaphore & systemSemaphore; }; public: ProcessCounter(const QString & key, int count, qint64 timeout = 1000); ~ProcessCounter(); bool acquired() const; private: QSystemSemaphore semaphore; ProcessCounterThread thread; }; inline ProcessCounter::ProcessCounterThread::ProcessCounterThread(qint64 timeout, QSystemSemaphore & sem) : acquired(false), maxWaitTime(timeout), systemSemaphore(sem) { } inline bool ProcessCounter::ProcessCounterThread::success() const { return acquired; } inline void ProcessCounter::ProcessCounterThread::release() { semaphore.release(); } inline void ProcessCounter::ProcessCounterThread::cancel() { semaphore.release(); wait(); acquired = false; } void ProcessCounter::ProcessCounterThread::run() { acquired = semaphore.tryAcquire(1, maxWaitTime); if (!acquired) systemSemaphore.release(); } ProcessCounter::ProcessCounter(const QString & key, int count, qint64 timeout) : semaphore(key, count), thread(timeout, semaphore) { thread.start(); if (!semaphore.acquire()) { thread.cancel(); // ... Error handling return; } thread.wait(); // Wait a bit hoping for the global resource to become available } ProcessCounter::~ProcessCounter() { if (thread.success()) semaphore.release(); // We had our global semaphore acquired, now it's time to release it } inline bool ProcessCounter::acquired() const { return thread.success(); }
Usage on the stack (as expected):
static const int maxProcesses = 20; static const qint64 timeout = 1000; static const QString key = QStringLiteral("KeyForTheGlobalSemaphore"); int main(int argc, char ** argv) { QApplication app(argc, argv); ProcessCounter counter(key, maxProcesses, timeout); if (!counter.acquired()) return 0; // Not allowed, too many processes running already return QApplication::exec(); }
Note you'd need to handle
SIGSEGV
and possibly other signals manually and you shouldn't call::exit
at all, because it will not run theProcessCounter
's destructor, meaning the global semaphore won't be released. Graceful shutdown is paramount here.