Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

__init__.py 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. # -*- coding: utf-8 -*-
  2. """
  3. lockfile.py - Platform-independent advisory file locks.
  4. Requires Python 2.5 unless you apply 2.4.diff
  5. Locking is done on a per-thread basis instead of a per-process basis.
  6. Usage:
  7. >>> lock = LockFile('somefile')
  8. >>> try:
  9. ... lock.acquire()
  10. ... except AlreadyLocked:
  11. ... print 'somefile', 'is locked already.'
  12. ... except LockFailed:
  13. ... print 'somefile', 'can\\'t be locked.'
  14. ... else:
  15. ... print 'got lock'
  16. got lock
  17. >>> print lock.is_locked()
  18. True
  19. >>> lock.release()
  20. >>> lock = LockFile('somefile')
  21. >>> print lock.is_locked()
  22. False
  23. >>> with lock:
  24. ... print lock.is_locked()
  25. True
  26. >>> print lock.is_locked()
  27. False
  28. >>> lock = LockFile('somefile')
  29. >>> # It is okay to lock twice from the same thread...
  30. >>> with lock:
  31. ... lock.acquire()
  32. ...
  33. >>> # Though no counter is kept, so you can't unlock multiple times...
  34. >>> print lock.is_locked()
  35. False
  36. Exceptions:
  37. Error - base class for other exceptions
  38. LockError - base class for all locking exceptions
  39. AlreadyLocked - Another thread or process already holds the lock
  40. LockFailed - Lock failed for some other reason
  41. UnlockError - base class for all unlocking exceptions
  42. AlreadyUnlocked - File was not locked.
  43. NotMyLock - File was locked but not by the current thread/process
  44. """
  45. from __future__ import absolute_import
  46. import functools
  47. import os
  48. import socket
  49. import threading
  50. import warnings
  51. # Work with PEP8 and non-PEP8 versions of threading module.
  52. if not hasattr(threading, "current_thread"):
  53. threading.current_thread = threading.currentThread
  54. if not hasattr(threading.Thread, "get_name"):
  55. threading.Thread.get_name = threading.Thread.getName
  56. __all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked',
  57. 'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
  58. 'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock',
  59. 'LockBase', 'locked']
  60. class Error(Exception):
  61. """
  62. Base class for other exceptions.
  63. >>> try:
  64. ... raise Error
  65. ... except Exception:
  66. ... pass
  67. """
  68. pass
  69. class LockError(Error):
  70. """
  71. Base class for error arising from attempts to acquire the lock.
  72. >>> try:
  73. ... raise LockError
  74. ... except Error:
  75. ... pass
  76. """
  77. pass
  78. class LockTimeout(LockError):
  79. """Raised when lock creation fails within a user-defined period of time.
  80. >>> try:
  81. ... raise LockTimeout
  82. ... except LockError:
  83. ... pass
  84. """
  85. pass
  86. class AlreadyLocked(LockError):
  87. """Some other thread/process is locking the file.
  88. >>> try:
  89. ... raise AlreadyLocked
  90. ... except LockError:
  91. ... pass
  92. """
  93. pass
  94. class LockFailed(LockError):
  95. """Lock file creation failed for some other reason.
  96. >>> try:
  97. ... raise LockFailed
  98. ... except LockError:
  99. ... pass
  100. """
  101. pass
  102. class UnlockError(Error):
  103. """
  104. Base class for errors arising from attempts to release the lock.
  105. >>> try:
  106. ... raise UnlockError
  107. ... except Error:
  108. ... pass
  109. """
  110. pass
  111. class NotLocked(UnlockError):
  112. """Raised when an attempt is made to unlock an unlocked file.
  113. >>> try:
  114. ... raise NotLocked
  115. ... except UnlockError:
  116. ... pass
  117. """
  118. pass
  119. class NotMyLock(UnlockError):
  120. """Raised when an attempt is made to unlock a file someone else locked.
  121. >>> try:
  122. ... raise NotMyLock
  123. ... except UnlockError:
  124. ... pass
  125. """
  126. pass
  127. class _SharedBase(object):
  128. def __init__(self, path):
  129. self.path = path
  130. def acquire(self, timeout=None):
  131. """
  132. Acquire the lock.
  133. * If timeout is omitted (or None), wait forever trying to lock the
  134. file.
  135. * If timeout > 0, try to acquire the lock for that many seconds. If
  136. the lock period expires and the file is still locked, raise
  137. LockTimeout.
  138. * If timeout <= 0, raise AlreadyLocked immediately if the file is
  139. already locked.
  140. """
  141. raise NotImplemented("implement in subclass")
  142. def release(self):
  143. """
  144. Release the lock.
  145. If the file is not locked, raise NotLocked.
  146. """
  147. raise NotImplemented("implement in subclass")
  148. def __enter__(self):
  149. """
  150. Context manager support.
  151. """
  152. self.acquire()
  153. return self
  154. def __exit__(self, *_exc):
  155. """
  156. Context manager support.
  157. """
  158. self.release()
  159. def __repr__(self):
  160. return "<%s: %r>" % (self.__class__.__name__, self.path)
  161. class LockBase(_SharedBase):
  162. """Base class for platform-specific lock classes."""
  163. def __init__(self, path, threaded=True, timeout=None):
  164. """
  165. >>> lock = LockBase('somefile')
  166. >>> lock = LockBase('somefile', threaded=False)
  167. """
  168. super(LockBase, self).__init__(path)
  169. self.lock_file = os.path.abspath(path) + ".lock"
  170. self.hostname = socket.gethostname()
  171. self.pid = os.getpid()
  172. if threaded:
  173. t = threading.current_thread()
  174. # Thread objects in Python 2.4 and earlier do not have ident
  175. # attrs. Worm around that.
  176. ident = getattr(t, "ident", hash(t))
  177. self.tname = "-%x" % (ident & 0xffffffff)
  178. else:
  179. self.tname = ""
  180. dirname = os.path.dirname(self.lock_file)
  181. # unique name is mostly about the current process, but must
  182. # also contain the path -- otherwise, two adjacent locked
  183. # files conflict (one file gets locked, creating lock-file and
  184. # unique file, the other one gets locked, creating lock-file
  185. # and overwriting the already existing lock-file, then one
  186. # gets unlocked, deleting both lock-file and unique file,
  187. # finally the last lock errors out upon releasing.
  188. self.unique_name = os.path.join(dirname,
  189. "%s%s.%s%s" % (self.hostname,
  190. self.tname,
  191. self.pid,
  192. hash(self.path)))
  193. self.timeout = timeout
  194. def is_locked(self):
  195. """
  196. Tell whether or not the file is locked.
  197. """
  198. raise NotImplemented("implement in subclass")
  199. def i_am_locking(self):
  200. """
  201. Return True if this object is locking the file.
  202. """
  203. raise NotImplemented("implement in subclass")
  204. def break_lock(self):
  205. """
  206. Remove a lock. Useful if a locking thread failed to unlock.
  207. """
  208. raise NotImplemented("implement in subclass")
  209. def __repr__(self):
  210. return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name,
  211. self.path)
  212. def _fl_helper(cls, mod, *args, **kwds):
  213. warnings.warn("Import from %s module instead of lockfile package" % mod,
  214. DeprecationWarning, stacklevel=2)
  215. # This is a bit funky, but it's only for awhile. The way the unit tests
  216. # are constructed this function winds up as an unbound method, so it
  217. # actually takes three args, not two. We want to toss out self.
  218. if not isinstance(args[0], str):
  219. # We are testing, avoid the first arg
  220. args = args[1:]
  221. if len(args) == 1 and not kwds:
  222. kwds["threaded"] = True
  223. return cls(*args, **kwds)
  224. def LinkFileLock(*args, **kwds):
  225. """Factory function provided for backwards compatibility.
  226. Do not use in new code. Instead, import LinkLockFile from the
  227. lockfile.linklockfile module.
  228. """
  229. from . import linklockfile
  230. return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile",
  231. *args, **kwds)
  232. def MkdirFileLock(*args, **kwds):
  233. """Factory function provided for backwards compatibility.
  234. Do not use in new code. Instead, import MkdirLockFile from the
  235. lockfile.mkdirlockfile module.
  236. """
  237. from . import mkdirlockfile
  238. return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile",
  239. *args, **kwds)
  240. def SQLiteFileLock(*args, **kwds):
  241. """Factory function provided for backwards compatibility.
  242. Do not use in new code. Instead, import SQLiteLockFile from the
  243. lockfile.mkdirlockfile module.
  244. """
  245. from . import sqlitelockfile
  246. return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile",
  247. *args, **kwds)
  248. def locked(path, timeout=None):
  249. """Decorator which enables locks for decorated function.
  250. Arguments:
  251. - path: path for lockfile.
  252. - timeout (optional): Timeout for acquiring lock.
  253. Usage:
  254. @locked('/var/run/myname', timeout=0)
  255. def myname(...):
  256. ...
  257. """
  258. def decor(func):
  259. @functools.wraps(func)
  260. def wrapper(*args, **kwargs):
  261. lock = FileLock(path, timeout=timeout)
  262. lock.acquire()
  263. try:
  264. return func(*args, **kwargs)
  265. finally:
  266. lock.release()
  267. return wrapper
  268. return decor
  269. if hasattr(os, "link"):
  270. from . import linklockfile as _llf
  271. LockFile = _llf.LinkLockFile
  272. else:
  273. from . import mkdirlockfile as _mlf
  274. LockFile = _mlf.MkdirLockFile
  275. FileLock = LockFile