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
147 """Open, lock, and unlock a file using fcntl.lockf."""
148
150 """Open the file and lock it.
151
152 Args:
153 timeout: float, How long to try to lock for.
154 delay: float, How long to wait between retries
155
156 Raises:
157 AlreadyLockedException: if the lock is already acquired.
158 IOError: if the open fails.
159 """
160 if self._locked:
161 raise AlreadyLockedException('File %s is already locked' %
162 self._filename)
163 start_time = time.time()
164
165 try:
166 self._fh = open(self._filename, self._mode)
167 except IOError, e:
168
169 if e.errno == errno.EACCES:
170 self._fh = open(self._filename, self._fallback_mode)
171 return
172
173
174 while True:
175 try:
176 fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX)
177 self._locked = True
178 return
179 except IOError, e:
180
181 if timeout == 0:
182 raise e
183 if e.errno != errno.EACCES:
184 raise e
185
186 if (time.time() - start_time) >= timeout:
187 logger.warn('Could not lock %s in %s seconds' % (
188 self._filename, timeout))
189 if self._fh:
190 self._fh.close()
191 self._fh = open(self._filename, self._fallback_mode)
192 return
193 time.sleep(delay)
194
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
208 """Represent a file that has exclusive access."""
209
210 - def __init__(self, filename, mode, fallback_mode, use_fcntl=True):
211 """Construct a LockedFile.
212
213 Args:
214 filename: string, The path of the file to open.
215 mode: string, The mode to try to open the file with.
216 fallback_mode: string, The mode to use if locking fails.
217 use_fcntl: string, Whether or not fcntl-based locking should be used.
218 """
219 if not use_fcntl:
220 self._opener = _PosixOpener(filename, mode, fallback_mode)
221 else:
222 if _FcntlOpener:
223 self._opener = _FcntlOpener(filename, mode, fallback_mode)
224 else:
225 self._opener = _PosixOpener(filename, mode, fallback_mode)
226
228 """Return the filename we were constructed with."""
229 return self._opener._filename
230
232 """Return the file_handle to the opened file."""
233 return self._opener.file_handle()
234
236 """Return whether we successfully locked the file."""
237 return self._opener.is_locked()
238
240 """Open the file, trying to lock it.
241
242 Args:
243 timeout: float, The number of seconds to try to acquire the lock.
244 delay: float, The number of seconds to wait between retry attempts.
245
246 Raises:
247 AlreadyLockedException: if the lock is already acquired.
248 IOError: if the open fails.
249 """
250 self._opener.open_and_lock(timeout, delay)
251
255