This file locking code is not really usable yet, at least not Windows. The lock file is not deleted there. * Resources * http://cvs.sourceforge.net/viewcvs.py/mailman/mailman/Mailman/LockFile.py?rev=2.9&view=log * http://cvs.sourceforge.net/viewcvs.py/mailman/mailman3/src/mailman/lock.py?rev=3.0&view=log * http://cvs.sourceforge.net/viewcvs.py/mailman/mailman3/src/mailman/lockfile.py?rev=3.0&view=log * [[http://groups.google.de/groups?hl=de&lr=&client=firefox-a&threadm=qpoiotkbgol8dg0cljle74oqb5uq01ifj2%404ax.com&rnum=6&prev=/groups%3Fq%3Dmsvcrt%2Bfcntl%2Block%26hl%3Dde%26lr%3D%26client%3Dfirefox-a%26rls%3Dorg.mozilla:en-US:official%26sa%3DN%26tab%3Dwg|Usenet posting thread about locking]] {{{#!python # -*- coding: iso-8859-1 -*- """ MoinMoin - File Locking Classes File locking is used for interprocess/multithreaded access to common files. @copyright: 2005 by Alexander Schremmer partly based on code by Barry A. Warsaw @license: GNU GPL, see COPYING for details. """ import os import time import random import errno # Exceptions that can be raised by this module class LockError(Exception): """Base class for all exceptions in this module.""" class AlreadyLockedError(LockError): """An attempt is made to lock an already locked object.""" class NotLockedError(LockError): """An attempt is made to unlock an object that isn't locked.""" class TimeOutError(LockError): """The timeout interval elapsed before the lock succeeded.""" class FileLockBase: def __init__(self, path): self._path = path self._islocked = False def _sleep(self): """ Used to sleep in a loop which tries to acquire a lock in a blocking way. """ interval = random.random() * 2.0 + 0.01 time.sleep(interval) def isLocked(self): return self._islocked def close(self): raise NotImplementedError def lock(self, timeout=None): """Acquire the lock With no timeout, a blocking lock is acquired. A timeout is a hint in floating seconds for the length of time to attempt to acquire the lock. If no lock can be acquired during that time, a TimeOutError is raised. """ raise NotImplementedError def unlock(self): raise NotImplementedError class FileLockDummy(FileLockBase): def __init__(self, path): FileLockBase.__init__(self, path) self.isDummy = True # Allow duck typing :-) def close(self): pass def lock(self, timeout=None): self._islocked = True pass def unlock(self): self._islocked = False pass class FileLockPosix(FileLockBase): """ File locking used on Posix platforms. Note that this implementation is not NFSv2-safe. """ def __init__(self, path): FileLockBase.__init__(self, path) try: self._fp = open(path, 'r+') except IOError, e: if e.errno <> errno.ENOENT: raise try: self._fp = open(path, 'w+') except: print "OH OH" raise def __del__(self): self.close() def close(self): if self._fp is not None: if self._islocked: self.unlock() self._fp.close() try: os.unlink(self._path) except OSError, e: if e.errno <> errno.ENOENT: raise self._fp = None def lock(self, timeout=None): if self._islocked: raise AlreadyLockedError fileno = self._fp.fileno() if timeout is None: # block until we have the lock fcntl.flock(fileno, fcntl.LOCK_EX) self._islocked = True return expires = time.time() + timeout while True: try: fcntl.flock(fileno, fcntl.LOCK_EX | fcntl.LOCK_NB) self._islocked = True break except IOError, e: if e.errno not in (errno.EAGAIN, errno.EACCES): raise if time.time() > expires: raise TimeOutError self._sleep() def unlock(self): if not self._islocked: raise NotLockedError fcntl.flock(self._fp.fileno(), fcntl.LOCK_UN) self._islocked = False class FileLockWindows(FileLockBase): """ File locking used on Windows. """ # the range of bytes to be locked _lockrange = 2147483647 def __init__(self, path): FileLockBase.__init__(self, path) try: self._fp = open(path, 'r+') except IOError, e: if e.errno <> errno.ENOENT: raise self._fp = open(path, 'w+') def __del__(self): self.close() def close(self): if self._fp is not None: if self._islocked: self.unlock() self._fp.close() self._fp = None def lock(self, timeout=None): if self._islocked: raise AlreadyLockedError fileno = self._fp.fileno() if timeout is None: # block until we have the lock try: msvcrt.locking(fileno, msvcrt.LK_LOCK, FileLockWindows._lockrange) except IOError, e: if e.errno == errno.EDEADLOCK: raise TimeOutError else: raise else: self._islocked = True return expires = time.time() + timeout while True: try: msvcrt.locking(fileno, msvcrt.LK_NBLCK, FileLockWindows._lockrange) self._islocked = True break except IOError, e: if e.errno not in (errno.EAGAIN, errno.EACCES): raise if time.time() > expires: raise TimeOutError self._sleep() def unlock(self): if not self._islocked: raise NotLockedError msvcrt.locking(self._fp.fileno(), msvcrt.LK_UNLCK, FileLockWindows._lockrange) self._islocked = False # Set the right locking class if os.name == 'posix': import fcntl LockFile = FileLockPosix elif os.name == 'nt': import msvcrt LockFile = FileLockWindows else: LockFile = FileLockDummy import warnings warnings.warn("filelocking.py: Your platform is not supported by the" " file locking code. Data corruptions might happen.\n") # Multi-process stress tests def _dochild(childno): prefix = '[%d]' % childno # Create somewhere between 1 and 1000 locks lockfile = LockFile('LockTest') workinterval = 3 * random.random() hitwait = 10 * random.random() print prefix, 'workinterval:', workinterval islocked = False t0 = 0 t1 = 0 t2 = 0 try: try: t0 = time.time() print prefix, 'acquiring...' lockfile.lock() print prefix, 'acquired...' islocked = True except TimeOutError: print prefix, 'timed out - ERR' else: t1 = time.time() print prefix, 'acquisition time:', t1-t0, 'sec' time.sleep(workinterval) finally: if islocked: try: lockfile.close() t2 = time.time() print prefix, 'released; lock hold time:', t2-t1, 'secs' except NotLockedError: print prefix, 'lock was broken - ERR' # wait for next web hit print prefix, 'sleep:', hitwait time.sleep(hitwait) def _seed(): d = sha.new(`os.getpid()`+`time.time()`).hexdigest() random.seed(d) def _test_thread(numtests): import threading kids = [] for childno in range(numtests): pid = threading.Thread(target=_testthread2, args=(childno,)) kids.append(pid) pid.start() while kids: kids[0].join() del kids[0] def _testthread2(childno): import thread # child _seed() try: loopcount = random.randint(1, 10) for i in range(loopcount): print '[%d] Loop %d of %d' % (childno, i+1, loopcount) _dochild(childno) except KeyboardInterrupt: pass thread.exit() def _test_fork(numtests): kids = {} for childno in range(numtests): pid = os.fork() if pid: # parent kids[pid] = pid else: # child _seed() try: loopcount = random.randint(1, 10) for i in range(loopcount): print '[%d] Loop %d of %d' % (childno, i+1, loopcount) pid = os.fork() if pid: # parent, wait for child to exit pid, status = os.waitpid(pid, 0) else: # child _seed() try: _dochild(childno) except KeyboardInterrupt: pass os._exit(0) except KeyboardInterrupt: pass os._exit(0) while kids: pid, status = os.waitpid(-1, os.WNOHANG) if pid <> 0: del kids[pid] if __name__ == '__main__': import sha, sys, time, random sys.argv.insert(1,"10") {'nt': _test_thread, 'posix': _test_fork, }[os.name](int(sys.argv[1])) }}}