locklib 0.0.23__tar.gz → 0.0.24__tar.gz
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.
- {locklib-0.0.23 → locklib-0.0.24}/PKG-INFO +29 -4
- {locklib-0.0.23 → locklib-0.0.24}/README.md +28 -3
- {locklib-0.0.23 → locklib-0.0.24}/locklib/__init__.py +1 -0
- locklib-0.0.23/locklib/locks/smart_lock/lock.py → locklib-0.0.24/locklib/locks/smart_lock/abstract.py +22 -3
- locklib-0.0.24/locklib/locks/smart_lock/lock.py +5 -0
- locklib-0.0.24/locklib/locks/smart_lock/rlock.py +5 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib.egg-info/PKG-INFO +29 -4
- {locklib-0.0.23 → locklib-0.0.24}/locklib.egg-info/SOURCES.txt +2 -0
- {locklib-0.0.23 → locklib-0.0.24}/pyproject.toml +1 -1
- {locklib-0.0.23 → locklib-0.0.24}/LICENSE +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/errors.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/__init__.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/empty/__init__.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/empty/async_empty_lock.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/empty/empty_lock.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/smart_lock/__init__.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/smart_lock/graph.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/tracer/__init__.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/tracer/events.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/tracer/tracer.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/protocols/__init__.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/protocols/async_context_lock.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/protocols/context_lock.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/protocols/lock.py +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib/py.typed +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib.egg-info/dependency_links.txt +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/locklib.egg-info/top_level.txt +0 -0
- {locklib-0.0.23 → locklib-0.0.24}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: locklib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.24
|
|
4
4
|
Summary: When there are not enough locks from the standard library
|
|
5
5
|
Author-email: Evgeniy Blinov <zheni-b@yandex.ru>
|
|
6
6
|
Project-URL: Source, https://github.com/mutating/locklib
|
|
@@ -100,13 +100,14 @@ from multiprocessing import Lock as MLock
|
|
|
100
100
|
from threading import Lock as TLock, RLock as TRLock
|
|
101
101
|
from asyncio import Lock as ALock
|
|
102
102
|
|
|
103
|
-
from locklib import SmartLock, LockProtocol
|
|
103
|
+
from locklib import SmartLock, SmartRLock, LockProtocol
|
|
104
104
|
|
|
105
105
|
print(isinstance(MLock(), LockProtocol)) # True
|
|
106
106
|
print(isinstance(TLock(), LockProtocol)) # True
|
|
107
107
|
print(isinstance(TRLock(), LockProtocol)) # True
|
|
108
108
|
print(isinstance(ALock(), LockProtocol)) # True
|
|
109
109
|
print(isinstance(SmartLock(), LockProtocol)) # True
|
|
110
|
+
print(isinstance(SmartRLock(), LockProtocol)) # True
|
|
110
111
|
```
|
|
111
112
|
|
|
112
113
|
However, most idiomatic Python code uses locks as context managers. If your code does too, you can use one of the two protocols derived from the base `LockProtocol`: `ContextLockProtocol` or `AsyncContextLockProtocol`. Thus, the protocol hierarchy looks like this:
|
|
@@ -119,18 +120,19 @@ LockProtocol
|
|
|
119
120
|
|
|
120
121
|
`ContextLockProtocol` describes objects that satisfy `LockProtocol` and also implement the [context manager protocol](https://docs.python.org/3/library/stdtypes.html#typecontextmanager). Similarly,`AsyncContextLockProtocol` describes objects that satisfy `LockProtocol` and implement the [asynchronous context manager](https://docs.python.org/3/reference/datamodel.html#async-context-managers) protocol.
|
|
121
122
|
|
|
122
|
-
Almost all standard library locks, as well as `SmartLock`, satisfy `ContextLockProtocol`:
|
|
123
|
+
Almost all standard library locks, as well as `SmartLock` and `SmartRLock`, satisfy `ContextLockProtocol`:
|
|
123
124
|
|
|
124
125
|
```python
|
|
125
126
|
from multiprocessing import Lock as MLock
|
|
126
127
|
from threading import Lock as TLock, RLock as TRLock
|
|
127
128
|
|
|
128
|
-
from locklib import SmartLock, ContextLockProtocol
|
|
129
|
+
from locklib import SmartLock, SmartRLock, ContextLockProtocol
|
|
129
130
|
|
|
130
131
|
print(isinstance(MLock(), ContextLockProtocol)) # True
|
|
131
132
|
print(isinstance(TLock(), ContextLockProtocol)) # True
|
|
132
133
|
print(isinstance(TRLock(), ContextLockProtocol)) # True
|
|
133
134
|
print(isinstance(SmartLock(), ContextLockProtocol)) # True
|
|
135
|
+
print(isinstance(SmartRLock(), ContextLockProtocol)) # True
|
|
134
136
|
```
|
|
135
137
|
|
|
136
138
|
However, the [`asyncio.Lock`](https://docs.python.org/3/library/asyncio-sync.html#asyncio.Lock) belongs to a separate category and `AsyncContextLockProtocol` is needed to describe it:
|
|
@@ -239,6 +241,29 @@ If you want to catch this exception, you can also import it from `locklib`:
|
|
|
239
241
|
from locklib import DeadLockError
|
|
240
242
|
```
|
|
241
243
|
|
|
244
|
+
`SmartLock` is deliberately not recursive: acquiring the same instance twice from the same thread raises `DeadLockError`. When recursive ownership is needed, use `SmartRLock` instead. It keeps the same deadlock detection for waits between threads, but lets the owning thread enter the same lock repeatedly:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from locklib import SmartRLock
|
|
248
|
+
|
|
249
|
+
lock = SmartRLock()
|
|
250
|
+
|
|
251
|
+
with lock, lock:
|
|
252
|
+
pass
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
If you use explicit `acquire()` and `release()` calls, every successful acquire must have a matching release:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from locklib import SmartRLock
|
|
259
|
+
|
|
260
|
+
lock = SmartRLock()
|
|
261
|
+
|
|
262
|
+
lock.acquire()
|
|
263
|
+
lock.acquire()
|
|
264
|
+
lock.release()
|
|
265
|
+
lock.release()
|
|
266
|
+
```
|
|
242
267
|
|
|
243
268
|
## Test your locks
|
|
244
269
|
|
|
@@ -70,13 +70,14 @@ from multiprocessing import Lock as MLock
|
|
|
70
70
|
from threading import Lock as TLock, RLock as TRLock
|
|
71
71
|
from asyncio import Lock as ALock
|
|
72
72
|
|
|
73
|
-
from locklib import SmartLock, LockProtocol
|
|
73
|
+
from locklib import SmartLock, SmartRLock, LockProtocol
|
|
74
74
|
|
|
75
75
|
print(isinstance(MLock(), LockProtocol)) # True
|
|
76
76
|
print(isinstance(TLock(), LockProtocol)) # True
|
|
77
77
|
print(isinstance(TRLock(), LockProtocol)) # True
|
|
78
78
|
print(isinstance(ALock(), LockProtocol)) # True
|
|
79
79
|
print(isinstance(SmartLock(), LockProtocol)) # True
|
|
80
|
+
print(isinstance(SmartRLock(), LockProtocol)) # True
|
|
80
81
|
```
|
|
81
82
|
|
|
82
83
|
However, most idiomatic Python code uses locks as context managers. If your code does too, you can use one of the two protocols derived from the base `LockProtocol`: `ContextLockProtocol` or `AsyncContextLockProtocol`. Thus, the protocol hierarchy looks like this:
|
|
@@ -89,18 +90,19 @@ LockProtocol
|
|
|
89
90
|
|
|
90
91
|
`ContextLockProtocol` describes objects that satisfy `LockProtocol` and also implement the [context manager protocol](https://docs.python.org/3/library/stdtypes.html#typecontextmanager). Similarly,`AsyncContextLockProtocol` describes objects that satisfy `LockProtocol` and implement the [asynchronous context manager](https://docs.python.org/3/reference/datamodel.html#async-context-managers) protocol.
|
|
91
92
|
|
|
92
|
-
Almost all standard library locks, as well as `SmartLock`, satisfy `ContextLockProtocol`:
|
|
93
|
+
Almost all standard library locks, as well as `SmartLock` and `SmartRLock`, satisfy `ContextLockProtocol`:
|
|
93
94
|
|
|
94
95
|
```python
|
|
95
96
|
from multiprocessing import Lock as MLock
|
|
96
97
|
from threading import Lock as TLock, RLock as TRLock
|
|
97
98
|
|
|
98
|
-
from locklib import SmartLock, ContextLockProtocol
|
|
99
|
+
from locklib import SmartLock, SmartRLock, ContextLockProtocol
|
|
99
100
|
|
|
100
101
|
print(isinstance(MLock(), ContextLockProtocol)) # True
|
|
101
102
|
print(isinstance(TLock(), ContextLockProtocol)) # True
|
|
102
103
|
print(isinstance(TRLock(), ContextLockProtocol)) # True
|
|
103
104
|
print(isinstance(SmartLock(), ContextLockProtocol)) # True
|
|
105
|
+
print(isinstance(SmartRLock(), ContextLockProtocol)) # True
|
|
104
106
|
```
|
|
105
107
|
|
|
106
108
|
However, the [`asyncio.Lock`](https://docs.python.org/3/library/asyncio-sync.html#asyncio.Lock) belongs to a separate category and `AsyncContextLockProtocol` is needed to describe it:
|
|
@@ -209,6 +211,29 @@ If you want to catch this exception, you can also import it from `locklib`:
|
|
|
209
211
|
from locklib import DeadLockError
|
|
210
212
|
```
|
|
211
213
|
|
|
214
|
+
`SmartLock` is deliberately not recursive: acquiring the same instance twice from the same thread raises `DeadLockError`. When recursive ownership is needed, use `SmartRLock` instead. It keeps the same deadlock detection for waits between threads, but lets the owning thread enter the same lock repeatedly:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from locklib import SmartRLock
|
|
218
|
+
|
|
219
|
+
lock = SmartRLock()
|
|
220
|
+
|
|
221
|
+
with lock, lock:
|
|
222
|
+
pass
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
If you use explicit `acquire()` and `release()` calls, every successful acquire must have a matching release:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
from locklib import SmartRLock
|
|
229
|
+
|
|
230
|
+
lock = SmartRLock()
|
|
231
|
+
|
|
232
|
+
lock.acquire()
|
|
233
|
+
lock.acquire()
|
|
234
|
+
lock.release()
|
|
235
|
+
lock.release()
|
|
236
|
+
```
|
|
212
237
|
|
|
213
238
|
## Test your locks
|
|
214
239
|
|
|
@@ -10,6 +10,7 @@ from locklib.locks.empty.async_empty_lock import (
|
|
|
10
10
|
)
|
|
11
11
|
from locklib.locks.empty.empty_lock import EmptyLock as EmptyLock
|
|
12
12
|
from locklib.locks.smart_lock.lock import SmartLock as SmartLock
|
|
13
|
+
from locklib.locks.smart_lock.rlock import SmartRLock as SmartRLock
|
|
13
14
|
from locklib.locks.tracer.tracer import (
|
|
14
15
|
LockTraceWrapper as LockTraceWrapper,
|
|
15
16
|
)
|
|
@@ -7,20 +7,26 @@ except ImportError: # pragma: no cover
|
|
|
7
7
|
from threading import Lock # get_native_id is available only since python 3.8
|
|
8
8
|
from threading import get_ident as get_native_id
|
|
9
9
|
|
|
10
|
+
from abc import ABC
|
|
10
11
|
from collections import deque
|
|
11
12
|
from types import TracebackType
|
|
12
|
-
from typing import Deque, Dict, Optional, Type
|
|
13
|
+
from typing import ClassVar, Deque, Dict, Optional, Type
|
|
13
14
|
|
|
15
|
+
from locklib.errors import DeadLockError
|
|
14
16
|
from locklib.locks.smart_lock.graph import LocksGraph
|
|
15
17
|
|
|
16
18
|
graph = LocksGraph()
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
|
|
21
|
+
class AbstractSmartLock(ABC): # noqa: B024
|
|
22
|
+
recursive: ClassVar[bool] = False
|
|
23
|
+
|
|
19
24
|
def __init__(self, local_graph: LocksGraph = graph) -> None:
|
|
20
25
|
self.graph: LocksGraph = local_graph
|
|
21
26
|
self.lock: Lock = Lock()
|
|
22
27
|
self.deque: Deque[int] = deque()
|
|
23
28
|
self.local_locks: Dict[int, Lock] = {}
|
|
29
|
+
self.recursion_depths: Dict[int, int] = {}
|
|
24
30
|
|
|
25
31
|
def __enter__(self) -> None:
|
|
26
32
|
self.acquire()
|
|
@@ -33,16 +39,24 @@ class SmartLock:
|
|
|
33
39
|
previous_element_lock = None
|
|
34
40
|
|
|
35
41
|
with self.lock, self.graph.lock:
|
|
42
|
+
if self.deque and self.deque[-1] == thread_id:
|
|
43
|
+
if not self.recursive:
|
|
44
|
+
raise DeadLockError(f'A cycle between {thread_id}th and {thread_id}th threads has been detected.')
|
|
45
|
+
self.recursion_depths[thread_id] += 1
|
|
46
|
+
return
|
|
47
|
+
|
|
36
48
|
if not self.deque:
|
|
37
49
|
self.deque.appendleft(thread_id)
|
|
38
50
|
self.local_locks[thread_id] = Lock()
|
|
39
51
|
self.local_locks[thread_id].acquire()
|
|
52
|
+
self.recursion_depths[thread_id] = 1
|
|
40
53
|
else:
|
|
41
54
|
previous_element = self.deque[0]
|
|
42
55
|
self.graph.add_link(thread_id, previous_element)
|
|
43
56
|
self.deque.appendleft(thread_id)
|
|
44
57
|
self.local_locks[thread_id] = Lock()
|
|
45
58
|
self.local_locks[thread_id].acquire()
|
|
59
|
+
self.recursion_depths[thread_id] = 1
|
|
46
60
|
previous_element_lock = self.local_locks[previous_element]
|
|
47
61
|
|
|
48
62
|
if previous_element_lock is not None:
|
|
@@ -52,12 +66,17 @@ class SmartLock:
|
|
|
52
66
|
thread_id = get_native_id()
|
|
53
67
|
|
|
54
68
|
with self.lock, self.graph.lock:
|
|
55
|
-
if
|
|
69
|
+
if not self.deque or self.deque[-1] != thread_id:
|
|
56
70
|
raise RuntimeError('Release unlocked lock.')
|
|
57
71
|
|
|
72
|
+
if self.recursive and self.recursion_depths[thread_id] > 1:
|
|
73
|
+
self.recursion_depths[thread_id] -= 1
|
|
74
|
+
return
|
|
75
|
+
|
|
58
76
|
self.deque.pop()
|
|
59
77
|
lock = self.local_locks[thread_id]
|
|
60
78
|
del self.local_locks[thread_id]
|
|
79
|
+
del self.recursion_depths[thread_id]
|
|
61
80
|
|
|
62
81
|
if len(self.deque) != 0:
|
|
63
82
|
next_element = self.deque[-1]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: locklib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.24
|
|
4
4
|
Summary: When there are not enough locks from the standard library
|
|
5
5
|
Author-email: Evgeniy Blinov <zheni-b@yandex.ru>
|
|
6
6
|
Project-URL: Source, https://github.com/mutating/locklib
|
|
@@ -100,13 +100,14 @@ from multiprocessing import Lock as MLock
|
|
|
100
100
|
from threading import Lock as TLock, RLock as TRLock
|
|
101
101
|
from asyncio import Lock as ALock
|
|
102
102
|
|
|
103
|
-
from locklib import SmartLock, LockProtocol
|
|
103
|
+
from locklib import SmartLock, SmartRLock, LockProtocol
|
|
104
104
|
|
|
105
105
|
print(isinstance(MLock(), LockProtocol)) # True
|
|
106
106
|
print(isinstance(TLock(), LockProtocol)) # True
|
|
107
107
|
print(isinstance(TRLock(), LockProtocol)) # True
|
|
108
108
|
print(isinstance(ALock(), LockProtocol)) # True
|
|
109
109
|
print(isinstance(SmartLock(), LockProtocol)) # True
|
|
110
|
+
print(isinstance(SmartRLock(), LockProtocol)) # True
|
|
110
111
|
```
|
|
111
112
|
|
|
112
113
|
However, most idiomatic Python code uses locks as context managers. If your code does too, you can use one of the two protocols derived from the base `LockProtocol`: `ContextLockProtocol` or `AsyncContextLockProtocol`. Thus, the protocol hierarchy looks like this:
|
|
@@ -119,18 +120,19 @@ LockProtocol
|
|
|
119
120
|
|
|
120
121
|
`ContextLockProtocol` describes objects that satisfy `LockProtocol` and also implement the [context manager protocol](https://docs.python.org/3/library/stdtypes.html#typecontextmanager). Similarly,`AsyncContextLockProtocol` describes objects that satisfy `LockProtocol` and implement the [asynchronous context manager](https://docs.python.org/3/reference/datamodel.html#async-context-managers) protocol.
|
|
121
122
|
|
|
122
|
-
Almost all standard library locks, as well as `SmartLock`, satisfy `ContextLockProtocol`:
|
|
123
|
+
Almost all standard library locks, as well as `SmartLock` and `SmartRLock`, satisfy `ContextLockProtocol`:
|
|
123
124
|
|
|
124
125
|
```python
|
|
125
126
|
from multiprocessing import Lock as MLock
|
|
126
127
|
from threading import Lock as TLock, RLock as TRLock
|
|
127
128
|
|
|
128
|
-
from locklib import SmartLock, ContextLockProtocol
|
|
129
|
+
from locklib import SmartLock, SmartRLock, ContextLockProtocol
|
|
129
130
|
|
|
130
131
|
print(isinstance(MLock(), ContextLockProtocol)) # True
|
|
131
132
|
print(isinstance(TLock(), ContextLockProtocol)) # True
|
|
132
133
|
print(isinstance(TRLock(), ContextLockProtocol)) # True
|
|
133
134
|
print(isinstance(SmartLock(), ContextLockProtocol)) # True
|
|
135
|
+
print(isinstance(SmartRLock(), ContextLockProtocol)) # True
|
|
134
136
|
```
|
|
135
137
|
|
|
136
138
|
However, the [`asyncio.Lock`](https://docs.python.org/3/library/asyncio-sync.html#asyncio.Lock) belongs to a separate category and `AsyncContextLockProtocol` is needed to describe it:
|
|
@@ -239,6 +241,29 @@ If you want to catch this exception, you can also import it from `locklib`:
|
|
|
239
241
|
from locklib import DeadLockError
|
|
240
242
|
```
|
|
241
243
|
|
|
244
|
+
`SmartLock` is deliberately not recursive: acquiring the same instance twice from the same thread raises `DeadLockError`. When recursive ownership is needed, use `SmartRLock` instead. It keeps the same deadlock detection for waits between threads, but lets the owning thread enter the same lock repeatedly:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from locklib import SmartRLock
|
|
248
|
+
|
|
249
|
+
lock = SmartRLock()
|
|
250
|
+
|
|
251
|
+
with lock, lock:
|
|
252
|
+
pass
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
If you use explicit `acquire()` and `release()` calls, every successful acquire must have a matching release:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from locklib import SmartRLock
|
|
259
|
+
|
|
260
|
+
lock = SmartRLock()
|
|
261
|
+
|
|
262
|
+
lock.acquire()
|
|
263
|
+
lock.acquire()
|
|
264
|
+
lock.release()
|
|
265
|
+
lock.release()
|
|
266
|
+
```
|
|
242
267
|
|
|
243
268
|
## Test your locks
|
|
244
269
|
|
|
@@ -13,8 +13,10 @@ locklib/locks/empty/__init__.py
|
|
|
13
13
|
locklib/locks/empty/async_empty_lock.py
|
|
14
14
|
locklib/locks/empty/empty_lock.py
|
|
15
15
|
locklib/locks/smart_lock/__init__.py
|
|
16
|
+
locklib/locks/smart_lock/abstract.py
|
|
16
17
|
locklib/locks/smart_lock/graph.py
|
|
17
18
|
locklib/locks/smart_lock/lock.py
|
|
19
|
+
locklib/locks/smart_lock/rlock.py
|
|
18
20
|
locklib/locks/tracer/__init__.py
|
|
19
21
|
locklib/locks/tracer/events.py
|
|
20
22
|
locklib/locks/tracer/tracer.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|