I recently faced a very common problem, how to make sure that only one instance of my program is running at a time on the host.
There are a lot of approaches that can be taken to solve this problem, but I needed a portable solution for Python.
My first idea was to use widely known IPC techniques to lock some global resource. In C I would just create a semaphore and lock it. One problem is that a semaphore is not unlocked when a process dies. Another issue is a lack of support of named semaphores for Python.
The best solution on Unix is to gain an exclusive write lock on a file using fcntl(LOCK_EX).
It’s also possible to use the fact that only one process at a time can bind to specific tcp/ip port (unless you use SO_REUSEPORT). This is the most portable, but also the most obscure method.
Here’s the code for this inter process “locking”. It’s not really locking, because you can’t block and wait for a lock. All you can do is grab a lock or get an exception. But this is enough to make sure that there is only one process that’s using a resource. This is how you can use this module:
import interlocks, time lock = interlocks.InterProcessLock("my resource name") try: lock.lock() except interlocks.SingleInstanceError: print "Other process has acquired this lock." else: print "Press CTRL+C to release the lock..." while True: time.sleep(32767)
Test code for the interlocks module needs to open an external process that blocks the resource. The code is not perfect (race conditions), but should be enough for just a test case:
def execute(cmd): ''' spawn a new python process that will execute 'cmd' ''' cmd = '''import time;''' + cmd + '''time.sleep(10);''' pid = os.spawnv(os.P_NOWAIT,'/usr/bin/python', ['/usr/bin/python', '-c', cmd]) time.sleep(1) # poor man's synchronization return pid lock = interlocks.InterProcessLock('test') # lock resource from other process pid = execute("import interlocks; a=interlocks.InterProcessLock('test');a.lock();") try: # fail to grab a lock lock.lock() except interlocks.SingleInstanceError: print "success: the lock is blocked by spawned process" else: print "FAILURE: the lock should be blocked by spawned process (pid=%i), but isn't" % (pid,) os.kill(pid, signal.SIGKILL) time.sleep(1) # poor man's synchronization
Coding the tests wasn’t so painful, much more problematic was to make tests run on Windows. Obviously we need an os.kill replacement for this platform. The next problem is to make os.spawnv() work on Windows at all: which slashes to use or how to encode spaces in the path. Another issue is that the process pid returned from os.spawnv() can’t be killed. It seems that the return value is not really a proper pid. Don’t waste your time like I did, use subprocess.Popen(). Fixed test code, without os.spawnv is included in the lib.