home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / checkbox / contrib / glock.py < prev    next >
Encoding:
Python Source  |  2009-04-27  |  11.5 KB  |  317 lines

  1. #!/usr/bin/env python
  2. # -*- coding: latin1 -*-
  3. #----------------------------------------------------------------------------
  4. # glock.py:                 Global mutex
  5. #
  6. # See __doc__ string below.
  7. #
  8. # Requires:
  9. #    - Python 1.5.2 or newer (www.python.org)
  10. #    - On windows: win32 extensions installed
  11. #           (http://www.python.org/windows/win32all/win32all.exe)
  12. #    - OS: Unix, Windows.
  13. #
  14. # $Id: //depot/rgutils/rgutils/glock.py#5 $
  15. #----------------------------------------------------------------------------
  16. '''
  17. This module defines the class GlobalLock that implements a global
  18. (inter-process) mutex on Windows and Unix, using file-locking on
  19. Unix.
  20.  
  21. @see: class L{GlobalLock} for more details.
  22. '''
  23. __version__ = '0.2.' + '$Revision: #5 $'[12:-2]
  24. __author__ = 'Richard Gruet', 'rjgruet@yahoo.com'
  25. __date__    = '$Date: 2005/06/19 $'[7:-2], '$Author: rgruet $'[9:-2]
  26. __since__ = '2000-01-22'
  27. __doc__ += '\n@author: %s (U{%s})\n@version: %s' % (__author__[0],
  28.                                             __author__[1], __version__)
  29. __all__ = ['GlobalLock', 'GlobalLockError', 'LockAlreadyAcquired', 'NotOwner']
  30.  
  31. # Imports:
  32. import sys, string, os, errno, re, posixpath
  33.  
  34. # System-dependent imports for locking implementation:
  35. _windows = (sys.platform == 'win32')
  36.  
  37. if _windows:
  38.     try:
  39.         import win32event, win32api, pywintypes
  40.     except ImportError:
  41.         sys.stderr.write('The win32 extensions need to be installed!')
  42.     try:
  43.         import ctypes
  44.     except ImportError:
  45.         ctypes = None
  46. else:   # assume Unix
  47.     try:
  48.         import fcntl
  49.     except ImportError:
  50.         sys.stderr.write("On what kind of OS am I ? (Mac?) I should be on "
  51.                          "Unix but can't import fcntl.\n")
  52.         raise
  53.     import threading
  54.  
  55. # Exceptions :
  56. # ----------
  57. class GlobalLockError(Exception):
  58.     ''' Error raised by the glock module.
  59.     '''
  60.     pass
  61.  
  62. class NotOwner(GlobalLockError):
  63.     ''' Attempt to release somebody else's lock.
  64.     '''
  65.     pass
  66.  
  67. class LockAlreadyAcquired(GlobalLockError):
  68.     ''' Non-blocking acquire but lock already seized.
  69.     '''
  70.     pass
  71.  
  72.  
  73. # Constants
  74. # ---------:
  75. if sys.version[:3] < '2.2':
  76.     True, False = 1, 0  # built-in in Python 2.2+
  77.  
  78. #----------------------------------------------------------------------------
  79. class GlobalLock:
  80. #----------------------------------------------------------------------------
  81.     ''' A global mutex.
  82.  
  83.         B{Specification}
  84.  
  85.          - The lock must act as a global mutex, ie block between different
  86.            candidate processus, but ALSO between different candidate
  87.            threads of the same process.
  88.  
  89.          - It must NOT block in case of reentrant lock request issued by
  90.            the SAME thread.
  91.          - Extraneous unlocks should be ideally harmless.
  92.  
  93.         B{Implementation}
  94.  
  95.         In Python there is no portable global lock AFAIK. There is only a
  96.         LOCAL/ in-process Lock mechanism (threading.RLock), so we have to
  97.         implement our own solution:
  98.  
  99.          - Unix: use fcntl.flock(). Recursive calls OK. Different process OK.
  100.            But <> threads, same process don't block so we have to use an extra
  101.            threading.RLock to fix that point.
  102.          - Windows: We use WIN32 mutex from Python Win32 extensions. Can't use
  103.            std module msvcrt.locking(), because global lock is OK, but
  104.            blocks also for 2 calls from the same thread!
  105.     '''
  106.     RE_ERROR_MSG = re.compile ("^\[Errno ([0-9]+)\]")
  107.  
  108.     def __init__(self, fpath, lockInitially=False, logger=None):
  109.         ''' Creates (or opens) a global lock.
  110.  
  111.             @param fpath: Path of the file used as lock target. This is also
  112.                           the global id of the lock. The file will be created
  113.                           if non existent.
  114.             @param lockInitially: if True locks initially.
  115.             @param logger: an optional logger object.
  116.         '''
  117.         self.logger = logger
  118.         self.fpath = fpath
  119.         if posixpath.exists(fpath):
  120.             self.previous_lockfile_present = True
  121.         else:
  122.             self.previous_lockfile_present = False
  123.         if _windows:
  124.             self.name = string.replace(fpath, '\\', '_')
  125.             self.mutex = win32event.CreateMutex(None, lockInitially, self.name)
  126.         else: # Unix
  127.             self.name = fpath
  128.             self.flock = open(fpath, 'w')
  129.             self.fdlock = self.flock.fileno()
  130.             self.threadLock = threading.RLock()
  131.         if lockInitially:
  132.             self.acquire()
  133.  
  134.     def __del__(self):
  135.         #print '__del__ called' ##
  136.         try: self.release()
  137.         except: pass
  138.         if _windows:
  139.             win32api.CloseHandle(self.mutex)
  140.         else:
  141.             try: self.flock.close()
  142.             except: pass
  143.  
  144.     def __repr__(self):
  145.         return '<Global lock @ %s>' % self.name
  146.  
  147.     def acquire(self, blocking=False):
  148.         """ Locks. Attemps to acquire a lock.
  149.  
  150.             @param blocking: If True, suspends caller until done. Otherwise,
  151.             LockAlreadyAcquired is raised if the lock cannot be acquired immediately.
  152.  
  153.             On windows an IOError is always raised after ~10 sec if the lock
  154.             can't be acquired.
  155.             @exception GlobalLockError: if lock can't be acquired (timeout)
  156.             @exception LockAlreadyAcquired: someone already has the lock and
  157.                        the caller decided not to block.
  158.         """
  159.         if self.logger:
  160.             self.logger.info('creating lockfile')
  161.         if _windows:
  162.             if blocking:
  163.                 timeout = win32event.INFINITE
  164.             else:
  165.                 timeout = 0
  166.             r = win32event.WaitForSingleObject(self.mutex, timeout)
  167.             if r == win32event.WAIT_FAILED:
  168.                 raise GlobalLockError("Can't acquire mutex: error")
  169.             if not blocking and r == win32event.WAIT_TIMEOUT:
  170.                 raise LockAlreadyAcquired('Lock %s already acquired by '
  171.                                           'someone else' % self.name)
  172.         else:
  173.             # First, acquire the global (inter-process) lock:
  174.             if blocking:
  175.                 options = fcntl.LOCK_EX
  176.             else:
  177.                 options = fcntl.LOCK_EX|fcntl.LOCK_NB
  178.             try:
  179.                 fcntl.flock(self.fdlock, options)
  180.             except IOError, message: #(errno 13: perm. denied,
  181.                             #       36: Resource deadlock avoided)
  182.                 if not blocking and self._errnoOf (message) == errno.EWOULDBLOCK:
  183.                     raise LockAlreadyAcquired('Lock %s already acquired by '
  184.                                               'someone else' % self.name)
  185.                 else:
  186.                     raise GlobalLockError('Cannot acquire lock on "file" '
  187.                                           '%s: %s\n' % (self.name, message))
  188.             #print 'got file lock.' ##
  189.  
  190.             # Then acquire the local (inter-thread) lock:
  191.             if not self.threadLock.acquire(blocking):
  192.                 fcntl.flock(self.fdlock, fcntl.LOCK_UN) # release global lock
  193.                 raise LockAlreadyAcquired('Lock %s already acquired by '
  194.                                           'someone else' % self.name)
  195.             if self.previous_lockfile_present and self.logger:
  196.                 self.logger.warn("Stale lockfile detected and claimed.")
  197.             #print 'got thread lock.' ##
  198.  
  199.         self.is_locked = True
  200.  
  201.     def release(self, skip_delete=False):
  202.         ''' Unlocks. (caller must own the lock!)
  203.  
  204.             @param skip_delete: don't try to delete the file. This can
  205.                 be used when the original filename has changed; for
  206.                 instance, if the lockfile is erased out-of-band, or if
  207.                 the directory it contains has been renamed.
  208.  
  209.             @return: The lock count.
  210.             @exception IOError: if file lock can't be released
  211.             @exception NotOwner: Attempt to release somebody else's lock.
  212.         '''
  213.         if not self.is_locked:
  214.             return
  215.         if not skip_delete:
  216.             if self.logger:
  217.                 self.logger.debug('Removing lock file: %s', self.fpath)
  218.             os.unlink(self.fpath)
  219.         elif self.logger:
  220.             # At certain times the lockfile will have been removed or
  221.             # moved away before we call release(); log a message because
  222.             # this is unusual and could be an error.
  223.             self.logger.debug('Oops, my lock file disappeared: %s', self.fpath)
  224.         if _windows:
  225.             if ctypes:
  226.                 result = ctypes.windll.kernel32.ReleaseMutex(self.mutex.handle)
  227.                 if not result:
  228.                    raise NotOwner("Attempt to release somebody else's lock")
  229.             else:
  230.                 try:
  231.                     win32event.ReleaseMutex(self.mutex)
  232.                     #print "released mutex"
  233.                 except pywintypes.error, e:
  234.                     errCode, fctName, errMsg =  e.args
  235.                     if errCode == 288:
  236.                         raise NotOwner("Attempt to release somebody else's lock")
  237.                     else:
  238.                         raise GlobalLockError('%s: err#%d: %s' % (fctName, errCode,
  239.                                                                   errMsg))
  240.         else:
  241.             # First release the local (inter-thread) lock:
  242.             try:
  243.                 self.threadLock.release()
  244.             except AssertionError:
  245.                 raise NotOwner("Attempt to release somebody else's lock")
  246.  
  247.             # Then release the global (inter-process) lock:
  248.             try:
  249.                 fcntl.flock(self.fdlock, fcntl.LOCK_UN)
  250.             except IOError: # (errno 13: permission denied)
  251.                 raise GlobalLockError('Unlock of file "%s" failed\n' %
  252.                                                             self.name)
  253.         self.is_locked = False
  254.  
  255.     def _errnoOf (self, message):
  256.         match = self.RE_ERROR_MSG.search(str(message))
  257.         if match:
  258.             return int(match.group(1))
  259.         else:
  260.             raise Exception ('Malformed error message "%s"' % message)
  261.  
  262. #----------------------------------------------------------------------------
  263. def test():
  264. #----------------------------------------------------------------------------
  265.     ##TODO: a more serious test with distinct processes !
  266.  
  267.     print 'Testing glock.py...' 
  268.  
  269.     # unfortunately can't test inter-process lock here!
  270.     lockName = 'myFirstLock'
  271.     l = GlobalLock(lockName)
  272.     if not _windows:
  273.         assert posixpath.exists(lockName)
  274.     l.acquire()
  275.     l.acquire() # reentrant lock, must not block
  276.     l.release()
  277.     l.release()
  278.  
  279.     try: l.release()
  280.     except NotOwner: pass
  281.     else: raise Exception('should have raised a NotOwner exception')
  282.  
  283.     # Check that <> threads of same process do block:
  284.     import threading, time
  285.     thread = threading.Thread(target=threadMain, args=(l,))
  286.     print 'main: locking...',
  287.     l.acquire()
  288.     print ' done.'
  289.     thread.start()
  290.     time.sleep(3)
  291.     print '\nmain: unlocking...',
  292.     l.release()
  293.     print ' done.'
  294.     time.sleep(0.1)
  295.  
  296.     print '=> Test of glock.py passed.'
  297.     return l
  298.  
  299. def threadMain(lock):
  300.     print 'thread started(%s).' % lock
  301.     try: lock.acquire(blocking=False)
  302.     except LockAlreadyAcquired: pass
  303.     else: raise Exception('should have raised LockAlreadyAcquired')
  304.     print 'thread: locking (should stay blocked for ~ 3 sec)...',
  305.     lock.acquire()
  306.     print 'thread: locking done.'
  307.     print 'thread: unlocking...',
  308.     lock.release()
  309.     print ' done.'
  310.     print 'thread ended.'
  311.  
  312. #----------------------------------------------------------------------------
  313. #       M A I N
  314. #----------------------------------------------------------------------------
  315. if __name__ == "__main__":
  316.     l = test()
  317.