Single instance of C++ program, using boost::interprocess

2.2k views Asked by At

I have a console application which I am trying to make able to run just once at a time. I have used boost interprocess library shared_memory_object to do that. See the code snippet below,

  boost::scoped_ptr<shared_memory_object> sharedMem;

  try
  {
     sharedMem.reset(
       new shared_memory_object(create_only, "shared_memory", read_write));
  } catch(...)
  {
     // executable is already running
      cerr << "Another instance of this program is running!" << endl;
      return 1;
  }

  // do something

  shared_memory_object::remove("shared_memory");  // remove the shared memory before exiting the application

The thing is that, the method prevents my application from running more than once at the same time; however, let's assume that the user stops the program running, then the memory will not be freed and next time when the user tries to run the program again, it will not run. Do you have any suggestions ?

P.S. C++ console application, OS: Ubuntu (but a solution which will work on the other platforms as well would be perfect ). Thank you

2

There are 2 answers

4
sehe On

NOTICE Answer tele-ported from How to limit the number of running instances in C++. It's in place here since it addressess a portable solution using Boost Interprocess and Boost Asio, in a detailed fashion.

Note that the solution is more generic, in that you can use it to limit instances to a specific maximum, rather than just 1

On linux (and perhaps other OSes?) you can use a lock file idiom (but it's not supported with some file-systems and old kernels).

I would suggest to use Interprocess synchronisation objects.

E.g., using a Boost Interprocess named semaphore:

#include <boost/interprocess/sync/named_semaphore.hpp>
#include <boost/thread.hpp>
#include <cassert>

int main()
{
    using namespace boost::interprocess;
    named_semaphore sem(open_or_create, "ffed38bd-f0fc-4f79-8838-5301c328268c", 0ul);

    if (sem.try_wait())
    {
        std::cout << "Oops, second instance\n";
    }
    else
    {
        sem.post();

        // feign hard work for 30s
        boost::this_thread::sleep_for(boost::chrono::seconds(30));

        if (sem.try_wait())
        {
            sem.remove("ffed38bd-f0fc-4f79-8838-5301c328268c");
        }
    }
}

If you start one copy in the back ground, new copies will "refuse" to start ("Oops, second instance") for about 30s.

I have a feeling it might be easier to reverse the logic here. Mmm. Lemme try.

some time passes

Hehe. That was more tricky than I thought.

The thing is, you want to make sure that the lock doesn't remain when your application is interrupted or killed. In the interest of sharing the techniques for portably handling the signals:

#include <boost/interprocess/sync/named_semaphore.hpp>
#include <boost/thread.hpp>
#include <cassert>
#include <boost/asio.hpp>

#define MAX_PROCESS_INSTANCES 3

boost::interprocess::named_semaphore sem(
        boost::interprocess::open_or_create, 
        "4de7ddfe-2bd5-428f-b74d-080970f980be",
        MAX_PROCESS_INSTANCES);

// to handle signals:
boost::asio::io_service service;
boost::asio::signal_set sig(service);

int main()
{

    if (sem.try_wait())
    {
        sig.add(SIGINT);
        sig.add(SIGTERM);
        sig.add(SIGABRT);
        sig.async_wait([](boost::system::error_code,int sig){ 
                std::cerr << "Exiting with signal " << sig << "...\n";
                sem.post();
            });
        boost::thread sig_listener([&] { service.run(); });

        boost::this_thread::sleep_for(boost::chrono::seconds(3));

        service.post([&] { sig.cancel(); });
        sig_listener.join();
    }
    else
    {
        std::cout << "More than " << MAX_PROCESS_INSTANCES << " instances not allowed\n";
    }
}

There's a lot that could be explained there. Let me know if you're interested.

NOTE It should be quite obvious that if kill -9 is used on your application (forced termination) then all bets are off and you'll have to either remove the Name Semaphore object or explicitly unlock it (post()).

Here's a testrun on my system:

sehe@desktop:/tmp$ (for a in {1..6}; do ./test& done; time wait)
More than 3 instances not allowed
More than 3 instances not allowed
More than 3 instances not allowed
Exiting with signal 0...
Exiting with signal 0...
Exiting with signal 0...

real    0m3.005s
user    0m0.013s
sys 0m0.012s
5
AudioBubble On

What you need to do is catch unexpected terminations of the program and free the shared memory object accordingly. You can catch SIGINT as follows using the signal.h POSIX header:

#include <signal.h>

void handleSIGINT(int param=0) {
    // Reset the shared memory object here
}

int main() {

   // Connect the signal handler to SIGINT
   signal(SIGINT, handleSIGINT);

   // Etc...

}

And you can catch program termination in the same manner using the atexit() function. Documentation here.