HWRF  trunk@4391
locking.py
1 """!Handles file locking using Python "with" blocks.
2 
3 This module implements a Python with construct that can hold a lock
4 and release it at the end of the "with" block. It also implements a
5 safety feature to allow the program to disable locking, ensuring a
6 fatal exception (LockingDisabled) if anything tries to lock a file.
7 That functionality is connected to the produtil.sigsafety module,
8 which will disable locking if a fatal signal is received.
9 
10 @code
11 import produtil.locking
12 with produtil.locking.LockFile("some.lockfile"):
13  ... do things while the file is locked...
14 ... the file is now unlocked ...
15 @endcode"""
16 
17 import fcntl, time, errno, os.path
18 import produtil.retry as retry
19 import produtil.fileop
20 
21 ##@var __all__
22 # Symbols exported by "from produtil.locking import *"
23 __all__=['LockingDisabled','disable_locking','LockFile','LockHeld']
24 
25 ##@var locks
26 # Part of the internal implementation of this module: the list of
27 # existing locks (LockFile objects) that may be held.
28 locks=set()
29 
30 ##@var locks_okay
31 # Part of the internal implementation of this module: if True,
32 # locking is allowed, if False, locking is forbidden. When this is
33 # False, LockingDisabled is raised on any attempt to acquire a lock.
34 locks_okay=True
35 
37  """!Entirely disables all locking in this module.
38 
39  If this is called, any locking attempts will raise
40  LockingDisabled. That exception derives directly from
41  BaseException, which well-written Python code will never catch,
42  hence ensuring a rapid, abnormal exit of the program. This
43  routine should never be called directly: it is only used as part
44  of the implementation of the produtil.sigsafety, to prevent file
45  locking after catching a terminal signal, hence allowing the
46  program to exit as quickly as possible, and present a stack trace
47  to any locations that attempt locking."""
48  global locks_okay
49  locks_okay=False
50  for lock in locks:
51  try:
52  lock.release_impl()
53  except (Exception,LockingDisabled) as l: pass
54 
55 class LockHeld(Exception):
56  """!This exception is raised when a LockFile cannot lock a file
57  because another process or thread has locked it already."""
58 
59 class LockingDisabled(BaseException):
60  """!This exception is raised when a thread attempts to acquire a
61  lock while Python is exiting according to produtil.sigsafety.
62 
63  @warning This is a subclass of BaseException, not Exception, to
64  attempt to cleanly kill the thread."""
65 
66 class LockFile(object):
67  """!Automates locking of a lockfile
68 
69  @code
70  with LockFile("/path/to/lock.file"):
71  ... do things while file is locked ...
72  ...file is no longer locked.
73  @endcode"""
74  def __hash__(self):
75  """!Return a hash of this object."""
76  return hash(id(self))
77  def __eq__(self,other):
78  """!Is this lock the same as that lock?"""
79  return self is other
80  def __init__(self,filename,until=None,logger=None,max_tries=10,sleep_time=3,first_warn=0,giveup_quiet=False):
81  """!Creates an object that will lock the specified file.
82  @param filename the file to lock
83  @param until Unused.
84  @param logger Optional: a logging.Logger to log messages
85  @param max_tries Optional: maximum tries before giving up on locking
86  @param sleep_time Optional: approximate sleep time between
87  locking attempts.
88  @param first_warn Optional: first locking failure at which to
89  write warnings to the logger
90  @param giveup_quiet Optional: if True, do not log the final
91  failure to lock"""
92  if not locks_okay:
93  raise LockingDisabled('Attempted to create a LockFile object while the process was exiting.')
94  self._logger=logger
95  assert(filename is not None)
96  assert(len(filename)>0)
97  self._filename=filename
98  self._max_tries=max_tries
99  self._sleep_time=sleep_time
100  self._first_warn=first_warn
101  self._giveup_quiet=giveup_quiet
102  self._fd=None
103  def acquire_impl(self):
104  """!Internal implementation function; do not call directly.
105  Does the actual work of acquiring the lock, without retries,
106  logging or sleeping. Will raise LockHeld if it cannot acquire
107  the lock."""
108  if not locks_okay:
109  raise LockingDisabled('Attempted to acquire a lock while '
110  'the process was exiting.')
111  thedir=os.path.dirname(self._filename)
112  if thedir:
114  if self._fd is None:
115  self._fd=open(self._filename,'wb')
116  try:
117  fcntl.lockf(self._fd.fileno(),fcntl.LOCK_EX|fcntl.LOCK_NB)
118  except EnvironmentError as e:
119  if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
120  raise LockHeld('%s: already locked by another process or '
121  'thread: %s'% ( self._filename, str(e)))
122  raise
123  def release_impl(self):
124  """!Internal implementation function; do not call directly.
125  Does the actual work of releasing the lock, without retries,
126  logging or sleeping."""
127  if self._fd is not None:
128  fcntl.lockf(self._fd.fileno(),fcntl.LOCK_UN)
129  self._fd.close()
130  self._fd=None
131  def acquire(self):
132  """!Acquire the lock. Will try for a while, and will raise
133  LockHeld when giving up."""
134  locks.add(self)
135  return retry.retry_io(self._max_tries,self._sleep_time,self.acquire_impl,
136  fail=self._filename+': cannot lock',logger=self._logger,
137  first_warn=self._first_warn,giveup_quiet=self._giveup_quiet)
138  def release(self):
139  """!Release the lock. May raise exceptions on unexpected
140  failures."""
141  try:
142  return retry.retry_io(self._max_tries,self._sleep_time,self.release_impl,
143  fail=self._filename+': cannot unlock',logger=self._logger,
144  first_warn=self._first_warn,giveup_quiet=self._giveup_quiet)
145  except:
146  raise
147  else:
148  locks.remove(self)
149  def __enter__(self):
150  """!Calls self.acquire() to acquire the lock."""
151  self.acquire()
152  return self
153  def __exit__(self,etype,evalue,etraceback):
154  """!Calls self.release() to release the lock.
155  @param etype,evalue,etraceback Exception information."""
156  self.release()
This module provides a set of utility functions to do filesystem operations.
Definition: fileop.py:1
This exception is raised when a LockFile cannot lock a file because another process or thread has loc...
Definition: locking.py:55
def disable_locking()
Entirely disables all locking in this module.
Definition: locking.py:36
def __enter__(self)
Calls self.acquire() to acquire the lock.
Definition: locking.py:149
def release(self)
Release the lock.
Definition: locking.py:138
def __exit__(self, etype, evalue, etraceback)
Calls self.release() to release the lock.
Definition: locking.py:153
def __hash__(self)
Return a hash of this object.
Definition: locking.py:74
def acquire_impl(self)
Internal implementation function; do not call directly.
Definition: locking.py:103
Contains retry_io() which automates retrying operations.
Definition: retry.py:1
def __init__
Creates an object that will lock the specified file.
Definition: locking.py:80
def release_impl(self)
Internal implementation function; do not call directly.
Definition: locking.py:123
def makedirs
Make a directory tree, working around filesystem bugs.
Definition: fileop.py:224
def __eq__(self, other)
Is this lock the same as that lock?
Definition: locking.py:77
This exception is raised when a thread attempts to acquire a lock while Python is exiting according t...
Definition: locking.py:59
Automates locking of a lockfile.
Definition: locking.py:66
def acquire(self)
Acquire the lock.
Definition: locking.py:131