1
2
3 """Locked file interface that should work on Unix and Windows pythons.
4
5 This module first tries to use fcntl locking to ensure serialized access
6 to a file, then falls back on a lock file if that is unavialable.
7
8 Usage:
9 f = LockedFile('filename', 'r+b', 'rb')
10 f.open_and_lock()
11 if f.is_locked():
12 print 'Acquired filename with r+b mode'
13 f.file_handle().write('locked data')
14 else:
15 print 'Aquired filename with rb mode'
16 f.unlock_and_close()
17 """
18
19 __author__ = 'cache@google.com (David T McWherter)'
20
21 import errno
22 import logging
23 import os
24 import time
25
26 logger = logging.getLogger(__name__)
27
28
30 """Trying to lock a file that has already been locked by the LockedFile."""
31 pass
32
33
35 """Base class for different locking primitives."""
36
37 - def __init__(self, filename, mode, fallback_mode):
38 """Create an Opener.
39
40 Args:
41 filename: string, The pathname of the file.
42 mode: string, The preferred mode to access the file with.
43 fallback_mode: string, The mode to use if locking fails.
44 """
45 self._locked = False
46 self._filename = filename
47 self._mode = mode
48 self._fallback_mode = fallback_mode
49 self._fh = None
50
52 """Was the file locked."""
53 return self._locked
54
56 """The file handle to the file. Valid only after opened."""
57 return self._fh
58
60 """The filename that is being locked."""
61 return self._filename
62
64 """Open the file and lock it.
65
66 Args:
67 timeout: float, How long to try to lock for.
68 delay: float, How long to wait between retries.
69 """
70 pass
71
73 """Unlock and close the file."""
74 pass
75
76
78 """Lock files using Posix advisory lock files."""
79
81 """Open the file and lock it.
82
83 Tries to create a .lock file next to the file we're trying to open.
84
85 Args:
86 timeout: float, How long to try to lock for.
87 delay: float, How long to wait between retries.
88
89 Raises:
90 AlreadyLockedException: if the lock is already acquired.
91 IOError: if the open fails.
92 """
93 if self._locked:
94 raise AlreadyLockedException('File %s is already locked' %
95 self._filename)
96 self._locked = False
97
98 try:
99 self._fh = open(self._filename, self._mode)
100 except IOError, e:
101
102 if e.errno == errno.EACCES:
103 self._fh = open(self._filename, self._fallback_mode)
104 return
105
106 lock_filename = self._posix_lockfile(self._filename)
107 start_time = time.time()
108 while True:
109 try:
110 self._lock_fd = os.open(lock_filename,
111 os.O_CREAT|os.O_EXCL|os.O_RDWR)
112 self._locked = True
113 break
114
115 except OSError, e:
116 if e.errno != errno.EEXIST:
117 raise
118 if (time.time() - start_time) >= timeout:
119 logger.warn('Could not acquire lock %s in %s seconds' % (
120 lock_filename, timeout))
121
122 if self._fh:
123 self._fh.close()
124 self._fh = open(self._filename, self._fallback_mode)
125 return
126 time.sleep(delay)
127
129 """Unlock a file by removing the .lock file, and close the handle."""
130 if self._locked:
131 lock_filename = self._posix_lockfile(self._filename)
132 os.unlink(lock_filename)
133 os.close(self._lock_fd)
134 self._locked = False
135 self._lock_fd = None
136 if self._fh:
137 self._fh.close()
138
140 """The name of the lock file to use for posix locking."""
141 return '%s.lock' % filename
142
143
144 try:
145 import fcntl
146
148 """Open, lock, and unlock a file using fcntl.lockf."""
149
151 """Open the file and lock it.
152
153 Args:
154 timeout: float, How long to try to lock for.
155 delay: float, How long to wait between retries
156
157 Raises:
158 AlreadyLockedException: if the lock is already acquired.
159 IOError: if the open fails.
160 """
161 if self._locked:
162 raise AlreadyLockedException('File %s is already locked' %
163 self._filename)
164 start_time = time.time()
165
166 try:
167 self._fh = open(self._filename, self._mode)
168 except IOError, e:
169
170 if e.errno == errno.EACCES:
171 self._fh = open(self._filename, self._fallback_mode)
172 return
173
174
175 while True:
176 try:
177 fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX)
178 self._locked = True
179 return
180 except IOError, e:
181
182 if timeout == 0:
183 raise e
184 if e.errno != errno.EACCES:
185 raise e
186
187 if (time.time() - start_time) >= timeout:
188 logger.warn('Could not lock %s in %s seconds' % (
189 self._filename, timeout))
190 if self._fh:
191 self._fh.close()
192 self._fh = open(self._filename, self._fallback_mode)
193 return
194 time.sleep(delay)
195
197 """Close and unlock the file using the fcntl.lockf primitive."""
198 if self._locked:
199 fcntl.lockf(self._fh.fileno(), fcntl.LOCK_UN)
200 self._locked = False
201 if self._fh:
202 self._fh.close()
203 except ImportError:
204 _FcntlOpener = None
205
206
207 try:
208 import pywintypes
209 import win32con
210 import win32file
211
213 """Open, lock, and unlock a file using windows primitives."""
214
215
216
217 FILE_IN_USE_ERROR = 33
218
219
220
221 FILE_ALREADY_UNLOCKED_ERROR = 158
222
224 """Open the file and lock it.
225
226 Args:
227 timeout: float, How long to try to lock for.
228 delay: float, How long to wait between retries
229
230 Raises:
231 AlreadyLockedException: if the lock is already acquired.
232 IOError: if the open fails.
233 """
234 if self._locked:
235 raise AlreadyLockedException('File %s is already locked' %
236 self._filename)
237 start_time = time.time()
238
239 try:
240 self._fh = open(self._filename, self._mode)
241 except IOError, e:
242
243 if e.errno == errno.EACCES:
244 self._fh = open(self._filename, self._fallback_mode)
245 return
246
247
248 while True:
249 try:
250 hfile = win32file._get_osfhandle(self._fh.fileno())
251 win32file.LockFileEx(
252 hfile,
253 (win32con.LOCKFILE_FAIL_IMMEDIATELY|
254 win32con.LOCKFILE_EXCLUSIVE_LOCK), 0, -0x10000,
255 pywintypes.OVERLAPPED())
256 self._locked = True
257 return
258 except pywintypes.error, e:
259 if timeout == 0:
260 raise e
261
262
263 if e[0] != _Win32Opener.FILE_IN_USE_ERROR:
264 raise
265
266
267 if (time.time() - start_time) >= timeout:
268 logger.warn('Could not lock %s in %s seconds' % (
269 self._filename, timeout))
270 if self._fh:
271 self._fh.close()
272 self._fh = open(self._filename, self._fallback_mode)
273 return
274 time.sleep(delay)
275
277 """Close and unlock the file using the win32 primitive."""
278 if self._locked:
279 try:
280 hfile = win32file._get_osfhandle(self._fh.fileno())
281 win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED())
282 except pywintypes.error, e:
283 if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR:
284 raise
285 self._locked = False
286 if self._fh:
287 self._fh.close()
288 except ImportError:
289 _Win32Opener = None
290
291
293 """Represent a file that has exclusive access."""
294
295 - def __init__(self, filename, mode, fallback_mode, use_native_locking=True):
296 """Construct a LockedFile.
297
298 Args:
299 filename: string, The path of the file to open.
300 mode: string, The mode to try to open the file with.
301 fallback_mode: string, The mode to use if locking fails.
302 use_native_locking: bool, Whether or not fcntl/win32 locking is used.
303 """
304 opener = None
305 if not opener and use_native_locking:
306 if _Win32Opener:
307 opener = _Win32Opener(filename, mode, fallback_mode)
308 if _FcntlOpener:
309 opener = _FcntlOpener(filename, mode, fallback_mode)
310
311 if not opener:
312 opener = _PosixOpener(filename, mode, fallback_mode)
313
314 self._opener = opener
315
317 """Return the filename we were constructed with."""
318 return self._opener._filename
319
321 """Return the file_handle to the opened file."""
322 return self._opener.file_handle()
323
325 """Return whether we successfully locked the file."""
326 return self._opener.is_locked()
327
329 """Open the file, trying to lock it.
330
331 Args:
332 timeout: float, The number of seconds to try to acquire the lock.
333 delay: float, The number of seconds to wait between retry attempts.
334
335 Raises:
336 AlreadyLockedException: if the lock is already acquired.
337 IOError: if the open fails.
338 """
339 self._opener.open_and_lock(timeout, delay)
340
344