cdxcore 0.1.19__py3-none-any.whl → 0.1.21__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cdxcore might be problematic. Click here for more details.
- cdxcore/__init__.py +1 -1
- cdxcore/filelock.py +821 -0
- cdxcore/subdir.py +2 -2
- {cdxcore-0.1.19.dist-info → cdxcore-0.1.21.dist-info}/METADATA +2 -2
- {cdxcore-0.1.19.dist-info → cdxcore-0.1.21.dist-info}/RECORD +9 -8
- tmp/filelock.py +140 -56
- {cdxcore-0.1.19.dist-info → cdxcore-0.1.21.dist-info}/WHEEL +0 -0
- {cdxcore-0.1.19.dist-info → cdxcore-0.1.21.dist-info}/licenses/LICENSE +0 -0
- {cdxcore-0.1.19.dist-info → cdxcore-0.1.21.dist-info}/top_level.txt +0 -0
cdxcore/__init__.py
CHANGED
cdxcore/filelock.py
ADDED
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Simple file-based system-wide lock for both
|
|
4
|
+
`Linux <https://code.activestate.com/recipes/519626-simple-file-based-mutex-for-very-basic-ipc/>`__ and
|
|
5
|
+
`Windows <https://timgolden.me.uk/pywin32-docs/Windows_NT_Files_.2d.2d_Locking.html>`__.
|
|
6
|
+
|
|
7
|
+
Overview
|
|
8
|
+
--------
|
|
9
|
+
|
|
10
|
+
The most effective method of using ``filelock`` is calling :func:`cdxcore.filelock.AttemptLock`
|
|
11
|
+
in a context block::
|
|
12
|
+
|
|
13
|
+
from cdxcore.filelock import FileLock
|
|
14
|
+
from cdxcore.subdir import SubDir
|
|
15
|
+
lock_dir = SubDir("!/locks",ext="lck")
|
|
16
|
+
lock_name = lock_dir.full_file_name("lock1")
|
|
17
|
+
|
|
18
|
+
with AcquireLock( lock_name, timeout_second=2, timeout_retry=3 ):
|
|
19
|
+
# do locked activity
|
|
20
|
+
|
|
21
|
+
# locked section over
|
|
22
|
+
|
|
23
|
+
In above example the function :func:`cdxcore.filelock.AcquireLock` will attempt to acquire a file lock using the file ``lock_name`` in three attempts
|
|
24
|
+
with a timeout of two seconds between them. If acquiring a lock fails, a :class:`BlockingIOError` is raised.
|
|
25
|
+
|
|
26
|
+
If successful, the ``with`` construct ensures that the lock is released at the end of the block.
|
|
27
|
+
|
|
28
|
+
If we can handle a situation where a lock is not acquired safely,
|
|
29
|
+
the following pattern using :func:`cdxcore.filelock.AttemptLock` can bs used::
|
|
30
|
+
|
|
31
|
+
from cdxcore.filelock import FileLock
|
|
32
|
+
from cdxcore.subdir import SubDir
|
|
33
|
+
lock_dir = SubDir("!/locks",ext="lck")
|
|
34
|
+
lock_name = lock_dir.full_file_name("lock1")
|
|
35
|
+
|
|
36
|
+
with AttemptLock( lock_name, timeout_second=2, timeout_retry=3 ) as lock:
|
|
37
|
+
if lock.acquired:
|
|
38
|
+
# do locked activity
|
|
39
|
+
else:
|
|
40
|
+
# not locked activity
|
|
41
|
+
|
|
42
|
+
# locked section over
|
|
43
|
+
|
|
44
|
+
**Multiple Acquisitions**
|
|
45
|
+
|
|
46
|
+
Im both patterns above the lock was acquired only once. If the lock is to be acquired several times,
|
|
47
|
+
or to be passed to other functions, it is better to first create a :class:`cdxcore.filelock.Flock` object
|
|
48
|
+
and then use :func:`cdxcore.filelock.FLock.acquire` instead of :func:`cdxcore.filelock.AcquireLock`::
|
|
49
|
+
|
|
50
|
+
from cdxcore.filelock import FileLock
|
|
51
|
+
from cdxcore.subdir import SubDir
|
|
52
|
+
|
|
53
|
+
def subroutine( lock ):
|
|
54
|
+
with lock.aquire( timeout_second=2, timeout_retry=3 ):
|
|
55
|
+
# do locked activity
|
|
56
|
+
|
|
57
|
+
def mainroutine():
|
|
58
|
+
lock_dir = SubDir("!/locks",ext="lck")
|
|
59
|
+
lock_name = lock_dir.full_file_name("lock1")
|
|
60
|
+
lock = FileLock(lock_name)
|
|
61
|
+
|
|
62
|
+
with lock.aquire( timeout_second=2, timeout_retry=3 ):
|
|
63
|
+
# do locked activity
|
|
64
|
+
|
|
65
|
+
subroutine( lock )
|
|
66
|
+
|
|
67
|
+
In this case, :func:`cdxcore.filelock.FLock.attempt` can be used for conditional workflows
|
|
68
|
+
based on lock status::
|
|
69
|
+
|
|
70
|
+
def subroutine( lock ):
|
|
71
|
+
with lock.attempt( lock_name, timeout_second=2, timeout_retry=3 ) as lh:
|
|
72
|
+
if not lh.acquired:
|
|
73
|
+
return # job already done
|
|
74
|
+
# do locked activity
|
|
75
|
+
|
|
76
|
+
**Explicit State Management**
|
|
77
|
+
|
|
78
|
+
The use of ``with`` context blocks ensures that locks are released as soon
|
|
79
|
+
as the protected activity is finished. In some cases we may desired to finely
|
|
80
|
+
control such workflow.
|
|
81
|
+
In this case, use :func:`cdxcore.filelock.FLock.acquire` and :func:`cdxcore.filelock.FLock.release`
|
|
82
|
+
in pairs::
|
|
83
|
+
|
|
84
|
+
from cdxcore.filelock import FileLock
|
|
85
|
+
from cdxcore.subdir import SubDir
|
|
86
|
+
|
|
87
|
+
lock_dir = SubDir("!/locks",ext="lck")
|
|
88
|
+
lock_name = lock_dir.full_file_name("lock1")
|
|
89
|
+
lock = FileLock(lock_name)
|
|
90
|
+
|
|
91
|
+
def subroutine( lock ):
|
|
92
|
+
if not lock.acquire( timeout_second=2, timeout_retry=3 ):
|
|
93
|
+
return
|
|
94
|
+
# do protected work
|
|
95
|
+
lock.release()
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
if lock.acquire( timeout_second=2, timeout_retry=3 ):
|
|
99
|
+
# do some protected work
|
|
100
|
+
lock.release()
|
|
101
|
+
|
|
102
|
+
...
|
|
103
|
+
|
|
104
|
+
subroutine(lock)
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
if lock.acquire( timeout_second=2, timeout_retry=3 ):
|
|
108
|
+
# do some protected work
|
|
109
|
+
lock.release()
|
|
110
|
+
|
|
111
|
+
finally:
|
|
112
|
+
lock.clear() # <- clears all acquisitions of the lock and stops further use.
|
|
113
|
+
|
|
114
|
+
**Garbage Collection**
|
|
115
|
+
|
|
116
|
+
By default locks will delete the underlying file using :meth:`cdxcore.filelock.FLock.clear`
|
|
117
|
+
upon garbage collection. This can be triggered with :func:`gc.collect`.
|
|
118
|
+
|
|
119
|
+
Import
|
|
120
|
+
------
|
|
121
|
+
.. code-block:: python
|
|
122
|
+
|
|
123
|
+
from cdxcore.filelock import FileLock, AcquireLock
|
|
124
|
+
|
|
125
|
+
Documentation
|
|
126
|
+
-------------
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
from .err import verify
|
|
130
|
+
from .verbose import Context
|
|
131
|
+
from .util import datetime, fmt_datetime, fmt_seconds
|
|
132
|
+
from .subdir import SubDir
|
|
133
|
+
|
|
134
|
+
import os
|
|
135
|
+
import os.path
|
|
136
|
+
import time
|
|
137
|
+
import platform as platform
|
|
138
|
+
import threading as threading
|
|
139
|
+
|
|
140
|
+
_IS_WINDOWS = platform.system()[0] == "W"
|
|
141
|
+
_SYSTEM = "Windows" if _IS_WINDOWS else "Linux"
|
|
142
|
+
|
|
143
|
+
if _IS_WINDOWS:
|
|
144
|
+
# http://timgolden.me.uk/pywin32-docs/Windows_NT_Files_.2d.2d_Locking.html
|
|
145
|
+
# need to install pywin32
|
|
146
|
+
try:
|
|
147
|
+
import win32file as win32file
|
|
148
|
+
except Exception as e:
|
|
149
|
+
raise ModuleNotFoundError("pywin32") from e
|
|
150
|
+
|
|
151
|
+
import win32con
|
|
152
|
+
import pywintypes
|
|
153
|
+
import win32security
|
|
154
|
+
_WIN_HIGHBITS=0xffff0000 #high-order 32 bits of byte range to lock
|
|
155
|
+
|
|
156
|
+
else:
|
|
157
|
+
win32file = None
|
|
158
|
+
|
|
159
|
+
import os
|
|
160
|
+
|
|
161
|
+
class FLock:
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
class LockContext(object):
|
|
165
|
+
"""
|
|
166
|
+
A context handler returned by :meth:`cdxcore.filelock.Flock.acquire`,
|
|
167
|
+
:meth:`cdxcore.filelock.AcquireLock`, and :meth:`cdxcore.filelock.AttemptLock`.
|
|
168
|
+
"""
|
|
169
|
+
def __init__(self, flock : FLock, acquired : bool):
|
|
170
|
+
self._flock = flock
|
|
171
|
+
self._acquired = acquired
|
|
172
|
+
def __str__(self) -> str:
|
|
173
|
+
return str(self._flock)
|
|
174
|
+
def __bool__(self) -> bool:
|
|
175
|
+
return self._acquired
|
|
176
|
+
@property
|
|
177
|
+
def acquired(self) -> bool:
|
|
178
|
+
"""
|
|
179
|
+
Whether the underlying file lock was acquired by this context handler.
|
|
180
|
+
This is might be ``False`` for ``LockContext`` objects
|
|
181
|
+
returned by :meth:`cdxcore.filelock.AttemptLock`
|
|
182
|
+
"""
|
|
183
|
+
return self._acquired
|
|
184
|
+
@property
|
|
185
|
+
def filename(self) -> str:
|
|
186
|
+
""" Underlying file lock name """
|
|
187
|
+
return self._flock.filename
|
|
188
|
+
def __enter__(self):
|
|
189
|
+
"""
|
|
190
|
+
Enter a context block. This assumes that the lock was aquired; the corresponding ``__exit__``
|
|
191
|
+
will ``release()`` the lock.
|
|
192
|
+
"""
|
|
193
|
+
return self
|
|
194
|
+
def __exit__(self, *kargs, **kwargs):
|
|
195
|
+
""" Release the lock """
|
|
196
|
+
if self._acquired:
|
|
197
|
+
self._flock.release()
|
|
198
|
+
return False # raise exceptions
|
|
199
|
+
|
|
200
|
+
class FLock(object):#NOQA
|
|
201
|
+
r"""
|
|
202
|
+
System-wide file lock.
|
|
203
|
+
|
|
204
|
+
Do not construct members of this class directly as it will not be able to create a second lock on the same
|
|
205
|
+
lock file within the same process. Use the "factory" function :func:`cdxcore.filelock.FileLock`
|
|
206
|
+
instead.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
_CLASS_LOCK = threading.RLock()
|
|
210
|
+
_LOCKS = {}
|
|
211
|
+
_LOCK_CNT = 0
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def _create( filename : str, * ,
|
|
215
|
+
release_on_exit : bool = True,
|
|
216
|
+
verbose : Context|None = None ):
|
|
217
|
+
"""
|
|
218
|
+
Creates a new ``FLock`` in a multi-thread-safe way.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
with FLock._CLASS_LOCK:
|
|
222
|
+
flock = FLock._LOCKS.get(filename, None)
|
|
223
|
+
if flock is None:
|
|
224
|
+
flock = FLock( filename=filename, release_on_exit=release_on_exit, verbose=verbose )
|
|
225
|
+
FLock._LOCKS[filename] = flock
|
|
226
|
+
return flock
|
|
227
|
+
|
|
228
|
+
def __init__(self, filename : str, * ,
|
|
229
|
+
release_on_exit : bool = True,
|
|
230
|
+
verbose : Context|None = None ):
|
|
231
|
+
"""
|
|
232
|
+
__init__
|
|
233
|
+
"""
|
|
234
|
+
self._rlock = threading.RLock()
|
|
235
|
+
self._filename = SubDir.expandStandardRoot(filename)
|
|
236
|
+
self._fd = None
|
|
237
|
+
self._pid = os.getpid()
|
|
238
|
+
self._cnt = 0
|
|
239
|
+
self._verbose = verbose if not verbose is None else Context.quiet
|
|
240
|
+
self._release_on_exit = release_on_exit
|
|
241
|
+
nowstr = fmt_datetime(datetime.datetime.now())
|
|
242
|
+
self._lid = f"LOCK<{nowstr},...>: {filename}"
|
|
243
|
+
with self._CLASS_LOCK:
|
|
244
|
+
verify( not filename in self._LOCKS, lambda : f"Ther is already a lock for '{filename}' in place. Use 'FileLock()' to share locks accross threads", exception=FileExistsError)
|
|
245
|
+
my_cnt = self._LOCK_CNT
|
|
246
|
+
self._LOCK_CNT += 1
|
|
247
|
+
self._LOCKS[filename] = self
|
|
248
|
+
self._lid = f"LOCK<{nowstr},{my_cnt}>: {filename}"
|
|
249
|
+
|
|
250
|
+
def __del__(self):#NOQA
|
|
251
|
+
self.clear()
|
|
252
|
+
|
|
253
|
+
def clear(self):
|
|
254
|
+
"""
|
|
255
|
+
Clears the current object and forces its release.
|
|
256
|
+
Will delete the underlying lock file if ``release_on_exit`` was used
|
|
257
|
+
when constructing the lock.
|
|
258
|
+
"""
|
|
259
|
+
with self._rlock:
|
|
260
|
+
if self._filename is None:
|
|
261
|
+
return
|
|
262
|
+
if self._release_on_exit and not self._fd is None:
|
|
263
|
+
self._verbose.write("%s: deleting locked object", self._lid)
|
|
264
|
+
self.release( force=True )
|
|
265
|
+
with self._CLASS_LOCK:
|
|
266
|
+
del self._LOCKS[self._filename]
|
|
267
|
+
self._filename = None
|
|
268
|
+
|
|
269
|
+
def __str__(self) -> str:
|
|
270
|
+
""" Returns the current file name and the number of locks onbtained. """
|
|
271
|
+
assert not self._filename is None, ("Lock has been cleared", self._lid )
|
|
272
|
+
return f"{self._filename}:{self._cnt}"
|
|
273
|
+
|
|
274
|
+
def __bool__(self) -> bool:
|
|
275
|
+
""" Whether the lock is held """
|
|
276
|
+
assert not self._filename is None, ("Lock has been cleared", self._lid )
|
|
277
|
+
return self.locked
|
|
278
|
+
@property
|
|
279
|
+
def num_acquisitions(self) -> int:
|
|
280
|
+
"""
|
|
281
|
+
Returns the net number of times the file was acquired using :meth:`cdxcore.filelock.FLock.acquire`.
|
|
282
|
+
Zero if the lock is not currently held.
|
|
283
|
+
"""
|
|
284
|
+
assert not self._filename is None, ("Lock has been cleared", self._lid )
|
|
285
|
+
return self._cnt
|
|
286
|
+
@property
|
|
287
|
+
def locked(self) -> bool:
|
|
288
|
+
""" Whether the lock is active. """
|
|
289
|
+
assert not self._filename is None, ("Lock has been cleared", self._lid )
|
|
290
|
+
return self._cnt > 0
|
|
291
|
+
@property
|
|
292
|
+
def filename(self) -> str:
|
|
293
|
+
""" Return the filename of the lock. """
|
|
294
|
+
assert not self._filename is None, ("Lock has been cleared", self._lid )
|
|
295
|
+
return self._filename
|
|
296
|
+
|
|
297
|
+
def acquire(self, wait : bool = True,
|
|
298
|
+
*, timeout_seconds : int = 1,
|
|
299
|
+
timeout_retry : int = 5,
|
|
300
|
+
raise_on_fail : bool = True) -> LockContext|None:
|
|
301
|
+
"""
|
|
302
|
+
Acquire lock.
|
|
303
|
+
|
|
304
|
+
If successful, this function returns a :class:`cdxcore.filelock.LockContext` which can
|
|
305
|
+
be used in a ``with`` statement as follows::
|
|
306
|
+
|
|
307
|
+
from cdxcore.filelock import FileLock
|
|
308
|
+
from cdxcore.subdir import SubDir
|
|
309
|
+
|
|
310
|
+
lock_dir = SubDir("!/locks",ext="lck")
|
|
311
|
+
lock_name = lock_dir.full_file_name("lock1")
|
|
312
|
+
lock = FileLock(lock_name)
|
|
313
|
+
|
|
314
|
+
with lock.aquire( timeout_second=2, timeout_retry=3 ):
|
|
315
|
+
# do locked activity
|
|
316
|
+
# no longer locked
|
|
317
|
+
|
|
318
|
+
In case ``acquire()`` fails to obtain the lock, by default it will raise an exception.
|
|
319
|
+
|
|
320
|
+
**One-Shot**
|
|
321
|
+
|
|
322
|
+
If you only acquire a lock once, it is more convenient to use :func:`cdxcore.filelock.AcquireLock`::
|
|
323
|
+
|
|
324
|
+
from cdxcore.filelock import FileLock
|
|
325
|
+
from cdxcore.subdir import SubDir
|
|
326
|
+
|
|
327
|
+
lock_dir = SubDir("!/locks",ext="lck")
|
|
328
|
+
lock_name = lock_dir.full_file_name("lock1")
|
|
329
|
+
|
|
330
|
+
with AcquireLock( lock_name, timeout_second=2, timeout_retry=3 ):
|
|
331
|
+
# do locked activity
|
|
332
|
+
# no longer locked
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
wait : bool, default ``True``
|
|
338
|
+
|
|
339
|
+
* If ``False``, return immediately if the lock cannot be acquired.
|
|
340
|
+
* If ``True``, wait with below parameters; in particular if these are left as defaults the lock will wait indefinitely.
|
|
341
|
+
|
|
342
|
+
timeout_seconds : int | None, default ``None``
|
|
343
|
+
Number of seconds to wait before retrying.
|
|
344
|
+
Set to ``0``` to fail immediately.
|
|
345
|
+
If set to ``None``, then behaviour will depend on ``wait``:
|
|
346
|
+
|
|
347
|
+
* If wait is ``True``, then ``timeout_seconds==1``.
|
|
348
|
+
* If wait is ``False``, then ``timeout_seconds==0``.
|
|
349
|
+
|
|
350
|
+
timeout_retry : int | None, default ``None``
|
|
351
|
+
How many times to retry before timing out.
|
|
352
|
+
Set to ``None`` to retry indefinitely.
|
|
353
|
+
|
|
354
|
+
raise_on_fail : bool, default ``True``
|
|
355
|
+
By default, if the constructor fails to obtain the lock, raise an exception.
|
|
356
|
+
This will be either of type
|
|
357
|
+
|
|
358
|
+
* :class:`TimeoutError` if ``timeout_seconds > 0`` and ``wait==True``, or
|
|
359
|
+
* :class:`BlockingIOError` if ``timeout_seconds == 0`` or ``wait==False``.
|
|
360
|
+
|
|
361
|
+
If the function could not acquire a lock on the file and if ``raise_on_fail`` is ``False``,
|
|
362
|
+
then this function returns ``None``. This can be used for manual control workflows.
|
|
363
|
+
|
|
364
|
+
Returns
|
|
365
|
+
-------
|
|
366
|
+
Context : :class:`cdxcore.filelock.LockContext`
|
|
367
|
+
A context manager representing the acquired state which can be used with ``with``. If the context
|
|
368
|
+
manager protocol os used,
|
|
369
|
+
then :meth:`cdxcore.filelock.release` is called at the end of the ``with`` statement.
|
|
370
|
+
|
|
371
|
+
This function returns ``None`` if the lock could be acquired and ``raise_on_fail`` is ``False``.-heut3//..X
|
|
372
|
+
The method :meth:`cdxcore.filelock.FLock.attempt`
|
|
373
|
+
will return an unacquired context manager in case of a failure.
|
|
374
|
+
|
|
375
|
+
Raises
|
|
376
|
+
------
|
|
377
|
+
Timeout : :class:`TimeoutError`
|
|
378
|
+
Raised if ``acquire`` is ``True``, if ``timeout_seconds > 0`` and ``wait==True``, and if the call failed
|
|
379
|
+
to obtain the file lock.
|
|
380
|
+
|
|
381
|
+
Blocked : :class:`BlockingIOError`
|
|
382
|
+
Raised if ``acquire`` is ``True``, if ``timeout_seconds == 0`` or ``wait==False``, and if the call failed
|
|
383
|
+
to obtain the file lock.
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
timeout_seconds = int(timeout_seconds) if not timeout_seconds is None else None
|
|
387
|
+
timeout_retry = int(timeout_retry) if not timeout_retry is None else None
|
|
388
|
+
assert not self._filename is None, ("self._filename is None. That probably means 'self' was deleted.")
|
|
389
|
+
|
|
390
|
+
if timeout_seconds is None:
|
|
391
|
+
timeout_seconds = 0 if not wait else 1
|
|
392
|
+
else:
|
|
393
|
+
verify( timeout_seconds>=0, "'timeout_seconds' cannot be negative", exception=ValueError)
|
|
394
|
+
verify( not wait or timeout_seconds>0, "Using 'timeout_seconds==0' and 'wait=True' is inconsistent.", exception=ValueError)
|
|
395
|
+
|
|
396
|
+
with self._rlock:
|
|
397
|
+
if not self._fd is None:
|
|
398
|
+
self._cnt += 1
|
|
399
|
+
self._verbose.write("%s: acquire(): raised lock counter to %ld", self._lid, self._cnt)
|
|
400
|
+
return LockContext(self,True)
|
|
401
|
+
assert self._cnt == 0
|
|
402
|
+
self._cnt = 0
|
|
403
|
+
|
|
404
|
+
i = 0
|
|
405
|
+
while True:
|
|
406
|
+
self._verbose.write("\r%s: acquire(): locking [%s]... ", self._lid, _SYSTEM, end='')
|
|
407
|
+
if not _IS_WINDOWS:
|
|
408
|
+
# Linux
|
|
409
|
+
# -----
|
|
410
|
+
# Systemwide Lock (Mutex) using files
|
|
411
|
+
# https://code.activestate.com/recipes/519626-simple-file-based-mutex-for-very-basic-ipc/
|
|
412
|
+
try:
|
|
413
|
+
self._fd = os.open(self._filename, os.O_CREAT|os.O_EXCL|os.O_RDWR)
|
|
414
|
+
os.write(self._fd, bytes("%d" % self._pid, 'utf-8'))
|
|
415
|
+
except OSError as e:
|
|
416
|
+
if not self._fd is None:
|
|
417
|
+
os.close(self._fd)
|
|
418
|
+
self._fd = None
|
|
419
|
+
if e.errno != 17:
|
|
420
|
+
self._verbose.write("failed: %s", str(e), head=False)
|
|
421
|
+
raise e
|
|
422
|
+
else:
|
|
423
|
+
# Windows
|
|
424
|
+
# ------
|
|
425
|
+
secur_att = win32security.SECURITY_ATTRIBUTES()
|
|
426
|
+
secur_att.Initialize()
|
|
427
|
+
try:
|
|
428
|
+
self._fd = win32file.CreateFile( self._filename,
|
|
429
|
+
win32con.GENERIC_READ|win32con.GENERIC_WRITE,
|
|
430
|
+
win32con.FILE_SHARE_READ|win32con.FILE_SHARE_WRITE,
|
|
431
|
+
secur_att,
|
|
432
|
+
win32con.OPEN_ALWAYS,
|
|
433
|
+
win32con.FILE_ATTRIBUTE_NORMAL , 0 )
|
|
434
|
+
|
|
435
|
+
ov=pywintypes.OVERLAPPED() #used to indicate starting region to lock
|
|
436
|
+
win32file.LockFileEx(self._fd,win32con.LOCKFILE_EXCLUSIVE_LOCK|win32con.LOCKFILE_FAIL_IMMEDIATELY,0,_WIN_HIGHBITS,ov)
|
|
437
|
+
except BaseException as e:
|
|
438
|
+
if not self._fd is None:
|
|
439
|
+
self._fd.Close()
|
|
440
|
+
self._fd = None
|
|
441
|
+
if e.winerror not in [17,33]:
|
|
442
|
+
self._verbose.write("failed: %s", str(e), head=False)
|
|
443
|
+
raise e
|
|
444
|
+
|
|
445
|
+
if not self._fd is None:
|
|
446
|
+
# success
|
|
447
|
+
self._cnt = 1
|
|
448
|
+
self._verbose.write("done; lock counter set to 1", head=False)
|
|
449
|
+
return LockContext(self,True)
|
|
450
|
+
|
|
451
|
+
if timeout_seconds <= 0:
|
|
452
|
+
break
|
|
453
|
+
|
|
454
|
+
if not timeout_retry is None:
|
|
455
|
+
i += 1
|
|
456
|
+
if i>timeout_retry:
|
|
457
|
+
break
|
|
458
|
+
self._verbose.write("locked; waiting %s retry %ld/%ld", fmt_seconds(timeout_seconds), i+1, timeout_retry, head=False)
|
|
459
|
+
else:
|
|
460
|
+
self._verbose.write("locked; waiting %s", fmt_seconds(timeout_seconds), head=False)
|
|
461
|
+
|
|
462
|
+
time.sleep(timeout_seconds)
|
|
463
|
+
|
|
464
|
+
if timeout_seconds == 0:
|
|
465
|
+
self._verbose.write("failed.", head=False)
|
|
466
|
+
if raise_on_fail: raise BlockingIOError(self._filename)
|
|
467
|
+
else:
|
|
468
|
+
self._verbose.write("timed out. Cannot access lock.", head=False)
|
|
469
|
+
if raise_on_fail: raise TimeoutError(self._filename, dict(timeout_retry=timeout_retry, timeout_seconds=timeout_seconds))
|
|
470
|
+
return None
|
|
471
|
+
|
|
472
|
+
def attempt(self, wait : bool = True,
|
|
473
|
+
*, timeout_seconds : int = 1,
|
|
474
|
+
timeout_retry : int = 5 ) -> LockContext:
|
|
475
|
+
"""
|
|
476
|
+
Attempt to acquire lock.
|
|
477
|
+
|
|
478
|
+
This function attempts to obtain the file lock within the specified timeout parameters. It will return
|
|
479
|
+
a :class:`cdxcore.filelock.LockContext` whose property
|
|
480
|
+
:attr:`cdxcore.filelock.LockContext.acquired`
|
|
481
|
+
provides success of this attempt.
|
|
482
|
+
|
|
483
|
+
The context object can be used
|
|
484
|
+
using ``with`` as follows:
|
|
485
|
+
|
|
486
|
+
from cdxcore.filelock import FileLock
|
|
487
|
+
from cdxcore.subdir import SubDir
|
|
488
|
+
|
|
489
|
+
lock_dir = SubDir("!/locks",ext="lck")
|
|
490
|
+
lock_name = lock_dir.full_file_name("lock1")
|
|
491
|
+
lock = FileLock(lock_name)
|
|
492
|
+
|
|
493
|
+
with lock.attempt( timeout_second=2, timeout_retry=3 ) as lh:
|
|
494
|
+
if lh.acquired:
|
|
495
|
+
# do locked activity
|
|
496
|
+
else:
|
|
497
|
+
# do some other activity; warn the user; etc
|
|
498
|
+
|
|
499
|
+
# no longer locked
|
|
500
|
+
|
|
501
|
+
In contrast, the function :meth:`cdxcore.filelock.FLock.acquire` will only return a :class:`cdxcore.filelock.LockContext`
|
|
502
|
+
object if the acquisiton of the lock was successful.
|
|
503
|
+
|
|
504
|
+
**One-Shot**
|
|
505
|
+
|
|
506
|
+
If you only make one attempt to use a lock, it is more convenient to use :func:`cdxcore.filelock.AttemptLock`::
|
|
507
|
+
|
|
508
|
+
with AttemptLock( lock_name, timeout_second=2, timeout_retry=3 ) as lock:
|
|
509
|
+
if lock.acquired:
|
|
510
|
+
# do locked activity
|
|
511
|
+
else:
|
|
512
|
+
# do not locked activity
|
|
513
|
+
# no longer locked
|
|
514
|
+
|
|
515
|
+
Parameters
|
|
516
|
+
----------
|
|
517
|
+
wait : bool, default ``True``
|
|
518
|
+
|
|
519
|
+
* If ``False``, return immediately if the lock cannot be acquired.
|
|
520
|
+
* If ``True``, wait with below parameters; in particular if these are left as defaults the lock will wait indefinitely.
|
|
521
|
+
|
|
522
|
+
timeout_seconds : int | None, default ``None``
|
|
523
|
+
Number of seconds to wait before retrying.
|
|
524
|
+
Set to ``0``` to fail immediately.
|
|
525
|
+
If set to ``None``, then behaviour will depend on ``wait``:
|
|
526
|
+
|
|
527
|
+
* If wait is ``True``, then ``timeout_seconds==1``.
|
|
528
|
+
* If wait is ``False``, then ``timeout_seconds==0``.
|
|
529
|
+
|
|
530
|
+
timeout_retry : int | None, default ``None``
|
|
531
|
+
How many times to retry before timing out.
|
|
532
|
+
Set to ``None`` to retry indefinitely.
|
|
533
|
+
|
|
534
|
+
Returns
|
|
535
|
+
-------
|
|
536
|
+
Context : :class:`cdxcore.filelock.LockContext`
|
|
537
|
+
A context representing the acquired state which can be used with ``with``.
|
|
538
|
+
Check :attr:`cdxcore.filelock.LockContext.acquired` to validate whether the lock was acquired successfully.
|
|
539
|
+
|
|
540
|
+
If ``with`` is used and :attr:`cdxcore.filelock.LockContext.acquired` is ``True``,
|
|
541
|
+
then :meth:`cdxcore.filelock.release` is called at the end of the ``with`` statement to
|
|
542
|
+
release the acquired lock.
|
|
543
|
+
"""
|
|
544
|
+
r = self.acquire( wait=wait, timeout_seconds=timeout_seconds, timeout_retry=timeout_retry, raise_on_fail=False )
|
|
545
|
+
return r if not r is None else LockContext(self, False)
|
|
546
|
+
|
|
547
|
+
def release(self, *, force : bool = False ):
|
|
548
|
+
"""
|
|
549
|
+
Release lock.
|
|
550
|
+
|
|
551
|
+
By default this function will only decreased the number of successful acquisitions by one, and will delete the file lock
|
|
552
|
+
only once the number of acquisitions is zero.
|
|
553
|
+
Use ``force`` to force an unlock.
|
|
554
|
+
|
|
555
|
+
Parameters
|
|
556
|
+
----------
|
|
557
|
+
force : bool, default: ``False``
|
|
558
|
+
Whether to close the file regardless of the internal acquisition counter.
|
|
559
|
+
|
|
560
|
+
Returns
|
|
561
|
+
-------
|
|
562
|
+
Remaining : int
|
|
563
|
+
Returns numbner of remaining lock acquisitions; in other words returns 0 if the lock is no longer locked by this process.
|
|
564
|
+
"""
|
|
565
|
+
with self._rlock:
|
|
566
|
+
# we must have a file handle unless 'force' is used.
|
|
567
|
+
if self._fd is None:
|
|
568
|
+
verify( force, lambda : f"Lock '{self._filename}' is not currrenty locked by this process. Use 'force' to avoid this message if need be.")
|
|
569
|
+
self._cnt = 0
|
|
570
|
+
return 0
|
|
571
|
+
|
|
572
|
+
# lower counter
|
|
573
|
+
assert self._cnt > 0, "Internal error - have file handle but counter is zero"
|
|
574
|
+
self._cnt -= 1
|
|
575
|
+
if self._cnt > 0 and not force:
|
|
576
|
+
self._verbose.write("%s: lock counter lowered to %ld", self._lid, self._cnt)
|
|
577
|
+
return self._cnt
|
|
578
|
+
|
|
579
|
+
# remove file
|
|
580
|
+
self._verbose.write("%s: releasing lock [%s]... ", self._lid, _SYSTEM, end='')
|
|
581
|
+
err = ""
|
|
582
|
+
if not _IS_WINDOWS:
|
|
583
|
+
# Linux
|
|
584
|
+
# Locks on Linxu are remarably shaky.
|
|
585
|
+
# In particular, it is possible to remove a locked file.
|
|
586
|
+
try:
|
|
587
|
+
os.close(self._fd)
|
|
588
|
+
except:
|
|
589
|
+
err = f"*** WARNING: could not close lock file '{self._filename}'."
|
|
590
|
+
pass
|
|
591
|
+
try:
|
|
592
|
+
os.remove(self._filename)
|
|
593
|
+
except FileNotFoundError:
|
|
594
|
+
pass
|
|
595
|
+
except:
|
|
596
|
+
err = f"*** WARNING: could not delete lock file '{self._filename}'." if err == "" else err
|
|
597
|
+
else:
|
|
598
|
+
try:
|
|
599
|
+
ov=pywintypes.OVERLAPPED() #used to indicate starting region to lock
|
|
600
|
+
win32file.UnlockFileEx(self._fd,0,_WIN_HIGHBITS,ov)
|
|
601
|
+
except:
|
|
602
|
+
err = "*** WARNING: could not unlock lock file '{self._filename}'."
|
|
603
|
+
pass
|
|
604
|
+
try:
|
|
605
|
+
self._fd.Close()
|
|
606
|
+
except:
|
|
607
|
+
err = "*** WARNING: could not close lock file '{self._filename}'." if err == "" else err
|
|
608
|
+
pass
|
|
609
|
+
try:
|
|
610
|
+
win32file.DeleteFile(self._filename)
|
|
611
|
+
except FileNotFoundError:
|
|
612
|
+
pass
|
|
613
|
+
except:
|
|
614
|
+
err = f"*** WARNING: could not delete lock file '{self._filename}'." if err == "" else err
|
|
615
|
+
pass
|
|
616
|
+
self._verbose.write("done; lock file deleted." if err=="" else err, head=False)
|
|
617
|
+
self._fd = None
|
|
618
|
+
self._cnt = 0
|
|
619
|
+
return 0
|
|
620
|
+
|
|
621
|
+
def FileLock( filename, * ,
|
|
622
|
+
release_on_exit : bool = True,
|
|
623
|
+
verbose : Context|None = None ) -> FLock:
|
|
624
|
+
"""
|
|
625
|
+
Acquire a file lock object shared among threads.
|
|
626
|
+
|
|
627
|
+
This function is useful if a lock is going the be used iteratively, including
|
|
628
|
+
passing it to sub-routines::
|
|
629
|
+
|
|
630
|
+
from cdxcore.filelock import FileLock
|
|
631
|
+
from cdxcore.subdir import SubDir
|
|
632
|
+
|
|
633
|
+
def subroutine( lock ):
|
|
634
|
+
with lock.aquire( lock_name, timeout_second=2, timeout_retry=3 ):
|
|
635
|
+
# do locked activity
|
|
636
|
+
|
|
637
|
+
def mainroutine():
|
|
638
|
+
lock_dir = SubDir("!/locks",ext="lck")
|
|
639
|
+
lock_name = lock_dir.full_file_name("lock1")
|
|
640
|
+
lock = FileLock(lock_name)
|
|
641
|
+
|
|
642
|
+
with lock.aquire( timeout_second=2, timeout_retry=3 ):
|
|
643
|
+
# do locked activity
|
|
644
|
+
|
|
645
|
+
subroutine( lock )
|
|
646
|
+
|
|
647
|
+
If the lock is only used for a one-of acquisition, it is usally
|
|
648
|
+
prettier to use :func:`cdxcore.filelock.AcquireLock` instead.
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
Parameters
|
|
652
|
+
----------
|
|
653
|
+
filename : str
|
|
654
|
+
Filename of the lock.
|
|
655
|
+
|
|
656
|
+
``filename`` may start with ``'!/'`` to refer to the temp directory, or ``'~/'`` to refer to the user directory.
|
|
657
|
+
On Unix ``'/dev/shm/'`` can be used to refer to the standard shared memory directory in case a shared memory
|
|
658
|
+
file is being locked.
|
|
659
|
+
|
|
660
|
+
release_on_exit : bool, default ``True``
|
|
661
|
+
Whether to auto-release the lock upon exit.
|
|
662
|
+
|
|
663
|
+
verbose : :class:`cdxcore.verbose.Context` | None, default ``None``
|
|
664
|
+
Context which will print out operating information of the lock. This is helpful for debugging.
|
|
665
|
+
In particular, it will track ``__del__()`` function calls.
|
|
666
|
+
Set to ``None`` to supress printing any context.
|
|
667
|
+
|
|
668
|
+
Returns
|
|
669
|
+
-------
|
|
670
|
+
lock : :class:`cdxcore.filelock.FLock`
|
|
671
|
+
The lock. This function will re-use an existing lock if it has been created elsewhere by the same process.
|
|
672
|
+
"""
|
|
673
|
+
return FLock._create( filename=filename,
|
|
674
|
+
release_on_exit=release_on_exit,
|
|
675
|
+
verbose=verbose )
|
|
676
|
+
|
|
677
|
+
def AttemptLock(filename, * ,
|
|
678
|
+
wait : bool = True,
|
|
679
|
+
timeout_seconds : int|None = None,
|
|
680
|
+
timeout_retry : int|None = None,
|
|
681
|
+
verbose : Context|None = None ) -> FLock:
|
|
682
|
+
"""
|
|
683
|
+
Attempt to acquire a file lock and return a context handler even if the lock was not acquired.
|
|
684
|
+
The context handler's :attr:`cdxcore.filelock.LockContext.acquired` can be used to assess
|
|
685
|
+
whether the lock was acquired.
|
|
686
|
+
|
|
687
|
+
The pattern is as follows::
|
|
688
|
+
|
|
689
|
+
from cdxcore.filelock import FileLock
|
|
690
|
+
from cdxcore.subdir import SubDir
|
|
691
|
+
|
|
692
|
+
lock_dir = SubDir("!/locks",ext="lck")
|
|
693
|
+
lock_name = lock_dir.full_file_name("lock1")
|
|
694
|
+
|
|
695
|
+
with AttemptLock( lock_name, timeout_second=2, timeout_retry=3 ) as lock:
|
|
696
|
+
if lock.acquired:
|
|
697
|
+
# do locked activity
|
|
698
|
+
else:
|
|
699
|
+
# do not locked activity
|
|
700
|
+
# no longer locked
|
|
701
|
+
|
|
702
|
+
Parameters
|
|
703
|
+
----------
|
|
704
|
+
filename : str
|
|
705
|
+
Filename of the lock.
|
|
706
|
+
|
|
707
|
+
``filename`` may start with ``'!/'`` to refer to the temp directory, or ``'~/'`` to refer to the user directory.
|
|
708
|
+
On Unix ``'/dev/shm/'`` can be used to refer to the standard shared memory directory in case a shared memory
|
|
709
|
+
file is being locked.
|
|
710
|
+
|
|
711
|
+
wait : bool, default ``True``
|
|
712
|
+
|
|
713
|
+
* If ``False``, return immediately if the lock cannot be acquired.
|
|
714
|
+
* If ``True``, wait with below parameters; in particular if these are left as defaults the lock will wait indefinitely.
|
|
715
|
+
|
|
716
|
+
timeout_seconds : int | None, default ``None``
|
|
717
|
+
Number of seconds to wait before retrying.
|
|
718
|
+
Set to ``0``` to fail immediately.
|
|
719
|
+
If set to ``None``, then behaviour will depend on ``wait``:
|
|
720
|
+
|
|
721
|
+
* If wait is ``True``, then ``timeout_seconds==1``.
|
|
722
|
+
* If wait is ``False``, then ``timeout_seconds==0``.
|
|
723
|
+
|
|
724
|
+
timeout_retry : int | None, default ``None``
|
|
725
|
+
How many times to retry before timing out.
|
|
726
|
+
Set to ``None`` to retry indefinitely.
|
|
727
|
+
|
|
728
|
+
verbose : :class:`cdxcore.verbose.Context` | None, default ``None``
|
|
729
|
+
Context which will print out operating information of the lock. This is helpful for debugging.
|
|
730
|
+
In particular, it will track ``__del__()`` function calls.
|
|
731
|
+
Set to ``None`` to supress printing any context.
|
|
732
|
+
|
|
733
|
+
Exceptions
|
|
734
|
+
----------
|
|
735
|
+
Will not raise any exceptions
|
|
736
|
+
|
|
737
|
+
Returns
|
|
738
|
+
-------
|
|
739
|
+
Filelock if acquired or None
|
|
740
|
+
"""
|
|
741
|
+
flock = FLock._create( filename=filename,
|
|
742
|
+
release_on_exit=True,
|
|
743
|
+
verbose=verbose )
|
|
744
|
+
return flock.attempt( wait=wait, timeout_seconds=timeout_seconds, timeout_retry=timeout_retry, raise_on_fail=True )
|
|
745
|
+
|
|
746
|
+
def AcquireLock(filename, * ,
|
|
747
|
+
wait : bool = True,
|
|
748
|
+
timeout_seconds : int|None = None,
|
|
749
|
+
timeout_retry : int|None = None,
|
|
750
|
+
verbose : Context|None = None ) -> LockContext:
|
|
751
|
+
"""
|
|
752
|
+
Acquire a file lock and return a context handler, or raise an exception.
|
|
753
|
+
The context handler can be used in a ``with`` statement as follows::
|
|
754
|
+
|
|
755
|
+
from cdxcore.filelock import FileLock
|
|
756
|
+
from cdxcore.subdir import SubDir
|
|
757
|
+
|
|
758
|
+
lock_dir = SubDir("!/locks",ext="lck")
|
|
759
|
+
lock_name = lock_dir.full_file_name("lock1")
|
|
760
|
+
|
|
761
|
+
with AcquireLock( lock_name, timeout_second=2, timeout_retry=3 ):
|
|
762
|
+
# do locked activity
|
|
763
|
+
# no longer locked
|
|
764
|
+
|
|
765
|
+
Note that this function will raise an exception if the lock could be acquired.
|
|
766
|
+
Use :func:`cdxcore.filelock.AttemptLock` to obtain a context handler
|
|
767
|
+
even if the lock was not acquired.
|
|
768
|
+
|
|
769
|
+
Parameters
|
|
770
|
+
----------
|
|
771
|
+
filename : str
|
|
772
|
+
Filename of the lock.
|
|
773
|
+
|
|
774
|
+
``filename`` may start with ``'!/'`` to refer to the temp directory, or ``'~/'`` to refer to the user directory.
|
|
775
|
+
On Unix ``'/dev/shm/'`` can be used to refer to the standard shared memory directory in case a shared memory
|
|
776
|
+
file is being locked.
|
|
777
|
+
|
|
778
|
+
wait : bool, default ``True``
|
|
779
|
+
|
|
780
|
+
* If ``False``, return immediately if the lock cannot be acquired.
|
|
781
|
+
* If ``True``, wait with below parameters; in particular if these are left as defaults the lock will wait indefinitely.
|
|
782
|
+
|
|
783
|
+
timeout_seconds : int | None, default ``None``
|
|
784
|
+
Number of seconds to wait before retrying.
|
|
785
|
+
Set to ``0``` to fail immediately.
|
|
786
|
+
If set to ``None``, then behaviour will depend on ``wait``:
|
|
787
|
+
|
|
788
|
+
* If wait is ``True``, then ``timeout_seconds==1``.
|
|
789
|
+
* If wait is ``False``, then ``timeout_seconds==0``.
|
|
790
|
+
|
|
791
|
+
timeout_retry : int | None, default ``None``
|
|
792
|
+
How many times to retry before timing out.
|
|
793
|
+
Set to ``None`` to retry indefinitely.
|
|
794
|
+
|
|
795
|
+
verbose : :class:`cdxcore.verbose.Context` | None, default ``None``
|
|
796
|
+
Context which will print out operating information of the lock. This is helpful for debugging.
|
|
797
|
+
In particular, it will track ``__del__()`` function calls.
|
|
798
|
+
Set to ``None`` to supress printing any context.
|
|
799
|
+
|
|
800
|
+
Returns
|
|
801
|
+
-------
|
|
802
|
+
Context : :class:`cdxcore.filelock.LockContext`
|
|
803
|
+
A context representing the acquired state which can be used with ``with``. The function
|
|
804
|
+
:meth:`cdxcore.filelock.release` is called at the end of the ``with`` statement to
|
|
805
|
+
release the acquired lock.
|
|
806
|
+
|
|
807
|
+
Raises
|
|
808
|
+
------
|
|
809
|
+
Timeout : :class:`TimeoutError`
|
|
810
|
+
Raised if ``acquire`` is ``True``, if ``timeout_seconds > 0`` and ``wait==True``, and if the call failed
|
|
811
|
+
to obtain the file lock.
|
|
812
|
+
|
|
813
|
+
Blocked : :class:`BlockingIOError`
|
|
814
|
+
Raised if ``acquire`` is ``True``, if ``timeout_seconds == 0`` or ``wait==False``, and if the call failed
|
|
815
|
+
to obtain the file lock.
|
|
816
|
+
"""
|
|
817
|
+
flock = FLock._create( filename=filename,
|
|
818
|
+
release_on_exit=True,
|
|
819
|
+
verbose=verbose )
|
|
820
|
+
return flock.acquire( wait=wait, timeout_seconds=timeout_seconds, timeout_retry=timeout_retry, raise_on_fail=True )
|
|
821
|
+
|
cdxcore/subdir.py
CHANGED
|
@@ -3390,11 +3390,11 @@ def VersionedCacheRoot( directory : str, *,
|
|
|
3390
3390
|
|
|
3391
3391
|
Returns
|
|
3392
3392
|
-------
|
|
3393
|
-
Root : SubDir
|
|
3393
|
+
Root : :class:`cdxcore.subdir.SubDir`
|
|
3394
3394
|
A root directory suitable for caching.
|
|
3395
3395
|
"""
|
|
3396
3396
|
controller = CacheController(**controller_kwargs) if len(controller_kwargs) > 0 else None
|
|
3397
|
-
return SubDir( directory
|
|
3397
|
+
return SubDir( directory, ext=ext, fmt=fmt, create_directory=create_directory, controller=controller )
|
|
3398
3398
|
|
|
3399
3399
|
version = version_decorator
|
|
3400
3400
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cdxcore
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.21
|
|
4
4
|
Summary: Basic Python Tools; upgraded cdxbasics
|
|
5
5
|
Author-email: Hans Buehler <github@buehler.london>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -49,7 +49,7 @@ Documentation can be found here: <https://quantitative-research.de/docs/cdxcore>
|
|
|
49
49
|
animated updates for graphs (e.g. during training runs), and allows generation of plot layouts without knowing upfront
|
|
50
50
|
the number of plots (e.g. for plotting a list of features).
|
|
51
51
|
|
|
52
|
-

|
|
53
53
|
|
|
54
54
|
- [`cdxcore.config`](https://quantitative-research.de/docs/cdxcore/api/generated/cdxcore.config.html) allows **robust management of configurations**. It automates help, validation checking,
|
|
55
55
|
and detects misspelled configuration arguments.
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
cdxcore/__init__.py,sha256=
|
|
1
|
+
cdxcore/__init__.py,sha256=tTewrG_1bFOYsVSvBReV8lY8tGQSqaDr_rEEDNk-yP0,127
|
|
2
2
|
cdxcore/config.py,sha256=RaxpAnTEyGXLiq-1O2prXFBb2sdyqB6hHDHPAFGe2k0,98472
|
|
3
3
|
cdxcore/deferred.py,sha256=5bREI2IQ3uW3EMDcAO17gJbVnbUIV__-bdEUQpZIEeU,44684
|
|
4
4
|
cdxcore/dynalimits.py,sha256=TXEwa4wCdeM5epG1ceezaHwvmhnbU-oXlXpHNbOMQR8,11293
|
|
5
5
|
cdxcore/dynaplot.py,sha256=1g4IFTfUir13BEDNxDXxUvlwh__kq5n0kgehZ-e8ah4,51031
|
|
6
6
|
cdxcore/err.py,sha256=3JP1IZDMIKayTt-ZNV9PCYdjtvkybQTjZOrRmBZWyfg,14709
|
|
7
|
+
cdxcore/filelock.py,sha256=D2U8rzxYkeuDc8RLRbePBQ939PPxcJuRC8sahmVx-x8,34781
|
|
7
8
|
cdxcore/jcpool.py,sha256=Vw8o8S4_qTVQNIr2sRaBR_kHz_wO0zpYs0QjlKSYFz8,27280
|
|
8
9
|
cdxcore/npio.py,sha256=SVpKkFt6lyRogkns-oky0wEiWgVTalgB0OdaSvyWtlU,24212
|
|
9
10
|
cdxcore/npshm.py,sha256=9buYPNJoNlw69NZQ-nLF13PEWBWNx51a0gjQw5Gc24U,18368
|
|
10
11
|
cdxcore/pretty.py,sha256=FsI62rlaqRX1E-uPCSnu0M4UoCQ5Z55TDvYPnzTNO70,17220
|
|
11
|
-
cdxcore/subdir.py,sha256=
|
|
12
|
+
cdxcore/subdir.py,sha256=tSocug4h5iV2mTYOMojg2IXSwAenvYZt7cRdEXTG6UU,176894
|
|
12
13
|
cdxcore/uniquehash.py,sha256=0aMJXxGzA6xLyuaygRZF2Z-1hwznrvqSZQM7K1bycTQ,50705
|
|
13
14
|
cdxcore/util.py,sha256=dqCEhrDvUM4qjeUwb6n8jPfS8WC4Navcj83gDjXfRFE,39349
|
|
14
15
|
cdxcore/verbose.py,sha256=vsjGTVnAHMPg2L2RfsowWKKPjUSnQJ3F653vDTydBkI,30223
|
|
15
16
|
cdxcore/version.py,sha256=S3H0ktEZsM0zAj3EQ5AdrZ2KxWhQtemd8clzEFE9hOQ,27160
|
|
16
|
-
cdxcore-0.1.
|
|
17
|
+
cdxcore-0.1.21.dist-info/licenses/LICENSE,sha256=M-cisgK9kb1bqVRJ7vrCxHcMQQfDxdY3c2YFJJWfNQg,1090
|
|
17
18
|
docs/source/conf.py,sha256=yn3LYgw3sT45mUyll-B2emVp6jg7H6KfAHOcBg_MNv4,4182
|
|
18
19
|
tests/test_config.py,sha256=N86mH3y7k3LXEmU8uPLfrmRMZ-80VhlD35nBbpLmebg,15617
|
|
19
20
|
tests/test_deferred.py,sha256=4Xsb76r-XqHKiBuHa4jbErjMWbrgHXfPwewzzY4lf9Y,7922
|
|
@@ -27,13 +28,13 @@ tests/test_uniquehash.py,sha256=n6ZCkdBw-iRsyzeAEmrnLK0hJLGH6l_Dtt_KIkSa6KA,2463
|
|
|
27
28
|
tests/test_util.py,sha256=mwtz3o_RiLNn808928xP4jawHmGNxzUXiatMh0zBc3o,24342
|
|
28
29
|
tests/test_verbose.py,sha256=zXheIqAVOnwML2zsCjLugjYzB_KNzU_S4Xu2CSb4o10,4723
|
|
29
30
|
tests/test_version.py,sha256=m_RODPDFlXTC1jAIczm3t1v6tXzqczDiUFFJtGvRG98,4381
|
|
30
|
-
tmp/filelock.py,sha256=
|
|
31
|
+
tmp/filelock.py,sha256=MhLcCT4iRbNfvL6LJz_yIfUrnEnReKwk5A6VzbilcTs,21189
|
|
31
32
|
tmp/np.py,sha256=2MynhiaTfmx984Gz7TwfZH3t7GCmCAQiyeWzDDCL6_k,47686
|
|
32
33
|
tmp/npsh1.py,sha256=mNucUl2-jNmE84GlMlliB4aJ0UQ9FqdymgcY_9mLeZY,15432
|
|
33
34
|
tmp/sharedarray.py,sha256=dNOT1ObCc3nM3qA3OA508NcENIBnkmWMxRPCqvMVa8A,12862
|
|
34
35
|
up/git_message.py,sha256=EfSH7Pit3ZoCiRqSMwRCUN_QyuwreU4LTIyGSutBlm4,123
|
|
35
36
|
up/pip_modify_setup.py,sha256=Esaml4yA9tFsqxLhk5bWSwvKCURONjQqfyChgFV2TSY,1584
|
|
36
|
-
cdxcore-0.1.
|
|
37
|
-
cdxcore-0.1.
|
|
38
|
-
cdxcore-0.1.
|
|
39
|
-
cdxcore-0.1.
|
|
37
|
+
cdxcore-0.1.21.dist-info/METADATA,sha256=CbOmczjod2520L0-DwaavH20Dm2tJzaAOiz-mYlvZRs,5939
|
|
38
|
+
cdxcore-0.1.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
39
|
+
cdxcore-0.1.21.dist-info/top_level.txt,sha256=phNSwCyJFe7UP2YMoi8o6ykhotatlIbJHjTp9EHM51k,26
|
|
40
|
+
cdxcore-0.1.21.dist-info/RECORD,,
|
tmp/filelock.py
CHANGED
|
@@ -1,9 +1,70 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
1
2
|
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Simple filelock for both Windows and Linux.
|
|
4
|
+
|
|
5
|
+
The class :class:`cdxcore.filelock.FileLock` is for common file lock patterns for
|
|
6
|
+
`Linux <https://code.activestate.com/recipes/519626-simple-file-based-mutex-for-very-basic-ipc/>`__ and
|
|
7
|
+
`Windows <https://timgolden.me.uk/pywin32-docs/Windows_NT_Files_.2d.2d_Locking.html>`__.
|
|
8
|
+
|
|
9
|
+
Overview
|
|
10
|
+
--------
|
|
11
|
+
|
|
12
|
+
The main use of this module are the functions
|
|
13
|
+
:func:`cdxcore.err.verify` and :func:`cdxcore.err.warn_if`.
|
|
14
|
+
Both test some runtime condition and will either
|
|
15
|
+
raise an ``Exception`` or issue a ``Warning`` if triggered. In both cases, required string formatting is only performed
|
|
16
|
+
if the event is actually triggered.
|
|
17
|
+
|
|
18
|
+
This way we are able to write neat code which produces robust,
|
|
19
|
+
informative errors and warnings without impeding runtime performance.
|
|
20
|
+
|
|
21
|
+
Example::
|
|
22
|
+
|
|
23
|
+
from cdxcore.err import verify, warn_if
|
|
24
|
+
import numpy as np
|
|
25
|
+
|
|
26
|
+
def f( x : np.ndarray ):
|
|
27
|
+
std = np.std(x,axis=0,keepdims=True)
|
|
28
|
+
verify( np.all( std>1E-8 ), "Cannot normalize 'x' by standard deviation: standard deviations are {std}", std=std )
|
|
29
|
+
x /= std
|
|
30
|
+
|
|
31
|
+
f( np.zeros((10,10)) )
|
|
32
|
+
|
|
33
|
+
raises a :class:`RuntimeError`
|
|
34
|
+
|
|
35
|
+
.. code-block:: python
|
|
36
|
+
|
|
37
|
+
Cannot normalize 'x' by standard deviation: standard deviations are [[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
|
|
38
|
+
|
|
39
|
+
For warnings, we can use ``warn_if``::
|
|
40
|
+
|
|
41
|
+
from cdxcore.err import verify, warn_if
|
|
42
|
+
import numpy as np
|
|
43
|
+
|
|
44
|
+
def f( x : np.ndarray ):
|
|
45
|
+
std = np.std(x,axis=0,keepdims=True)
|
|
46
|
+
warn_if( not np.all( std>1E-8 ), lambda : f"Normalizing 'x' by standard deviation: standard deviations are {std}" )
|
|
47
|
+
x = np.where( std<1E-8, 0., x/np.where( std<1E-8, 1., std ) )
|
|
48
|
+
|
|
49
|
+
f( np.zeros((10,10)) )
|
|
50
|
+
|
|
51
|
+
issues a warning::
|
|
52
|
+
|
|
53
|
+
RuntimeWarning: Normalizing 'x' by standard deviation: standard deviations are [[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
|
|
54
|
+
warn_if( not np.all( std>1E-8 ), "Normalizing 'x' by standard deviation: standard deviations are {std}", std=std )
|
|
6
55
|
|
|
56
|
+
Note that though we used two different approaches for message formatting, the the error messages in both cases
|
|
57
|
+
are only formatted if the condition in ``verify`` is not met.
|
|
58
|
+
|
|
59
|
+
Import
|
|
60
|
+
------
|
|
61
|
+
.. code-block:: python
|
|
62
|
+
|
|
63
|
+
from cdxcore.err import verify, warn_if, error, warn
|
|
64
|
+
|
|
65
|
+
Documentation
|
|
66
|
+
-------------
|
|
67
|
+
"""
|
|
7
68
|
from .logger import Logger
|
|
8
69
|
from .verbose import Context
|
|
9
70
|
from .util import datetime, fmt_datetime, fmt_seconds
|
|
@@ -29,7 +90,7 @@ if IS_WINDOWS:
|
|
|
29
90
|
import pywintypes
|
|
30
91
|
import win32security
|
|
31
92
|
import win32api
|
|
32
|
-
|
|
93
|
+
_WIN_HIGHBITS=0xffff0000 #high-order 32 bits of byte range to lock
|
|
33
94
|
|
|
34
95
|
else:
|
|
35
96
|
win32file = None
|
|
@@ -37,9 +98,67 @@ else:
|
|
|
37
98
|
import os
|
|
38
99
|
|
|
39
100
|
class FileLock(object):
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
|
|
101
|
+
r"""
|
|
102
|
+
System-wide file lock.
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
filename : str
|
|
110
|
+
Filename of the lock.
|
|
111
|
+
``filename`` may start with ``'!/'`` to refer to the temp directory, or ``'~/'`` to refer to the user directory.
|
|
112
|
+
On Unix ``'/dev/shm/'`` can be used to refer to the standard shared memory directory in case a shared memory
|
|
113
|
+
file is being locked.
|
|
114
|
+
|
|
115
|
+
acquire : bool, default: ``False``
|
|
116
|
+
Whether to attempt aquiring the lock upon initialization.
|
|
117
|
+
|
|
118
|
+
release_on_exit : bool, default ``True``
|
|
119
|
+
Whether to auto-release the lock upon exit.
|
|
120
|
+
|
|
121
|
+
wait : bool, default ``True``
|
|
122
|
+
* If ``False``, return immediately if the lock cannot be acquired.
|
|
123
|
+
* If ``True``, wait with below parameters; in particular if these are left as defaults the lock will wait indefinitely.
|
|
124
|
+
|
|
125
|
+
timeout_seconds : int | None, default ``None``
|
|
126
|
+
Number of seconds to wait before retrying.
|
|
127
|
+
Set to ``0``` to fail immediately.
|
|
128
|
+
If set to ``None``, then behaviour will depend on ``wait``:
|
|
129
|
+
|
|
130
|
+
* If wait is ``True``, then ``timeout_seconds==1``;
|
|
131
|
+
* If wait is ``False``, then ``timeout_seconds==0``.
|
|
132
|
+
|
|
133
|
+
timeout_retry : int|None, default ``None``
|
|
134
|
+
How many times to retry before timing out.
|
|
135
|
+
Set to ``None`` to retry indefinitely.
|
|
136
|
+
|
|
137
|
+
raise_on_fail : bool, default ``True``
|
|
138
|
+
If the constructor fails to obtain the lock, raise an exception.
|
|
139
|
+
This will be either of type
|
|
140
|
+
|
|
141
|
+
* :class:`TimeoutError` if ``timeout_seconds > 0`` and ``wait==True``, or
|
|
142
|
+
* :class:`BlockingIOError` if ``timeout_seconds == 0`` or ``wait==False``.
|
|
143
|
+
|
|
144
|
+
verbose : :class:`cdxcore.verbose.Context`|None, default ``None``
|
|
145
|
+
Context which will print out operating information of the lock. This is helpful for debugging.
|
|
146
|
+
In particular, it will track ``__del__()`` function calls.
|
|
147
|
+
Set to ``None`` to supress printing any context.
|
|
148
|
+
|
|
149
|
+
Exceptions
|
|
150
|
+
----------
|
|
151
|
+
Timeout : :class:`TimeoutError`
|
|
152
|
+
Raised if ``acquire`` is ``True``, if ``timeout_seconds > 0`` and ``wait==True``, and if the call failed
|
|
153
|
+
to obtain the file lock.
|
|
154
|
+
|
|
155
|
+
Blocked : :class:`BlockingIOError`
|
|
156
|
+
Raised if ``acquire`` is ``True``, if ``timeout_seconds == 0`` or ``wait==False``, and if the call failed
|
|
157
|
+
to obtain the file lock.
|
|
158
|
+
|
|
159
|
+
If acquire is True, then this constructor may raise an exception:
|
|
160
|
+
TimeoutError if 'timeout_seconds' > 0 and 'wait' is True, or
|
|
161
|
+
BlockingIOError if 'timeout_seconds' == 0 or 'wait' is False.
|
|
43
162
|
"""
|
|
44
163
|
|
|
45
164
|
__LOCK_ID = 0
|
|
@@ -48,57 +167,19 @@ class FileLock(object):
|
|
|
48
167
|
acquire : bool = False,
|
|
49
168
|
release_on_exit : bool = True,
|
|
50
169
|
wait : bool = True,
|
|
51
|
-
timeout_seconds : int = None,
|
|
52
|
-
timeout_retry : int = None,
|
|
170
|
+
timeout_seconds : int|None = None,
|
|
171
|
+
timeout_retry : int|None = None,
|
|
53
172
|
raise_on_fail : bool = True,
|
|
54
|
-
verbose : Context =
|
|
173
|
+
verbose : Context|None = None ):
|
|
55
174
|
"""
|
|
56
|
-
|
|
57
|
-
Acquire the lock if 'acquire' is True
|
|
58
|
-
|
|
59
|
-
Parameters
|
|
60
|
-
----------
|
|
61
|
-
filename :
|
|
62
|
-
Filename of the lock.
|
|
63
|
-
'filename' may start with '!/' to refer to the temp directory, or '~/' to refer to the user directory.
|
|
64
|
-
On Unix /dev/shm/ can be used to refer to shared memory.
|
|
65
|
-
acquire :
|
|
66
|
-
Whether to attempt aquiring the lock upon initialization
|
|
67
|
-
release_on_exit :
|
|
68
|
-
Whether to auto-release the lock upon exit.
|
|
69
|
-
wait :
|
|
70
|
-
If False, return immediately if the lock cannot be acquired.
|
|
71
|
-
If True, wait with below parameters; in particular if these are left as defaults the lock will wait indefinitely.
|
|
72
|
-
timeout_seconds :
|
|
73
|
-
Number of seconds to wait before retrying.
|
|
74
|
-
Set to 0 to fail immediately.
|
|
75
|
-
If set to None, then its value will depend on 'wait'.
|
|
76
|
-
If wait is True, then timeout_seconds==1; if wait is False, then timeout_seconds==0
|
|
77
|
-
timeout_retry :
|
|
78
|
-
How many times to retry before timing out.
|
|
79
|
-
Set to None to retry indefinitely.
|
|
80
|
-
raise_on_fail :
|
|
81
|
-
If the constructor fails to obtain the lock, raise an Exception:
|
|
82
|
-
This will be either of type
|
|
83
|
-
TimeoutError if 'timeout_seconds' > 0 and 'wait' is True, or
|
|
84
|
-
BlockingIOError if 'timeout_seconds' == 0 or 'wait' is False.
|
|
85
|
-
verbose :
|
|
86
|
-
Context which will print out operating information of the lock. This is helpful for debugging.
|
|
87
|
-
In particular, it will track __del__() function calls.
|
|
88
|
-
Set to None to print all context.
|
|
89
|
-
|
|
90
|
-
Exceptions
|
|
91
|
-
----------
|
|
92
|
-
If acquire is True, then this constructor may raise an exception:
|
|
93
|
-
TimeoutError if 'timeout_seconds' > 0 and 'wait' is True, or
|
|
94
|
-
BlockingIOError if 'timeout_seconds' == 0 or 'wait' is False.
|
|
175
|
+
__init__
|
|
95
176
|
"""
|
|
96
177
|
self._filename = SubDir.expandStandardRoot(filename)
|
|
97
178
|
self._fd = None
|
|
98
179
|
self._pid = os.getpid()
|
|
99
180
|
self._cnt = 0
|
|
100
181
|
self._lid = "LOCK" + fmt_datetime(datetime.datetime.now()) + (",%03ld:" % FileLock.__LOCK_ID) + filename
|
|
101
|
-
self.verbose = verbose if not verbose is None else Context
|
|
182
|
+
self.verbose = verbose if not verbose is None else Context.quiet
|
|
102
183
|
self.release_on_exit = release_on_exit
|
|
103
184
|
FileLock.__LOCK_ID +=1
|
|
104
185
|
|
|
@@ -133,7 +214,7 @@ class FileLock(object):
|
|
|
133
214
|
""" Return filename """
|
|
134
215
|
return self._filename
|
|
135
216
|
|
|
136
|
-
def acquire(self, wait = True,
|
|
217
|
+
def acquire(self, wait : bool = True,
|
|
137
218
|
*, timeout_seconds : int = 1,
|
|
138
219
|
timeout_retry : int = 5,
|
|
139
220
|
raise_on_fail : bool = True) -> int:
|
|
@@ -142,9 +223,10 @@ class FileLock(object):
|
|
|
142
223
|
|
|
143
224
|
Parameters
|
|
144
225
|
----------
|
|
145
|
-
wait :
|
|
146
|
-
If False
|
|
226
|
+
wait : bool, default ``True``
|
|
227
|
+
If ``False``, return immediately if the lock cannot be acquired.
|
|
147
228
|
If True, wait with below parameters
|
|
229
|
+
|
|
148
230
|
timeout_seconds :
|
|
149
231
|
Number of seconds to wait before retrying. If wait is False, this must be zero.
|
|
150
232
|
Set to 0 to fail immediately.
|
|
@@ -188,6 +270,8 @@ class FileLock(object):
|
|
|
188
270
|
if not IS_WINDOWS:
|
|
189
271
|
# Linux
|
|
190
272
|
# -----
|
|
273
|
+
# Systemwide Lock (Mutex) using files
|
|
274
|
+
# https://code.activestate.com/recipes/519626-simple-file-based-mutex-for-very-basic-ipc/
|
|
191
275
|
try:
|
|
192
276
|
self._fd = os.open(self._filename, os.O_CREAT|os.O_EXCL|os.O_RDWR)
|
|
193
277
|
os.write(self._fd, bytes("%d" % self._pid, 'utf-8'))
|
|
@@ -212,7 +296,7 @@ class FileLock(object):
|
|
|
212
296
|
win32con.FILE_ATTRIBUTE_NORMAL , 0 )
|
|
213
297
|
|
|
214
298
|
ov=pywintypes.OVERLAPPED() #used to indicate starting region to lock
|
|
215
|
-
win32file.LockFileEx(self._fd,win32con.LOCKFILE_EXCLUSIVE_LOCK|win32con.LOCKFILE_FAIL_IMMEDIATELY,0,
|
|
299
|
+
win32file.LockFileEx(self._fd,win32con.LOCKFILE_EXCLUSIVE_LOCK|win32con.LOCKFILE_FAIL_IMMEDIATELY,0,_WIN_HIGHBITS,ov)
|
|
216
300
|
except BaseException as e:
|
|
217
301
|
if not self._fd is None:
|
|
218
302
|
self._fd.Close()
|
|
@@ -291,7 +375,7 @@ class FileLock(object):
|
|
|
291
375
|
else:
|
|
292
376
|
try:
|
|
293
377
|
ov=pywintypes.OVERLAPPED() #used to indicate starting region to lock
|
|
294
|
-
win32file.UnlockFileEx(self._fd,0,
|
|
378
|
+
win32file.UnlockFileEx(self._fd,0,_WIN_HIGHBITS,ov)
|
|
295
379
|
except:
|
|
296
380
|
err = "*** WARNING: could not unlock file."
|
|
297
381
|
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|