Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/filelock.py @ 0:d30785e31577 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author | guerler |
---|---|
date | Fri, 31 Jul 2020 00:18:57 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:d30785e31577 |
---|---|
1 # This is free and unencumbered software released into the public domain. | |
2 # | |
3 # Anyone is free to copy, modify, publish, use, compile, sell, or | |
4 # distribute this software, either in source code form or as a compiled | |
5 # binary, for any purpose, commercial or non-commercial, and by any | |
6 # means. | |
7 # | |
8 # In jurisdictions that recognize copyright laws, the author or authors | |
9 # of this software dedicate any and all copyright interest in the | |
10 # software to the public domain. We make this dedication for the benefit | |
11 # of the public at large and to the detriment of our heirs and | |
12 # successors. We intend this dedication to be an overt act of | |
13 # relinquishment in perpetuity of all present and future rights to this | |
14 # software under copyright law. | |
15 # | |
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
17 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
18 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
19 # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
20 # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
21 # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
22 # OTHER DEALINGS IN THE SOFTWARE. | |
23 # | |
24 # For more information, please refer to <http://unlicense.org> | |
25 | |
26 """ | |
27 A platform independent file lock that supports the with-statement. | |
28 """ | |
29 | |
30 | |
31 # Modules | |
32 # ------------------------------------------------ | |
33 import logging | |
34 import os | |
35 import threading | |
36 import time | |
37 try: | |
38 import warnings | |
39 except ImportError: | |
40 warnings = None | |
41 | |
42 try: | |
43 import msvcrt | |
44 except ImportError: | |
45 msvcrt = None | |
46 | |
47 try: | |
48 import fcntl | |
49 except ImportError: | |
50 fcntl = None | |
51 | |
52 | |
53 # Backward compatibility | |
54 # ------------------------------------------------ | |
55 try: | |
56 TimeoutError | |
57 except NameError: | |
58 TimeoutError = OSError | |
59 | |
60 | |
61 # Data | |
62 # ------------------------------------------------ | |
63 __all__ = [ | |
64 "Timeout", | |
65 "BaseFileLock", | |
66 "WindowsFileLock", | |
67 "UnixFileLock", | |
68 "SoftFileLock", | |
69 "FileLock" | |
70 ] | |
71 | |
72 __version__ = "3.0.12" | |
73 | |
74 | |
75 _logger = None | |
76 def logger(): | |
77 """Returns the logger instance used in this module.""" | |
78 global _logger | |
79 _logger = _logger or logging.getLogger(__name__) | |
80 return _logger | |
81 | |
82 | |
83 # Exceptions | |
84 # ------------------------------------------------ | |
85 class Timeout(TimeoutError): | |
86 """ | |
87 Raised when the lock could not be acquired in *timeout* | |
88 seconds. | |
89 """ | |
90 | |
91 def __init__(self, lock_file): | |
92 """ | |
93 """ | |
94 #: The path of the file lock. | |
95 self.lock_file = lock_file | |
96 return None | |
97 | |
98 def __str__(self): | |
99 temp = "The file lock '{}' could not be acquired."\ | |
100 .format(self.lock_file) | |
101 return temp | |
102 | |
103 | |
104 # Classes | |
105 # ------------------------------------------------ | |
106 | |
107 # This is a helper class which is returned by :meth:`BaseFileLock.acquire` | |
108 # and wraps the lock to make sure __enter__ is not called twice when entering | |
109 # the with statement. | |
110 # If we would simply return *self*, the lock would be acquired again | |
111 # in the *__enter__* method of the BaseFileLock, but not released again | |
112 # automatically. | |
113 # | |
114 # :seealso: issue #37 (memory leak) | |
115 class _Acquire_ReturnProxy(object): | |
116 | |
117 def __init__(self, lock): | |
118 self.lock = lock | |
119 return None | |
120 | |
121 def __enter__(self): | |
122 return self.lock | |
123 | |
124 def __exit__(self, exc_type, exc_value, traceback): | |
125 self.lock.release() | |
126 return None | |
127 | |
128 | |
129 class BaseFileLock(object): | |
130 """ | |
131 Implements the base class of a file lock. | |
132 """ | |
133 | |
134 def __init__(self, lock_file, timeout = -1): | |
135 """ | |
136 """ | |
137 # The path to the lock file. | |
138 self._lock_file = lock_file | |
139 | |
140 # The file descriptor for the *_lock_file* as it is returned by the | |
141 # os.open() function. | |
142 # This file lock is only NOT None, if the object currently holds the | |
143 # lock. | |
144 self._lock_file_fd = None | |
145 | |
146 # The default timeout value. | |
147 self.timeout = timeout | |
148 | |
149 # We use this lock primarily for the lock counter. | |
150 self._thread_lock = threading.Lock() | |
151 | |
152 # The lock counter is used for implementing the nested locking | |
153 # mechanism. Whenever the lock is acquired, the counter is increased and | |
154 # the lock is only released, when this value is 0 again. | |
155 self._lock_counter = 0 | |
156 return None | |
157 | |
158 @property | |
159 def lock_file(self): | |
160 """ | |
161 The path to the lock file. | |
162 """ | |
163 return self._lock_file | |
164 | |
165 @property | |
166 def timeout(self): | |
167 """ | |
168 You can set a default timeout for the filelock. It will be used as | |
169 fallback value in the acquire method, if no timeout value (*None*) is | |
170 given. | |
171 | |
172 If you want to disable the timeout, set it to a negative value. | |
173 | |
174 A timeout of 0 means, that there is exactly one attempt to acquire the | |
175 file lock. | |
176 | |
177 .. versionadded:: 2.0.0 | |
178 """ | |
179 return self._timeout | |
180 | |
181 @timeout.setter | |
182 def timeout(self, value): | |
183 """ | |
184 """ | |
185 self._timeout = float(value) | |
186 return None | |
187 | |
188 # Platform dependent locking | |
189 # -------------------------------------------- | |
190 | |
191 def _acquire(self): | |
192 """ | |
193 Platform dependent. If the file lock could be | |
194 acquired, self._lock_file_fd holds the file descriptor | |
195 of the lock file. | |
196 """ | |
197 raise NotImplementedError() | |
198 | |
199 def _release(self): | |
200 """ | |
201 Releases the lock and sets self._lock_file_fd to None. | |
202 """ | |
203 raise NotImplementedError() | |
204 | |
205 # Platform independent methods | |
206 # -------------------------------------------- | |
207 | |
208 @property | |
209 def is_locked(self): | |
210 """ | |
211 True, if the object holds the file lock. | |
212 | |
213 .. versionchanged:: 2.0.0 | |
214 | |
215 This was previously a method and is now a property. | |
216 """ | |
217 return self._lock_file_fd is not None | |
218 | |
219 def acquire(self, timeout=None, poll_intervall=0.05): | |
220 """ | |
221 Acquires the file lock or fails with a :exc:`Timeout` error. | |
222 | |
223 .. code-block:: python | |
224 | |
225 # You can use this method in the context manager (recommended) | |
226 with lock.acquire(): | |
227 pass | |
228 | |
229 # Or use an equivalent try-finally construct: | |
230 lock.acquire() | |
231 try: | |
232 pass | |
233 finally: | |
234 lock.release() | |
235 | |
236 :arg float timeout: | |
237 The maximum time waited for the file lock. | |
238 If ``timeout < 0``, there is no timeout and this method will | |
239 block until the lock could be acquired. | |
240 If ``timeout`` is None, the default :attr:`~timeout` is used. | |
241 | |
242 :arg float poll_intervall: | |
243 We check once in *poll_intervall* seconds if we can acquire the | |
244 file lock. | |
245 | |
246 :raises Timeout: | |
247 if the lock could not be acquired in *timeout* seconds. | |
248 | |
249 .. versionchanged:: 2.0.0 | |
250 | |
251 This method returns now a *proxy* object instead of *self*, | |
252 so that it can be used in a with statement without side effects. | |
253 """ | |
254 # Use the default timeout, if no timeout is provided. | |
255 if timeout is None: | |
256 timeout = self.timeout | |
257 | |
258 # Increment the number right at the beginning. | |
259 # We can still undo it, if something fails. | |
260 with self._thread_lock: | |
261 self._lock_counter += 1 | |
262 | |
263 lock_id = id(self) | |
264 lock_filename = self._lock_file | |
265 start_time = time.time() | |
266 try: | |
267 while True: | |
268 with self._thread_lock: | |
269 if not self.is_locked: | |
270 logger().debug('Attempting to acquire lock %s on %s', lock_id, lock_filename) | |
271 self._acquire() | |
272 | |
273 if self.is_locked: | |
274 logger().info('Lock %s acquired on %s', lock_id, lock_filename) | |
275 break | |
276 elif timeout >= 0 and time.time() - start_time > timeout: | |
277 logger().debug('Timeout on acquiring lock %s on %s', lock_id, lock_filename) | |
278 raise Timeout(self._lock_file) | |
279 else: | |
280 logger().debug( | |
281 'Lock %s not acquired on %s, waiting %s seconds ...', | |
282 lock_id, lock_filename, poll_intervall | |
283 ) | |
284 time.sleep(poll_intervall) | |
285 except: | |
286 # Something did go wrong, so decrement the counter. | |
287 with self._thread_lock: | |
288 self._lock_counter = max(0, self._lock_counter - 1) | |
289 | |
290 raise | |
291 return _Acquire_ReturnProxy(lock = self) | |
292 | |
293 def release(self, force = False): | |
294 """ | |
295 Releases the file lock. | |
296 | |
297 Please note, that the lock is only completly released, if the lock | |
298 counter is 0. | |
299 | |
300 Also note, that the lock file itself is not automatically deleted. | |
301 | |
302 :arg bool force: | |
303 If true, the lock counter is ignored and the lock is released in | |
304 every case. | |
305 """ | |
306 with self._thread_lock: | |
307 | |
308 if self.is_locked: | |
309 self._lock_counter -= 1 | |
310 | |
311 if self._lock_counter == 0 or force: | |
312 lock_id = id(self) | |
313 lock_filename = self._lock_file | |
314 | |
315 logger().debug('Attempting to release lock %s on %s', lock_id, lock_filename) | |
316 self._release() | |
317 self._lock_counter = 0 | |
318 logger().info('Lock %s released on %s', lock_id, lock_filename) | |
319 | |
320 return None | |
321 | |
322 def __enter__(self): | |
323 self.acquire() | |
324 return self | |
325 | |
326 def __exit__(self, exc_type, exc_value, traceback): | |
327 self.release() | |
328 return None | |
329 | |
330 def __del__(self): | |
331 self.release(force = True) | |
332 return None | |
333 | |
334 | |
335 # Windows locking mechanism | |
336 # ~~~~~~~~~~~~~~~~~~~~~~~~~ | |
337 | |
338 class WindowsFileLock(BaseFileLock): | |
339 """ | |
340 Uses the :func:`msvcrt.locking` function to hard lock the lock file on | |
341 windows systems. | |
342 """ | |
343 | |
344 def _acquire(self): | |
345 open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC | |
346 | |
347 try: | |
348 fd = os.open(self._lock_file, open_mode) | |
349 except OSError: | |
350 pass | |
351 else: | |
352 try: | |
353 msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) | |
354 except (IOError, OSError): | |
355 os.close(fd) | |
356 else: | |
357 self._lock_file_fd = fd | |
358 return None | |
359 | |
360 def _release(self): | |
361 fd = self._lock_file_fd | |
362 self._lock_file_fd = None | |
363 msvcrt.locking(fd, msvcrt.LK_UNLCK, 1) | |
364 os.close(fd) | |
365 | |
366 try: | |
367 os.remove(self._lock_file) | |
368 # Probably another instance of the application | |
369 # that acquired the file lock. | |
370 except OSError: | |
371 pass | |
372 return None | |
373 | |
374 # Unix locking mechanism | |
375 # ~~~~~~~~~~~~~~~~~~~~~~ | |
376 | |
377 class UnixFileLock(BaseFileLock): | |
378 """ | |
379 Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems. | |
380 """ | |
381 | |
382 def _acquire(self): | |
383 open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC | |
384 fd = os.open(self._lock_file, open_mode) | |
385 | |
386 try: | |
387 fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) | |
388 except (IOError, OSError): | |
389 os.close(fd) | |
390 else: | |
391 self._lock_file_fd = fd | |
392 return None | |
393 | |
394 def _release(self): | |
395 # Do not remove the lockfile: | |
396 # | |
397 # https://github.com/benediktschmitt/py-filelock/issues/31 | |
398 # https://stackoverflow.com/questions/17708885/flock-removing-locked-file-without-race-condition | |
399 fd = self._lock_file_fd | |
400 self._lock_file_fd = None | |
401 fcntl.flock(fd, fcntl.LOCK_UN) | |
402 os.close(fd) | |
403 return None | |
404 | |
405 # Soft lock | |
406 # ~~~~~~~~~ | |
407 | |
408 class SoftFileLock(BaseFileLock): | |
409 """ | |
410 Simply watches the existence of the lock file. | |
411 """ | |
412 | |
413 def _acquire(self): | |
414 open_mode = os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_TRUNC | |
415 try: | |
416 fd = os.open(self._lock_file, open_mode) | |
417 except (IOError, OSError): | |
418 pass | |
419 else: | |
420 self._lock_file_fd = fd | |
421 return None | |
422 | |
423 def _release(self): | |
424 os.close(self._lock_file_fd) | |
425 self._lock_file_fd = None | |
426 | |
427 try: | |
428 os.remove(self._lock_file) | |
429 # The file is already deleted and that's what we want. | |
430 except OSError: | |
431 pass | |
432 return None | |
433 | |
434 | |
435 # Platform filelock | |
436 # ~~~~~~~~~~~~~~~~~ | |
437 | |
438 #: Alias for the lock, which should be used for the current platform. On | |
439 #: Windows, this is an alias for :class:`WindowsFileLock`, on Unix for | |
440 #: :class:`UnixFileLock` and otherwise for :class:`SoftFileLock`. | |
441 FileLock = None | |
442 | |
443 if msvcrt: | |
444 FileLock = WindowsFileLock | |
445 elif fcntl: | |
446 FileLock = UnixFileLock | |
447 else: | |
448 FileLock = SoftFileLock | |
449 | |
450 if warnings is not None: | |
451 warnings.warn("only soft file lock is available") |