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