Monday, September 20, 2004

Python Locking sollution

Somewhere on a python cookbook, someone wrote something very nice. . . See the comments for the locking sollution that works cross platform, cross machine type, even over networks, can lock inter machine, inter process and interthread. Maybe even more.

This is a nice sollution to a common problem. Note though that it is easy enough to patch the code for your own requirements, as it's really simple. But effective - the Python way! :]

"""Usage example:
#test locking
import glock
file='lock.txt'
l=glock.lock(file)

#do something
time.sleep(60)
#extend the lock
l=glock.lock(file)

##do something again
time.sleep(60)

#now unlock
l.unlock()
###glock
"""
import os,time

class lock:
'''cross-platform locking.
Locking will raise exceptions.
Unlocking won't. So, unlock all you want'''
def __init__(self,nameInValidDirectory,wait=300):
"""Params::
- nameInValidDirectory: is a filename (doesn't need to exist though)
for wich the directory this file is in, is
writable for the current process, and exists on the
medium.
- wait: default period for waiting for a lock to become free: in seconds.
"""
name=nameInValidDirectory
self.wait = wait
self.d=name+'_lock/'
self.d2=name+'_lock2/'
self.locked={}
lock_successful=0
if self.locked.has_key(self.d2):
try:
#you got a lock, it is yours?
if os.stat(self.d2)[8]== self.locked.get(self.d2):
#try to extend lock before loss
#ATOMIC operation, extend may
#fail, presence of self.d will
#prevent loss of lock
#just by the act of extending
os.rmdir(self.d2)
os.mkdir(self.d2)
os.rmdir(self.d)
os.mkdir(self.d)
self.locked[self.d2]= os.stat(self.d2)[8]
lock_successful=1
if self.locked.has_key(self.d2):
del(self.locked[self.d2])
result='Fail: lost lock'
except:
if self.locked.has_key(self.d2):
del(self.locked[self.d2])
result='Fail: lost lock'
else:
for t in range(0,self.wait): #try 10 times
#not locked yet, try to lock it
try:
os.mkdir(self.d) #ATOMIC
os.mkdir(self.d2) #ATOMIC
self.locked[self.d2]= os.stat(self.d2)[8]
lock_successful=1
break
except Exception,error:
result=error
## print 'locking??',error
#mkdir probably failed
#lock already there,
#try to delete old lock
m_dir2=0;m_dir=0
if os.path.exists(self.d):
try:
m_dir2=os.stat(self.d2)[8]
except:
pass
try:
m_dir=os.stat(self.d)[8]
except:
pass
cur_tm=int(time.time())
#presence of either directory
#can stop you from taking lock
if cur_tm>m_dir+self.wait and cur_tm>m_dir2+self.wait:
#delete old locks
#ATOMIC here
os.rmdir(self.d2)
os.mkdir(self.d2)
os.rmdir(self.d)
os.mkdir(self.d)
self.locked[self.d2]= os.stat(self.d2)[8]
lock_successful=1
break
time.sleep(1)
#made it thru the loop, so we got no lock
if not lock_successful:
raise result

def unlock(self):
'''does not raise an exception,
safe to unlock as often as you want
it may just do nothing'''
if self.locked.has_key(self.d2):
#we're the ones that unlocked it,
#if time matched
if self.locked[self.d2]== os.stat(self.d2)[8]:
try:
del(self.locked[self.d2])
os.rmdir(self.d2)
os.rmdir(self.d)
return 1
except:
return 0
else:
del(self.locked[self.d2])
return 0
else:
return 0

if __name__ == "__main__":
print 'testing lock'
file='fred.txt'
l=lock(file, wait=20)
## f=open(file,'w')
## f.write('test')
## f.close()
raw_input("Locked '%s', presse enter to unlock" % (file))
l.unlock()