""" A file lock implementation that tries to avoid platform specific issues. It is inspired by a whole bunch of different implementations listed below. - https://bitbucket.org/jaraco/yg.lockfile/src/6c448dcbf6e5/yg/lockfile/__init__.py - http://svn.zope.org/zc.lockfile/trunk/src/zc/lockfile/__init__.py?rev=121133&view=markup - http://stackoverflow.com/questions/489861/locking-a-file-in-python - http://www.evanfosmark.com/2009/01/cross-platform-file-locking-support-in-python/ - http://packages.python.org/lockfile/lockfile.html There are some tests below and a blog posting conceptually the problems I wanted to try and solve. The tests reflect these ideas. - http://ionrock.wordpress.com/2012/06/28/file-locking-in-python/ I'm not advocating using this package. But if you do happen to try it out and have suggestions please let me know. """ import os import time class FileLocked(Exception): pass class FileLock(object): def __init__(self, fname, timeout=None, force=False): self.fname = fname self.lockfname = '%s.lock' % self.fname self.timeout = timeout self.force = force self.fh = None self.flags = os.O_CREAT | os.O_RDWR try: # *nix flags self.flags = self.flags | os.O_EXLOCK except AttributeError: # windows flags (not sure this is correct) self.flags = self.flags | os.O_NOINHERIT def valid_lock(self): """ See if the lock exists and is left over from an old process. """ if not os.path.exists(self.lockfname): return False my_pid = os.getpid() lock_pid = int(open(self.lockfname).read()) # this is our process if my_pid == lock_pid: return True # it is/was another process # see if it is running try: os.kill(lock_pid, 0) except OSError: os.remove(self.lockfname) return False # it is running return True def is_locked(self, force=False): # We aren't locked if not self.valid_lock(): return False # We are locked, but we want to force it without waiting if not self.timeout: if self.force: self.release() return False else: # We're not waiting or forcing the lock raise FileLocked() # Locked, but want to wait for an unlock interval = .1 intervals = int(self.timeout / interval) while intervals: if self.valid_lock(): intervals -= 1 time.sleep(interval) print('stopping %s' % intervals) else: return True # check one last time if self.valid_lock(): if self.force: self.release() else: # still locked :( raise FileLocked() def acquire(self): self.fh = os.open(self.lockfname, self.flags) os.write(self.fh, str(os.getpid())) def release(self): if self.fh: os.close(self.fh) os.remove(self.lockfname) def __enter__(self): if not self.is_locked(): self.acquire() return self def __exit__(self, type, value, traceback): self.release() class TestProcessKilled(object): def test_process_killed_force_unlock(self): lockfile = 'test.txt.lock' with open(lockfile, 'w+') as f: f.write('9999999') assert os.path.exists(lockfile) with FileLock('test.txt'): assert True def test_force_unlock_in_same_process(self): lockfile = 'test.txt.lock' with open(lockfile, 'w+') as f: f.write(str(os.getpid())) with FileLock('test.txt', force=True): assert True def test_exception_after_timeout(self): lockfile = 'test.txt.lock' with open(lockfile, 'w+') as f: f.write(str(os.getpid())) try: with FileLock('test.txt', timeout=1): assert False except FileLocked: assert True def test_force_after_timeout(self): lockfile = 'test.txt.lock' with open(lockfile, 'w+') as f: f.write(str(os.getpid())) timeout = 1 start = time.time() with FileLock('test.txt', timeout=timeout, force=True): assert True end = time.time() assert end - start > timeout