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 from oauth2client import util
27
28 logger = logging.getLogger(__name__)
32 """Trying to lock a file that has already been locked by the LockedFile."""
33 pass
34
37 """Base class for different locking primitives."""
38
39 - def __init__(self, filename, mode, fallback_mode):
40 """Create an Opener.
41
42 Args:
43 filename: string, The pathname of the file.
44 mode: string, The preferred mode to access the file with.
45 fallback_mode: string, The mode to use if locking fails.
46 """
47 self._locked = False
48 self._filename = filename
49 self._mode = mode
50 self._fallback_mode = fallback_mode
51 self._fh = None
52
54 """Was the file locked."""
55 return self._locked
56
58 """The file handle to the file. Valid only after opened."""
59 return self._fh
60
62 """The filename that is being locked."""
63 return self._filename
64
66 """Open the file and lock it.
67
68 Args:
69 timeout: float, How long to try to lock for.
70 delay: float, How long to wait between retries.
71 """
72 pass
73
75 """Unlock and close the file."""
76 pass
77
80 """Lock files using Posix advisory lock files."""
81
83 """Open the file and lock it.
84
85 Tries to create a .lock file next to the file we're trying to open.
86
87 Args:
88 timeout: float, How long to try to lock for.
89 delay: float, How long to wait between retries.
90
91 Raises:
92 AlreadyLockedException: if the lock is already acquired.
93 IOError: if the open fails.
94 """
95 if self._locked:
96 raise AlreadyLockedException('File %s is already locked' %
97 self._filename)
98 self._locked = False
99
100 try:
101 self._fh = open(self._filename, self._mode)
102 except IOError, e:
103
104 if e.errno == errno.EACCES:
105 self._fh = open(self._filename, self._fallback_mode)
106 return
107
108 lock_filename = self._posix_lockfile(self._filename)
109 start_time = time.time()
110 while True:
111 try:
112 self._lock_fd = os.open(lock_filename,
113 os.O_CREAT|os.O_EXCL|os.O_RDWR)
114 self._locked = True
115 break
116
117 except OSError, e:
118 if e.errno != errno.EEXIST:
119 raise
120 if (time.time() - start_time) >= timeout:
121 logger.warn('Could not acquire lock %s in %s seconds' % (
122 lock_filename, timeout))
123
124 if self._fh:
125 self._fh.close()
126 self._fh = open(self._filename, self._fallback_mode)
127 return
128 time.sleep(delay)
129
131 """Unlock a file by removing the .lock file, and close the handle."""
132 if self._locked:
133 lock_filename = self._posix_lockfile(self._filename)
134 os.unlink(lock_filename)
135 os.close(self._lock_fd)
136 self._locked = False
137 self._lock_fd = None
138 if self._fh:
139 self._fh.close()
140
142 """The name of the lock file to use for posix locking."""
143 return '%s.lock' % filename
144
145
146 try:
147 import fcntl
150 """Open, lock, and unlock a file using fcntl.lockf."""
151
153 """Open the file and lock it.
154
155 Args:
156 timeout: float, How long to try to lock for.
157 delay: float, How long to wait between retries
158
159 Raises:
160 AlreadyLockedException: if the lock is already acquired.
161 IOError: if the open fails.
162 """
163 if self._locked:
164 raise AlreadyLockedException('File %s is already locked' %
165 self._filename)
166 start_time = time.time()
167
168 try:
169 self._fh = open(self._filename, self._mode)
170 except IOError, e:
171
172 if e.errno == errno.EACCES:
173 self._fh = open(self._filename, self._fallback_mode)
174 return
175
176
177 while True:
178 try:
179 fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX)
180 self._locked = True
181 return
182 except IOError, e:
183
184 if timeout == 0:
185 raise e
186 if e.errno != errno.EACCES:
187 raise e
188
189 if (time.time() - start_time) >= timeout:
190 logger.warn('Could not lock %s in %s seconds' % (
191 self._filename, timeout))
192 if self._fh:
193 self._fh.close()
194 self._fh = open(self._filename, self._fallback_mode)
195 return
196 time.sleep(delay)
197
199 """Close and unlock the file using the fcntl.lockf primitive."""
200 if self._locked:
201 fcntl.lockf(self._fh.fileno(), fcntl.LOCK_UN)
202 self._locked = False
203 if self._fh:
204 self._fh.close()
205 except ImportError:
206 _FcntlOpener = None
207
208
209 try:
210 import pywintypes
211 import win32con
212 import win32file
215 """Open, lock, and unlock a file using windows primitives."""
216
217
218
219 FILE_IN_USE_ERROR = 33
220
221
222
223 FILE_ALREADY_UNLOCKED_ERROR = 158
224
226 """Open the file and lock it.
227
228 Args:
229 timeout: float, How long to try to lock for.
230 delay: float, How long to wait between retries
231
232 Raises:
233 AlreadyLockedException: if the lock is already acquired.
234 IOError: if the open fails.
235 """
236 if self._locked:
237 raise AlreadyLockedException('File %s is already locked' %
238 self._filename)
239 start_time = time.time()
240
241 try:
242 self._fh = open(self._filename, self._mode)
243 except IOError, e:
244
245 if e.errno == errno.EACCES:
246 self._fh = open(self._filename, self._fallback_mode)
247 return
248
249
250 while True:
251 try:
252 hfile = win32file._get_osfhandle(self._fh.fileno())
253 win32file.LockFileEx(
254 hfile,
255 (win32con.LOCKFILE_FAIL_IMMEDIATELY|
256 win32con.LOCKFILE_EXCLUSIVE_LOCK), 0, -0x10000,
257 pywintypes.OVERLAPPED())
258 self._locked = True
259 return
260 except pywintypes.error, e:
261 if timeout == 0:
262 raise e
263
264
265 if e[0] != _Win32Opener.FILE_IN_USE_ERROR:
266 raise
267
268
269 if (time.time() - start_time) >= timeout:
270 logger.warn('Could not lock %s in %s seconds' % (
271 self._filename, timeout))
272 if self._fh:
273 self._fh.close()
274 self._fh = open(self._filename, self._fallback_mode)
275 return
276 time.sleep(delay)
277
279 """Close and unlock the file using the win32 primitive."""
280 if self._locked:
281 try:
282 hfile = win32file._get_osfhandle(self._fh.fileno())
283 win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED())
284 except pywintypes.error, e:
285 if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR:
286 raise
287 self._locked = False
288 if self._fh:
289 self._fh.close()
290 except ImportError:
291 _Win32Opener = None
295 """Represent a file that has exclusive access."""
296
297 @util.positional(4)
298 - def __init__(self, filename, mode, fallback_mode, use_native_locking=True):
299 """Construct a LockedFile.
300
301 Args:
302 filename: string, The path of the file to open.
303 mode: string, The mode to try to open the file with.
304 fallback_mode: string, The mode to use if locking fails.
305 use_native_locking: bool, Whether or not fcntl/win32 locking is used.
306 """
307 opener = None
308 if not opener and use_native_locking:
309 if _Win32Opener:
310 opener = _Win32Opener(filename, mode, fallback_mode)
311 if _FcntlOpener:
312 opener = _FcntlOpener(filename, mode, fallback_mode)
313
314 if not opener:
315 opener = _PosixOpener(filename, mode, fallback_mode)
316
317 self._opener = opener
318
320 """Return the filename we were constructed with."""
321 return self._opener._filename
322
324 """Return the file_handle to the opened file."""
325 return self._opener.file_handle()
326
328 """Return whether we successfully locked the file."""
329 return self._opener.is_locked()
330
332 """Open the file, trying to lock it.
333
334 Args:
335 timeout: float, The number of seconds to try to acquire the lock.
336 delay: float, The number of seconds to wait between retry attempts.
337
338 Raises:
339 AlreadyLockedException: if the lock is already acquired.
340 IOError: if the open fails.
341 """
342 self._opener.open_and_lock(timeout, delay)
343
347