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.
Files changed (28) hide show
  1. {locklib-0.0.23 → locklib-0.0.24}/PKG-INFO +29 -4
  2. {locklib-0.0.23 → locklib-0.0.24}/README.md +28 -3
  3. {locklib-0.0.23 → locklib-0.0.24}/locklib/__init__.py +1 -0
  4. locklib-0.0.23/locklib/locks/smart_lock/lock.py → locklib-0.0.24/locklib/locks/smart_lock/abstract.py +22 -3
  5. locklib-0.0.24/locklib/locks/smart_lock/lock.py +5 -0
  6. locklib-0.0.24/locklib/locks/smart_lock/rlock.py +5 -0
  7. {locklib-0.0.23 → locklib-0.0.24}/locklib.egg-info/PKG-INFO +29 -4
  8. {locklib-0.0.23 → locklib-0.0.24}/locklib.egg-info/SOURCES.txt +2 -0
  9. {locklib-0.0.23 → locklib-0.0.24}/pyproject.toml +1 -1
  10. {locklib-0.0.23 → locklib-0.0.24}/LICENSE +0 -0
  11. {locklib-0.0.23 → locklib-0.0.24}/locklib/errors.py +0 -0
  12. {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/__init__.py +0 -0
  13. {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/empty/__init__.py +0 -0
  14. {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/empty/async_empty_lock.py +0 -0
  15. {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/empty/empty_lock.py +0 -0
  16. {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/smart_lock/__init__.py +0 -0
  17. {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/smart_lock/graph.py +0 -0
  18. {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/tracer/__init__.py +0 -0
  19. {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/tracer/events.py +0 -0
  20. {locklib-0.0.23 → locklib-0.0.24}/locklib/locks/tracer/tracer.py +0 -0
  21. {locklib-0.0.23 → locklib-0.0.24}/locklib/protocols/__init__.py +0 -0
  22. {locklib-0.0.23 → locklib-0.0.24}/locklib/protocols/async_context_lock.py +0 -0
  23. {locklib-0.0.23 → locklib-0.0.24}/locklib/protocols/context_lock.py +0 -0
  24. {locklib-0.0.23 → locklib-0.0.24}/locklib/protocols/lock.py +0 -0
  25. {locklib-0.0.23 → locklib-0.0.24}/locklib/py.typed +0 -0
  26. {locklib-0.0.23 → locklib-0.0.24}/locklib.egg-info/dependency_links.txt +0 -0
  27. {locklib-0.0.23 → locklib-0.0.24}/locklib.egg-info/top_level.txt +0 -0
  28. {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.23
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
- class SmartLock:
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 thread_id not in self.local_locks:
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]
@@ -0,0 +1,5 @@
1
+ from locklib.locks.smart_lock.abstract import AbstractSmartLock
2
+
3
+
4
+ class SmartLock(AbstractSmartLock):
5
+ recursive = False
@@ -0,0 +1,5 @@
1
+ from locklib.locks.smart_lock.abstract import AbstractSmartLock
2
+
3
+
4
+ class SmartRLock(AbstractSmartLock):
5
+ recursive = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: locklib
3
- Version: 0.0.23
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
@@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta'
4
4
 
5
5
  [project]
6
6
  name = 'locklib'
7
- version = '0.0.23'
7
+ version = '0.0.24'
8
8
  authors = [
9
9
  { name='Evgeniy Blinov', email='zheni-b@yandex.ru' },
10
10
  ]
File without changes
File without changes
File without changes
File without changes