|
|
 |
|
Title: Simple lockfile to detect previous instances of app
Submitter: Jordan Callicoat
(other recipes)
Last Updated: 2007/03/05
Version no: 1.2
Category:
Programs
|
|
1 vote(s)
|
|
|
|
Description:
This recipe implements a simple lockfile to ensure that only one instance of an app is alive at any given time.
1.2 Added documentation and cleaned up a bit.
Source: Text Source
import os
import socket
class flock(object):
'''Class to handle creating and removing (pid) lockfiles'''
class FileLockAcquisitionError(Exception): pass
class FileLockReleaseError(Exception): pass
addr = lambda self: '%d@%s' % (self.pid, self.host)
fddr = lambda self: '<%s %s>' % (self.path, self.addr())
pddr = lambda self, lock: '<%s %s@%s>' %\
(self.path, lock['pid'], lock['host'])
def __init__(self, path, debug=None):
self.pid = os.getpid()
self.host = socket.gethostname()
self.path = path
self.debug = debug
def acquire(self):
'''Acquire a lock, returning self if successful, False otherwise'''
if self.islocked():
if self.debug:
lock = self._readlock()
print 'Previous lock detected: %s' % self.pddr(lock)
return False
try:
fh = open(self.path, 'w')
fh.write(self.addr())
fh.close()
if self.debug:
print 'Acquired lock: %s' % self.fddr()
except:
if os.path.isfile(self.path):
try:
os.unlink(self.path)
except:
pass
raise (self.FileLockAcquisitionError,
'Error acquiring lock: %s' % self.fddr())
return self
def release(self):
'''Release lock, returning self'''
if self.ownlock():
try:
os.unlink(self.path)
if self.debug:
print 'Released lock: %s' % self.fddr()
except:
raise (self.FileLockReleaseError,
'Error releasing lock: %s' % self.fddr())
return self
def _readlock(self):
'''Internal method to read lock info'''
try:
lock = {}
fh = open(self.path)
data = fh.read().rstrip().split('@')
fh.close()
lock['pid'], lock['host'] = data
return lock
except:
return {'pid': 8**10, 'host': ''}
def islocked(self):
'''Check if we already have a lock'''
try:
lock = self._readlock()
os.kill(int(lock['pid']), 0)
return (lock['host'] == self.host)
except:
return False
def ownlock(self):
'''Check if we own the lock'''
lock = self._readlock()
return (self.fddr() == self.pddr(lock))
def __del__(self):
'''Magic method to clean up lock when program exits'''
self.release()
from time import sleep
from flock import flock
lock = flock('tmp.lock', True).acquire()
if lock:
sleep(30)
else:
print 'locked!'
from flock import flock
lock = flock('tmp.lock', True).acquire()
if lock:
print 'doing stuff'
else:
print 'locked!'
Discussion:
Kudos to Frederick Lundh for the idea.
Ps. The lock file should be automatically cleaned up, even if your program excepts, due to the "magic" __del__ method.
|
|
Add comment
|
|
Number of comments: 4
Rod Hyde, 2007/03/05
On UNIX systems mkdir is atomic which makes creating a lock very simple. I suspect that this also applies to Windows.
Add comment
Good point, Jordan Callicoat, 2007/03/06
For a simple lock, creating/checking for the presence of a dir (or file for that matter) would work. But if you want to do something more complex like remote execution over ssh, then you need to also keep track of hostnames.
Add comment
Race condition, Martin Blais, 2007/04/11
I'm sorry I fail to see how this achieves atomicity. You've got a race condition in acquire() between the "if self.islocked()" check and the subsequent call to open().
In order to implement filesystem locks, you need to use some sort of atomic call that both creates a filesystem object or fails. open(..., 'w') is not good enough, i.e. two concurrent processes could succesfully run through the check in acquire(), and both open the file in write mode. You're not going to get errors (i.e. no locking will have occurred), and the later file will remain. You can use mkdir() instead, but I don't know if that'll work under Windows.
In any case, I thought it would be important to point out the flaw in this code, for the benefit of those who would cut-n-paste without looking.
Add comment
True..., Jordan Callicoat, 2007/05/14
No doubt. This code isn't so robust as to catch the codition you mention, but for 99% of the use cases, it should "just work". Please do correct and improve.
Add comment
|
|
|
|
|
 |
|