root / trunk / twisted / python / lockfile.py

Revision 26634, 6.3 kB (checked in by exarkun, 3 months ago)

Merge skip-without-pywin32-3707

Author: amaury, christianmlong
Reviewer: exarkun
Fixes: #3707

Skip all the tests which require process support on Windows if pywin32
is not installed (and thus IReactorProcess won't work).

Line 
1 # -*- test-case-name: twisted.test.test_lockfile -*-
2 # Copyright (c) 2005 Divmod, Inc.
3 # Copyright (c) 2008 Twisted Matrix Laboratories.
4 # See LICENSE for details.
5
6 """
7 Filesystem-based interprocess mutex.
8 """
9
10 __metaclass__ = type
11
12 import errno, os
13
14 from time import time as _uniquefloat
15
16 def unique():
17     return str(long(_uniquefloat() * 1000))
18
19 from os import rename
20 try:
21     from os import kill
22     from os import symlink
23     from os import readlink
24     from os import remove as rmlink
25     _windows = False
26 except:
27     _windows = True
28
29     try:
30         from win32api import OpenProcess
31         import pywintypes
32     except ImportError:
33         kill = None
34     else:
35         ERROR_ACCESS_DENIED = 5
36         ERROR_INVALID_PARAMETER = 87
37
38         def kill(pid, signal):
39             try:
40                 OpenProcess(0, 0, pid)
41             except pywintypes.error, e:
42                 if e.args[0] == ERROR_ACCESS_DENIED:
43                     return
44                 elif e.args[0] == ERROR_INVALID_PARAMETER:
45                     raise OSError(errno.ESRCH, None)
46                 raise
47             else:
48                 raise RuntimeError("OpenProcess is required to fail.")
49
50     _open = file
51
52     # XXX Implement an atomic thingamajig for win32
53     def symlink(value, filename):
54         newlinkname = filename+"."+unique()+'.newlink'
55         newvalname = os.path.join(newlinkname,"symlink")
56         os.mkdir(newlinkname)
57         f = _open(newvalname,'wcb')
58         f.write(value)
59         f.flush()
60         f.close()
61         try:
62             rename(newlinkname, filename)
63         except:
64             os.remove(newvalname)
65             os.rmdir(newlinkname)
66             raise
67
68     def readlink(filename):
69         try:
70             fObj = _open(os.path.join(filename,'symlink'), 'rb')
71         except IOError, e:
72             if e.errno == errno.ENOENT or e.errno == errno.EIO:
73                 raise OSError(e.errno, None)
74             raise
75         else:
76             result = fObj.read()
77             fObj.close()
78             return result
79
80     def rmlink(filename):
81         os.remove(os.path.join(filename, 'symlink'))
82         os.rmdir(filename)
83
84
85
86 class FilesystemLock:
87     """
88     A mutex.
89
90     This relies on the filesystem property that creating
91     a symlink is an atomic operation and that it will
92     fail if the symlink already exists.  Deleting the
93     symlink will release the lock.
94
95     @ivar name: The name of the file associated with this lock.
96
97     @ivar clean: Indicates whether this lock was released cleanly by its
98         last owner.  Only meaningful after C{lock} has been called and
99         returns True.
100
101     @ivar locked: Indicates whether the lock is currently held by this
102         object.
103     """
104
105     clean = None
106     locked = False
107
108     def __init__(self, name):
109         self.name = name
110
111
112     def lock(self):
113         """
114         Acquire this lock.
115
116         @rtype: C{bool}
117         @return: True if the lock is acquired, false otherwise.
118
119         @raise: Any exception os.symlink() may raise, other than
120         EEXIST.
121         """
122         clean = True
123         while True:
124             try:
125                 symlink(str(os.getpid()), self.name)
126             except OSError, e:
127                 if _windows and e.errno in (errno.EACCES, errno.EIO):
128                     # The lock is in the middle of being deleted because we're
129                     # on Windows where lock removal isn't atomic.  Give up, we
130                     # don't know how long this is going to take.
131                     return False
132                 if e.errno == errno.EEXIST:
133                     try:
134                         pid = readlink(self.name)
135                     except OSError, e:
136                         if e.errno == errno.ENOENT:
137                             # The lock has vanished, try to claim it in the
138                             # next iteration through the loop.
139                             continue
140                         raise
141                     except IOError, e:
142                         if _windows and e.errno == errno.EACCES:
143                             # The lock is in the middle of being
144                             # deleted because we're on Windows where
145                             # lock removal isn't atomic.  Give up, we
146                             # don't know how long this is going to
147                             # take.
148                             return False
149                         raise
150                     try:
151                         if kill is not None:
152                             kill(int(pid), 0)
153                     except OSError, e:
154                         if e.errno == errno.ESRCH:
155                             # The owner has vanished, try to claim it in the next
156                             # iteration through the loop.
157                             try:
158                                 rmlink(self.name)
159                             except OSError, e:
160                                 if e.errno == errno.ENOENT:
161                                     # Another process cleaned up the lock.
162                                     # Race them to acquire it in the next
163                                     # iteration through the loop.
164                                     continue
165                                 raise
166                             clean = False
167                             continue
168                         raise
169                     return False
170                 raise
171             self.locked = True
172             self.clean = clean
173             return True
174
175
176     def unlock(self):
177         """
178         Release this lock.
179
180         This deletes the directory with the given name.
181
182         @raise: Any exception os.readlink() may raise, or
183         ValueError if the lock is not owned by this process.
184         """
185         pid = readlink(self.name)
186         if int(pid) != os.getpid():
187             raise ValueError("Lock %r not owned by this process" % (self.name,))
188         rmlink(self.name)
189         self.locked = False
190
191
192 def isLocked(name):
193     """Determine if the lock of the given name is held or not.
194
195     @type name: C{str}
196     @param name: The filesystem path to the lock to test
197
198     @rtype: C{bool}
199     @return: True if the lock is held, False otherwise.
200     """
201     l = FilesystemLock(name)
202     result = None
203     try:
204         result = l.lock()
205     finally:
206         if result:
207             l.unlock()
208     return not result
209
210
211 __all__ = ['FilesystemLock', 'isLocked']
212
Note: See TracBrowser for help on using the browser.