1
# -*- coding: utf-8 -*-
4
lockfile.py - Platform-independent advisory file locks.
6
Requires Python 2.5 unless you apply 2.4.diff
7
Locking is done on a per-thread basis instead of a per-process basis.
11
>>> lock = LockFile('somefile')
14
... except AlreadyLocked:
15
... print 'somefile', 'is locked already.'
16
... except LockFailed:
17
... print 'somefile', 'can\\'t be locked.'
21
>>> print lock.is_locked()
25
>>> lock = LockFile('somefile')
26
>>> print lock.is_locked()
29
... print lock.is_locked()
31
>>> print lock.is_locked()
34
>>> lock = LockFile('somefile')
35
>>> # It is okay to lock twice from the same thread...
39
>>> # Though no counter is kept, so you can't unlock multiple times...
40
>>> print lock.is_locked()
45
Error - base class for other exceptions
46
LockError - base class for all locking exceptions
47
AlreadyLocked - Another thread or process already holds the lock
48
LockFailed - Lock failed for some other reason
49
UnlockError - base class for all unlocking exceptions
50
AlreadyUnlocked - File was not locked.
51
NotMyLock - File was locked but not by the current thread/process
54
from __future__ import absolute_import
62
# Work with PEP8 and non-PEP8 versions of threading module.
63
if not hasattr(threading, "current_thread"):
64
threading.current_thread = threading.currentThread
65
if not hasattr(threading.Thread, "get_name"):
66
threading.Thread.get_name = threading.Thread.getName
68
__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked',
69
'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
70
'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock',
74
class Error(Exception):
76
Base class for other exceptions.
86
class LockError(Error):
88
Base class for error arising from attempts to acquire the lock.
98
class LockTimeout(LockError):
99
"""Raised when lock creation fails within a user-defined period of time.
102
... raise LockTimeout
103
... except LockError:
109
class AlreadyLocked(LockError):
110
"""Some other thread/process is locking the file.
113
... raise AlreadyLocked
114
... except LockError:
120
class LockFailed(LockError):
121
"""Lock file creation failed for some other reason.
125
... except LockError:
131
class UnlockError(Error):
133
Base class for errors arising from attempts to release the lock.
136
... raise UnlockError
143
class NotLocked(UnlockError):
144
"""Raised when an attempt is made to unlock an unlocked file.
148
... except UnlockError:
154
class NotMyLock(UnlockError):
155
"""Raised when an attempt is made to unlock a file someone else locked.
159
... except UnlockError:
165
class _SharedBase(object):
166
def __init__(self, path):
169
def acquire(self, timeout=None):
173
* If timeout is omitted (or None), wait forever trying to lock the
176
* If timeout > 0, try to acquire the lock for that many seconds. If
177
the lock period expires and the file is still locked, raise
180
* If timeout <= 0, raise AlreadyLocked immediately if the file is
183
raise NotImplemented("implement in subclass")
189
If the file is not locked, raise NotLocked.
191
raise NotImplemented("implement in subclass")
195
Context manager support.
200
def __exit__(self, *_exc):
202
Context manager support.
207
return "<%s: %r>" % (self.__class__.__name__, self.path)
210
class LockBase(_SharedBase):
211
"""Base class for platform-specific lock classes."""
212
def __init__(self, path, threaded=True, timeout=None):
214
>>> lock = LockBase('somefile')
215
>>> lock = LockBase('somefile', threaded=False)
217
super(LockBase, self).__init__(path)
218
self.lock_file = os.path.abspath(path) + ".lock"
219
self.hostname = socket.gethostname()
220
self.pid = os.getpid()
222
t = threading.current_thread()
223
# Thread objects in Python 2.4 and earlier do not have ident
224
# attrs. Worm around that.
225
ident = getattr(t, "ident", hash(t))
226
self.tname = "-%x" % (ident & 0xffffffff)
229
dirname = os.path.dirname(self.lock_file)
231
# unique name is mostly about the current process, but must
232
# also contain the path -- otherwise, two adjacent locked
233
# files conflict (one file gets locked, creating lock-file and
234
# unique file, the other one gets locked, creating lock-file
235
# and overwriting the already existing lock-file, then one
236
# gets unlocked, deleting both lock-file and unique file,
237
# finally the last lock errors out upon releasing.
238
self.unique_name = os.path.join(dirname,
239
"%s%s.%s%s" % (self.hostname,
243
self.timeout = timeout
247
Tell whether or not the file is locked.
249
raise NotImplemented("implement in subclass")
251
def i_am_locking(self):
253
Return True if this object is locking the file.
255
raise NotImplemented("implement in subclass")
257
def break_lock(self):
259
Remove a lock. Useful if a locking thread failed to unlock.
261
raise NotImplemented("implement in subclass")
264
return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name,
268
def _fl_helper(cls, mod, *args, **kwds):
269
warnings.warn("Import from %s module instead of lockfile package" % mod,
270
DeprecationWarning, stacklevel=2)
271
# This is a bit funky, but it's only for awhile. The way the unit tests
272
# are constructed this function winds up as an unbound method, so it
273
# actually takes three args, not two. We want to toss out self.
274
if not isinstance(args[0], str):
275
# We are testing, avoid the first arg
277
if len(args) == 1 and not kwds:
278
kwds["threaded"] = True
279
return cls(*args, **kwds)
282
def LinkFileLock(*args, **kwds):
283
"""Factory function provided for backwards compatibility.
285
Do not use in new code. Instead, import LinkLockFile from the
286
lockfile.linklockfile module.
288
from . import linklockfile
289
return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile",
293
def MkdirFileLock(*args, **kwds):
294
"""Factory function provided for backwards compatibility.
296
Do not use in new code. Instead, import MkdirLockFile from the
297
lockfile.mkdirlockfile module.
299
from . import mkdirlockfile
300
return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile",
304
def SQLiteFileLock(*args, **kwds):
305
"""Factory function provided for backwards compatibility.
307
Do not use in new code. Instead, import SQLiteLockFile from the
308
lockfile.mkdirlockfile module.
310
from . import sqlitelockfile
311
return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile",
315
def locked(path, timeout=None):
316
"""Decorator which enables locks for decorated function.
319
- path: path for lockfile.
320
- timeout (optional): Timeout for acquiring lock.
323
@locked('/var/run/myname', timeout=0)
328
@functools.wraps(func)
329
def wrapper(*args, **kwargs):
330
lock = FileLock(path, timeout=timeout)
333
return func(*args, **kwargs)
340
if hasattr(os, "link"):
341
from . import linklockfile as _llf
342
LockFile = _llf.LinkLockFile
344
from . import mkdirlockfile as _mlf
345
LockFile = _mlf.MkdirLockFile