Ensures application runs only once.
Python, 82 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
#! /usr/bin/env python import os, errno, fcntl class ApplicationLock: ''' Ensures application is running only once, by using a lock file. Ensure call to lock works. Then call unlock at program exit. You cannot read or write to the lock file, but for some reason you can remove it. Once removed, it is still in a locked state somehow. Another application attempting to lock against the file will fail, even though the directory listing does not show the file. Mysterious, but we are glad the lock integrity is upheld in such a case. Instance variables: lockfile -- Full path to lock file lockfd -- File descriptor of lock file exclusively locked ''' def __init__ (self, lockfile): self.lockfile = lockfile self.lockfd = None def lock (self): ''' Creates and holds on to the lock file with exclusive access. Returns True if lock successful, False if it is not, and raises an exception upon operating system errors encountered creating the lock file. ''' try: # # Create or else open and trucate lock file, in read-write mode. # # A crashed app might not delete the lock file, so the # os.O_CREAT | os.O_EXCL combination that guarantees # atomic create isn't useful here. That is, we don't want to # fail locking just because the file exists. # # Could use os.O_EXLOCK, but that doesn't exist yet in my Python # self.lockfd = os.open (self.lockfile, os.O_TRUNC | os.O_CREAT | os.O_RDWR) # Acquire exclusive lock on the file, but don't block waiting for it fcntl.flock (self.lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB) # Writing to file is pointless, nobody can see it os.write (self.lockfd, "My Lockfile") return True except (OSError, IOError), e: # Lock cannot be acquired is okay, everything else reraise exception if e.errno in (errno.EACCES, errno.EAGAIN): return False else: raise def unlock (self): try: # FIRST unlink file, then close it. This way, we avoid file # existence in an unlocked state os.unlink (self.lockfile) # Just in case, let's not leak file descriptors os.close (self.lockfd) except (OSError, IOError), e: # Ignore error destroying lock file. See class doc about how # lockfile can be erased and everything still works normally. pass # Test main routine if __name__ == '__main__': import time applock = ApplicationLock ('Test.lock') if (applock.lock ()): # Hint: try running 2nd program instance while this instance sleeps print ("Obtained lock, sleeping 10 seconds") time.sleep (10) print ("Unlocking") applock.unlock () else: print ("Unable to obtain lock, exiting")
This is different than recipe 498171 because there is no race condition.
This is different than recipe 576572 which is based on the file not already existing. This solution still works if the application stopped prematurely and the file is still sitting around.
fcntl locking will work only if the filesystem is mounted with the mandatory locking option, which is usually not set.
Cong Ma, I tested this on freebsd 7.2, osx 10.5, gentoo running 2.6, and ubuntu jaunty, all using their respective default filesystem/mount options, and the recipe works fine.
The only thing I would change would be for the program owning the lock to remove the file in a finally statement. As it stands, any except (even keyboard interrupt) will leave the lockfile hanging around.
Cong Ma is probably right. I dumbly discovered on Linux you can simply DELETE the lockfile and start the application a second time. Delete its lockfile and start the application a third time.
Therefore, the directory should be owned and writable only by the user running the app (permits them to create a file in that dir), and that user should never delete the file at any time forever. This self-discipline to not delete the lockfile manually is key to guaranteeing that the application won't run twice.