This file locking code is not really usable yet, at least not Windows. The lock file is not deleted there.

   1 # -*- coding: iso-8859-1 -*-
   2 """
   3     MoinMoin - File Locking Classes
   4 
   5     File locking is used for interprocess/multithreaded access
   6     to common files.
   7 
   8     @copyright: 2005 by Alexander Schremmer <alex AT alexanderweb DOT de>
   9         partly based on code by Barry A. Warsaw
  10     @license: GNU GPL, see COPYING for details.
  11 """
  12 
  13 import os
  14 import time
  15 import random
  16 import errno
  17 
  18 # Exceptions that can be raised by this module
  19 class LockError(Exception):
  20     """Base class for all exceptions in this module."""
  21 
  22 class AlreadyLockedError(LockError):
  23     """An attempt is made to lock an already locked object."""
  24 
  25 class NotLockedError(LockError):
  26     """An attempt is made to unlock an object that isn't locked."""
  27 
  28 class TimeOutError(LockError):
  29     """The timeout interval elapsed before the lock succeeded."""
  30 
  31 class FileLockBase:
  32     def __init__(self, path):
  33         self._path = path
  34         self._islocked = False
  35 
  36     def _sleep(self):
  37         """
  38         Used to sleep in a loop which tries to acquire a
  39         lock in a blocking way.
  40         """
  41         interval = random.random() * 2.0 + 0.01
  42         time.sleep(interval)
  43 
  44     def isLocked(self):
  45         return self._islocked
  46 
  47     def close(self):
  48         raise NotImplementedError
  49 
  50     def lock(self, timeout=None):
  51         """Acquire the lock
  52 
  53         With no timeout, a blocking lock is acquired.  A timeout is a hint in
  54         floating seconds for the length of time to attempt to acquire the
  55         lock.  If no lock can be acquired during that time, a TimeOutError is
  56         raised.
  57         """
  58         raise NotImplementedError
  59 
  60     def unlock(self):
  61         raise NotImplementedError
  62 
  63 class FileLockDummy(FileLockBase):
  64     def __init__(self, path):
  65         FileLockBase.__init__(self, path)
  66         self.isDummy = True # Allow duck typing :-)
  67 
  68     def close(self):
  69         pass
  70 
  71     def lock(self, timeout=None):
  72         self._islocked = True
  73         pass
  74 
  75     def unlock(self):
  76         self._islocked = False
  77         pass
  78 
  79 class FileLockPosix(FileLockBase):
  80     """
  81     File locking used on Posix platforms.
  82 
  83     Note that this implementation is not NFSv2-safe.
  84     """
  85 
  86     def __init__(self, path):
  87         FileLockBase.__init__(self, path)
  88         try:
  89             self._fp = open(path, 'r+')
  90         except IOError, e:
  91             if e.errno <> errno.ENOENT:
  92                 raise
  93             try:
  94                 self._fp = open(path, 'w+')
  95             except:
  96                 print "OH OH"    
  97                 raise
  98 
  99     def __del__(self):
 100         self.close()
 101 
 102     def close(self):
 103         if self._fp is not None:
 104             if self._islocked:
 105                 self.unlock()
 106             self._fp.close()
 107             try:
 108                 os.unlink(self._path)
 109             except OSError, e:
 110                 if e.errno <> errno.ENOENT:
 111                     raise
 112             self._fp = None
 113 
 114     def lock(self, timeout=None):
 115         if self._islocked:
 116             raise AlreadyLockedError
 117         fileno = self._fp.fileno()
 118         if timeout is None:
 119             # block until we have the lock
 120             fcntl.flock(fileno, fcntl.LOCK_EX)
 121             self._islocked = True
 122             return
 123         expires = time.time() + timeout
 124         while True:
 125             try:
 126                 fcntl.flock(fileno, fcntl.LOCK_EX | fcntl.LOCK_NB)
 127                 self._islocked = True
 128                 break
 129             except IOError, e:
 130                 if e.errno not in (errno.EAGAIN, errno.EACCES):
 131                     raise
 132             if time.time() > expires:
 133                 raise TimeOutError
 134             self._sleep()
 135 
 136     def unlock(self):
 137         if not self._islocked:
 138             raise NotLockedError
 139         fcntl.flock(self._fp.fileno(), fcntl.LOCK_UN)
 140         self._islocked = False
 141 
 142 
 143 class FileLockWindows(FileLockBase):
 144     """
 145     File locking used on Windows.
 146     """
 147     
 148     # the range of bytes to be locked
 149     _lockrange = 2147483647
 150 
 151     def __init__(self, path):
 152         FileLockBase.__init__(self, path)
 153         try:
 154             self._fp = open(path, 'r+')
 155         except IOError, e:
 156             if e.errno <> errno.ENOENT: raise
 157             self._fp = open(path, 'w+')
 158 
 159     def __del__(self):
 160         self.close()
 161 
 162     def close(self):
 163         if self._fp is not None:
 164             if self._islocked:
 165                 self.unlock()
 166             self._fp.close()
 167             self._fp = None
 168 
 169     def lock(self, timeout=None):
 170         if self._islocked:
 171             raise AlreadyLockedError
 172         fileno = self._fp.fileno()
 173         if timeout is None:
 174             # block until we have the lock
 175             try:
 176                 msvcrt.locking(fileno, msvcrt.LK_LOCK, FileLockWindows._lockrange)
 177             except IOError, e:
 178                 if e.errno == errno.EDEADLOCK:
 179                     raise TimeOutError
 180                 else:
 181                     raise
 182             else:
 183                 self._islocked = True
 184                 return
 185         expires = time.time() + timeout
 186         while True:
 187             try:
 188                 msvcrt.locking(fileno, msvcrt.LK_NBLCK, FileLockWindows._lockrange)
 189                 self._islocked = True
 190                 break
 191             except IOError, e:
 192                 if e.errno not in (errno.EAGAIN, errno.EACCES):
 193                     raise
 194             if time.time() > expires:
 195                 raise TimeOutError
 196             self._sleep()
 197 
 198     def unlock(self):
 199         if not self._islocked:
 200             raise NotLockedError
 201         msvcrt.locking(self._fp.fileno(), msvcrt.LK_UNLCK, FileLockWindows._lockrange)
 202         self._islocked = False
 203 
 204 
 205 # Set the right locking class
 206 
 207 if os.name == 'posix':
 208     import fcntl
 209     LockFile = FileLockPosix
 210 elif os.name == 'nt':
 211     import msvcrt
 212     LockFile = FileLockWindows
 213 else:
 214     LockFile = FileLockDummy
 215     import warnings
 216     warnings.warn("filelocking.py: Your platform is not supported by the"
 217                   " file locking code. Data corruptions might happen.\n")
 218 
 219 # Multi-process stress tests
 220 def _dochild(childno):
 221     prefix = '[%d]' % childno
 222     # Create somewhere between 1 and 1000 locks
 223     lockfile = LockFile('LockTest')
 224     workinterval = 3 * random.random()
 225     hitwait = 10 * random.random()
 226     print prefix, 'workinterval:', workinterval
 227     islocked = False
 228     t0 = 0
 229     t1 = 0
 230     t2 = 0
 231     try:
 232         try:
 233             t0 = time.time()
 234             print prefix, 'acquiring...'
 235             lockfile.lock()
 236             print prefix, 'acquired...'
 237             islocked = True
 238         except TimeOutError:
 239             print prefix, 'timed out - ERR'
 240         else:
 241             t1 = time.time()
 242             print prefix, 'acquisition time:', t1-t0, 'sec'
 243             time.sleep(workinterval)
 244     finally:
 245         if islocked:
 246             try:
 247                 lockfile.close()
 248                 t2 = time.time()
 249                 print prefix, 'released; lock hold time:', t2-t1, 'secs'
 250             except NotLockedError:
 251                 print prefix, 'lock was broken - ERR'
 252     # wait for next web hit
 253     print prefix, 'sleep:', hitwait
 254     time.sleep(hitwait)
 255 
 256 
 257 def _seed():
 258     d = sha.new(`os.getpid()`+`time.time()`).hexdigest()
 259     random.seed(d)
 260 
 261 def _test_thread(numtests):
 262     import threading
 263     kids = []
 264     for childno in range(numtests):
 265         pid = threading.Thread(target=_testthread2, args=(childno,))
 266         kids.append(pid)
 267         pid.start()
 268     while kids:
 269         kids[0].join()
 270         del kids[0]
 271 
 272 def _testthread2(childno):
 273             import thread
 274             # child
 275             _seed()
 276             try:
 277                 loopcount = random.randint(1, 10)
 278                 for i in range(loopcount):
 279                     print '[%d] Loop %d of %d' % (childno, i+1, loopcount)
 280                     _dochild(childno)
 281             except KeyboardInterrupt:
 282                 pass
 283             thread.exit()
 284 
 285 def _test_fork(numtests):
 286     kids = {}
 287     for childno in range(numtests):
 288         pid = os.fork()
 289         if pid:
 290             # parent
 291             kids[pid] = pid
 292         else:
 293             # child
 294             _seed()
 295             try:
 296                 loopcount = random.randint(1, 10)
 297                 for i in range(loopcount):
 298                     print '[%d] Loop %d of %d' % (childno, i+1, loopcount)
 299                     pid = os.fork()
 300                     if pid:
 301                         # parent, wait for child to exit
 302                         pid, status = os.waitpid(pid, 0)
 303                     else:
 304                         # child
 305                         _seed()
 306                         try:
 307                             _dochild(childno)
 308                         except KeyboardInterrupt:
 309                             pass
 310                         os._exit(0)
 311             except KeyboardInterrupt:
 312                 pass
 313             os._exit(0)
 314     while kids:
 315         pid, status = os.waitpid(-1, os.WNOHANG)
 316         if pid <> 0:
 317             del kids[pid]
 318 
 319 if __name__ == '__main__':
 320     import sha, sys, time, random
 321 
 322     sys.argv.insert(1,"10")
 323     {'nt': _test_thread,
 324      'posix': _test_fork,
 325     }[os.name](int(sys.argv[1]))

MoinMoin: AlexanderSchremmer/FileLocking (last edited 2007-10-29 19:08:49 by localhost)