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