rwlocker 3.0__tar.gz → 3.2__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.
- {rwlocker-3.0/rwlocker.egg-info → rwlocker-3.2}/PKG-INFO +23 -27
- {rwlocker-3.0 → rwlocker-3.2}/README-pypi.md +22 -26
- {rwlocker-3.0 → rwlocker-3.2}/pyproject.toml +1 -1
- {rwlocker-3.0 → rwlocker-3.2}/rwlocker/__init__.py +1 -1
- {rwlocker-3.0 → rwlocker-3.2}/rwlocker/async_rwlock.py +102 -65
- {rwlocker-3.0 → rwlocker-3.2}/rwlocker/thread_rwlock.py +77 -46
- {rwlocker-3.0 → rwlocker-3.2/rwlocker.egg-info}/PKG-INFO +23 -27
- {rwlocker-3.0 → rwlocker-3.2}/LICENSE +0 -0
- {rwlocker-3.0 → rwlocker-3.2}/MANIFEST.in +0 -0
- {rwlocker-3.0 → rwlocker-3.2}/rwlocker.egg-info/SOURCES.txt +0 -0
- {rwlocker-3.0 → rwlocker-3.2}/rwlocker.egg-info/dependency_links.txt +0 -0
- {rwlocker-3.0 → rwlocker-3.2}/rwlocker.egg-info/top_level.txt +0 -0
- {rwlocker-3.0 → rwlocker-3.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rwlocker
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2
|
|
4
4
|
Summary: Advanced, High-Performance, and State-Machine Based Synchronous/Asynchronous Read-Write Locks.
|
|
5
5
|
Author-email: TahsinCr <TahsinCrs@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -112,29 +112,33 @@ If you use the lock objects directly like a standard lock without specifying the
|
|
|
112
112
|
|
|
113
113
|
## 📊 Performance and Benchmark Results
|
|
114
114
|
|
|
115
|
-
`rwlocker`
|
|
115
|
+
The performance results demonstrate `rwlocker`'s true potential during Network and Database I/O operations where Python's GIL (Global Interpreter Lock) is released or context-switched.
|
|
116
116
|
|
|
117
|
-
**
|
|
117
|
+
**🖥️ Test Environment:** All tests were executed on an **Intel Core i7-12700H (2.4GHz)** processor running **EndeavourOS (Arch-based Linux)**, using **Python 3.14.3** and the experimental **Free-Threading (3.14.3t)** interpreters.
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
While standard locks queue readers single-file and choke the system, `rwlocker` allows readers to access the data simultaneously. This achieves **~37x FASTER** speed and throughput in **Threading** and **~30x FASTER** in **Asyncio**.
|
|
121
|
-
* **⚖️ Balanced Scenario (50 Readers, 50 Writers):**
|
|
122
|
-
Thanks to the Fair state machine, read operations are squeezed in parallel between write queues. It increases performance by **2x** compared to standard locks without creating a system bottleneck.
|
|
123
|
-
* **🛡️ Write-Heavy Scenario (2 Readers, 100 Writers):**
|
|
124
|
-
Even though write operations inherently cannot be executed concurrently (in parallel), thanks to `rwlocker`'s zero-allocation smart proxy architecture, it runs **7-8% faster** than standard `C`-based locks. Even the O(1) cost "ReentrantWriter" (reentrancy) feature adds almost no overhead to performance.
|
|
119
|
+
**🧪 Methodology:** To ensure absolute precision and zero margin of error, all reader and writer entities (threads or async tasks) are spawned in advance and held at a starting line using a synchronization `Event`. Once the event triggers, they execute simultaneously. The workloads strictly follow an `IOBoundScenario` that enforces a precise `time.sleep(0.001)` or `asyncio.sleep(0.001)` delay to accurately simulate real network/database I/O latency.
|
|
125
120
|
|
|
126
|
-
|
|
121
|
+
### 1. Read-Write Lock (RWLock) Benchmarks
|
|
127
122
|
|
|
128
|
-
Standard
|
|
123
|
+
Standard locks force threads to queue single-file even if they are only reading data. `rwlocker` unleashes concurrent read access.
|
|
129
124
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
* **🔀 Balanced Pub/Sub (50 Writers, 50 Readers):**
|
|
133
|
-
In mixed waiting and waking scenarios, our Condition locks with the `Write-Pref` strategy ran **~2x FASTER** than the standard library.
|
|
134
|
-
* **📉 Write-Heavy Limit (Stress Test - 100 Writers, 2 Readers):**
|
|
135
|
-
In this brutal scenario where writers constantly block each other and call `notify()`, C-based standard locks utilize their raw speed advantage. `rwlocker`'s Write-Pref model holds its ground neck-and-neck (1.0x) with the standard lock, while the Fair and Read-Pref models intentionally slow down (0.5x - 0.7x) for the sake of maintaining fairness.
|
|
125
|
+
**Synchronous (Thread) RWLock Performance:**
|
|
126
|
+
In the Read-Heavy scenario, standard C-based locks choke the system, whereas `rwlocker` achieves up to **~35x speedup**. Even in Write-Heavy workloads where parallelism isn't inherently possible, `rwlocker` matches or slightly outperforms the C-baseline thanks to its zero-allocation fast-paths.
|
|
136
127
|
|
|
137
|
-
|
|
128
|
+
**Asynchronous (Asyncio) RWLock Performance:**
|
|
129
|
+
`rwlocker` achieves **~30x faster throughput** by allowing thousands of reader tasks to await I/O simultaneously without blocking one another.
|
|
130
|
+
|
|
131
|
+
### 2. Condition Variable (RWCondition) Benchmarks
|
|
132
|
+
|
|
133
|
+
Standard `Condition` variables iterate through all sleeping threads/tasks one by one `O(N)` during a broadcast (`notify_all`), causing massive CPU spikes and "Cache Stampedes". `rwlocker` completely eradicates this with its pure `O(1)` queueing architecture.
|
|
134
|
+
|
|
135
|
+
**Synchronous (Thread) RWCondition Performance:**
|
|
136
|
+
When 100 sleeping readers are awakened simultaneously, `rwlocker` processes them instantly without locking the OS. This architectural leap results in a mind-blowing **~45x speedup** compared to the standard library's `threading.Condition`.
|
|
137
|
+
|
|
138
|
+
**Asynchronous (Asyncio) RWCondition Performance:**
|
|
139
|
+
In event-driven caching systems, waking up hundreds of waiting web requests simultaneously is a major bottleneck. The `AsyncRWCondition` completely bypasses standard asyncio constraints, reaching up to **~45x higher throughput** during massive broadcast scenarios, completely saving the loop from freezing.
|
|
140
|
+
|
|
141
|
+
*(Note: All lock, adapter, and condition classes have passed a massive suite of **315 different unit tests** covering reentrancy, deadlock, timeout, OS interrupts, O(N) leaks, and cancellation safety scenarios with 0 errors, and this entire test suite was completed in just **13.2 seconds**.)*
|
|
138
142
|
|
|
139
143
|
|
|
140
144
|
<br/>
|
|
@@ -469,7 +473,7 @@ Email: TahsinCrs@gmail.com
|
|
|
469
473
|
|
|
470
474
|
[issues-url]: https://github.com/TahsinCr/python-rwlocker/issues
|
|
471
475
|
|
|
472
|
-
[examples-url]: https://github.com/TahsinCr/python-rwlocker/
|
|
476
|
+
[examples-url]: https://github.com/TahsinCr/python-rwlocker/tree/main/examples
|
|
473
477
|
|
|
474
478
|
[license-url]: https://github.com/TahsinCr/python-rwlocker/blob/main/LICENSE
|
|
475
479
|
|
|
@@ -482,11 +486,3 @@ Email: TahsinCrs@gmail.com
|
|
|
482
486
|
[linkedin-url]: https://linkedin.com/in/TahsinCr
|
|
483
487
|
|
|
484
488
|
[x-url]: https://twitter.com/TahsinCrs
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
<!-- File URL -->
|
|
489
|
-
|
|
490
|
-
[lang-tr-url]: https://github.com/TahsinCr/python-rwlocker/blob/main/README_TR.md
|
|
491
|
-
|
|
492
|
-
[lang-en-url]: https://github.com/TahsinCr/python-rwlocker/blob/main/README.md
|
|
@@ -83,29 +83,33 @@ If you use the lock objects directly like a standard lock without specifying the
|
|
|
83
83
|
|
|
84
84
|
## 📊 Performance and Benchmark Results
|
|
85
85
|
|
|
86
|
-
`rwlocker`
|
|
86
|
+
The performance results demonstrate `rwlocker`'s true potential during Network and Database I/O operations where Python's GIL (Global Interpreter Lock) is released or context-switched.
|
|
87
87
|
|
|
88
|
-
**
|
|
88
|
+
**🖥️ Test Environment:** All tests were executed on an **Intel Core i7-12700H (2.4GHz)** processor running **EndeavourOS (Arch-based Linux)**, using **Python 3.14.3** and the experimental **Free-Threading (3.14.3t)** interpreters.
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
While standard locks queue readers single-file and choke the system, `rwlocker` allows readers to access the data simultaneously. This achieves **~37x FASTER** speed and throughput in **Threading** and **~30x FASTER** in **Asyncio**.
|
|
92
|
-
* **⚖️ Balanced Scenario (50 Readers, 50 Writers):**
|
|
93
|
-
Thanks to the Fair state machine, read operations are squeezed in parallel between write queues. It increases performance by **2x** compared to standard locks without creating a system bottleneck.
|
|
94
|
-
* **🛡️ Write-Heavy Scenario (2 Readers, 100 Writers):**
|
|
95
|
-
Even though write operations inherently cannot be executed concurrently (in parallel), thanks to `rwlocker`'s zero-allocation smart proxy architecture, it runs **7-8% faster** than standard `C`-based locks. Even the O(1) cost "ReentrantWriter" (reentrancy) feature adds almost no overhead to performance.
|
|
90
|
+
**🧪 Methodology:** To ensure absolute precision and zero margin of error, all reader and writer entities (threads or async tasks) are spawned in advance and held at a starting line using a synchronization `Event`. Once the event triggers, they execute simultaneously. The workloads strictly follow an `IOBoundScenario` that enforces a precise `time.sleep(0.001)` or `asyncio.sleep(0.001)` delay to accurately simulate real network/database I/O latency.
|
|
96
91
|
|
|
97
|
-
|
|
92
|
+
### 1. Read-Write Lock (RWLock) Benchmarks
|
|
98
93
|
|
|
99
|
-
Standard
|
|
94
|
+
Standard locks force threads to queue single-file even if they are only reading data. `rwlocker` unleashes concurrent read access.
|
|
100
95
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
* **🔀 Balanced Pub/Sub (50 Writers, 50 Readers):**
|
|
104
|
-
In mixed waiting and waking scenarios, our Condition locks with the `Write-Pref` strategy ran **~2x FASTER** than the standard library.
|
|
105
|
-
* **📉 Write-Heavy Limit (Stress Test - 100 Writers, 2 Readers):**
|
|
106
|
-
In this brutal scenario where writers constantly block each other and call `notify()`, C-based standard locks utilize their raw speed advantage. `rwlocker`'s Write-Pref model holds its ground neck-and-neck (1.0x) with the standard lock, while the Fair and Read-Pref models intentionally slow down (0.5x - 0.7x) for the sake of maintaining fairness.
|
|
96
|
+
**Synchronous (Thread) RWLock Performance:**
|
|
97
|
+
In the Read-Heavy scenario, standard C-based locks choke the system, whereas `rwlocker` achieves up to **~35x speedup**. Even in Write-Heavy workloads where parallelism isn't inherently possible, `rwlocker` matches or slightly outperforms the C-baseline thanks to its zero-allocation fast-paths.
|
|
107
98
|
|
|
108
|
-
|
|
99
|
+
**Asynchronous (Asyncio) RWLock Performance:**
|
|
100
|
+
`rwlocker` achieves **~30x faster throughput** by allowing thousands of reader tasks to await I/O simultaneously without blocking one another.
|
|
101
|
+
|
|
102
|
+
### 2. Condition Variable (RWCondition) Benchmarks
|
|
103
|
+
|
|
104
|
+
Standard `Condition` variables iterate through all sleeping threads/tasks one by one `O(N)` during a broadcast (`notify_all`), causing massive CPU spikes and "Cache Stampedes". `rwlocker` completely eradicates this with its pure `O(1)` queueing architecture.
|
|
105
|
+
|
|
106
|
+
**Synchronous (Thread) RWCondition Performance:**
|
|
107
|
+
When 100 sleeping readers are awakened simultaneously, `rwlocker` processes them instantly without locking the OS. This architectural leap results in a mind-blowing **~45x speedup** compared to the standard library's `threading.Condition`.
|
|
108
|
+
|
|
109
|
+
**Asynchronous (Asyncio) RWCondition Performance:**
|
|
110
|
+
In event-driven caching systems, waking up hundreds of waiting web requests simultaneously is a major bottleneck. The `AsyncRWCondition` completely bypasses standard asyncio constraints, reaching up to **~45x higher throughput** during massive broadcast scenarios, completely saving the loop from freezing.
|
|
111
|
+
|
|
112
|
+
*(Note: All lock, adapter, and condition classes have passed a massive suite of **315 different unit tests** covering reentrancy, deadlock, timeout, OS interrupts, O(N) leaks, and cancellation safety scenarios with 0 errors, and this entire test suite was completed in just **13.2 seconds**.)*
|
|
109
113
|
|
|
110
114
|
|
|
111
115
|
<br/>
|
|
@@ -440,7 +444,7 @@ Email: TahsinCrs@gmail.com
|
|
|
440
444
|
|
|
441
445
|
[issues-url]: https://github.com/TahsinCr/python-rwlocker/issues
|
|
442
446
|
|
|
443
|
-
[examples-url]: https://github.com/TahsinCr/python-rwlocker/
|
|
447
|
+
[examples-url]: https://github.com/TahsinCr/python-rwlocker/tree/main/examples
|
|
444
448
|
|
|
445
449
|
[license-url]: https://github.com/TahsinCr/python-rwlocker/blob/main/LICENSE
|
|
446
450
|
|
|
@@ -453,11 +457,3 @@ Email: TahsinCrs@gmail.com
|
|
|
453
457
|
[linkedin-url]: https://linkedin.com/in/TahsinCr
|
|
454
458
|
|
|
455
459
|
[x-url]: https://twitter.com/TahsinCrs
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
<!-- File URL -->
|
|
460
|
-
|
|
461
|
-
[lang-tr-url]: https://github.com/TahsinCr/python-rwlocker/blob/main/README_TR.md
|
|
462
|
-
|
|
463
|
-
[lang-en-url]: https://github.com/TahsinCr/python-rwlocker/blob/main/README.md
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rwlocker"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.2"
|
|
8
8
|
description = "Advanced, High-Performance, and State-Machine Based Synchronous/Asynchronous Read-Write Locks."
|
|
9
9
|
readme = {file = "README-pypi.md", content-type = "text/markdown"}
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -37,8 +37,9 @@ from collections import deque
|
|
|
37
37
|
from abc import ABC, abstractmethod
|
|
38
38
|
from typing import Callable, Protocol, Optional
|
|
39
39
|
from types import TracebackType
|
|
40
|
+
import weakref
|
|
40
41
|
|
|
41
|
-
__version__ = '3.
|
|
42
|
+
__version__ = '3.2'
|
|
42
43
|
__all__ = (
|
|
43
44
|
'AsyncLockable', 'AsyncLockDowngradable', 'AsyncRWLockBase', 'AsyncRWLockWithProxyBase',
|
|
44
45
|
'AsyncRWLockProxy', 'AsyncRWLockReaderProxy', 'AsyncRWLockWriterProxy',
|
|
@@ -180,6 +181,7 @@ class _AsyncWaitQueue:
|
|
|
180
181
|
for fut in waiters:
|
|
181
182
|
if not fut.done():
|
|
182
183
|
fut.set_result(True)
|
|
184
|
+
waiters.clear()
|
|
183
185
|
|
|
184
186
|
class AsyncRWLockProxy:
|
|
185
187
|
"""
|
|
@@ -277,7 +279,7 @@ class AsyncRWLockWriterProxy(AsyncRWLockProxy):
|
|
|
277
279
|
)
|
|
278
280
|
self._read_lock = rwlock.read
|
|
279
281
|
self._downgrade_core = rwlock._downgrade_core
|
|
280
|
-
self._downgraded_tasks:
|
|
282
|
+
self._downgraded_tasks:weakref.WeakSet[asyncio.Task] = weakref.WeakSet()
|
|
281
283
|
|
|
282
284
|
def downgrade(self):
|
|
283
285
|
"""
|
|
@@ -299,11 +301,12 @@ class AsyncRWLockWriterProxy(AsyncRWLockProxy):
|
|
|
299
301
|
the release logic to the reader core, avoiding Deadlocks and RuntimeErrors.
|
|
300
302
|
"""
|
|
301
303
|
if self._downgraded_tasks:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
self._downgraded_tasks.remove(task)
|
|
304
|
+
try:
|
|
305
|
+
self._downgraded_tasks.remove(asyncio.current_task())
|
|
305
306
|
self._read_lock._release_core()
|
|
306
307
|
return
|
|
308
|
+
except KeyError:
|
|
309
|
+
pass
|
|
307
310
|
self._release_core()
|
|
308
311
|
|
|
309
312
|
# Read and Write Lock
|
|
@@ -344,17 +347,17 @@ class AsyncRWLockWrite(AsyncRWLockWithProxyBase):
|
|
|
344
347
|
self._writer_active = False
|
|
345
348
|
self._readers_active = 0
|
|
346
349
|
|
|
347
|
-
def _can_read(self) -> bool:
|
|
350
|
+
def _can_read(self) -> bool:
|
|
348
351
|
return not self._writer_active and self.write.those_waiting == 0
|
|
349
352
|
|
|
350
|
-
def _acquire_read_core(self):
|
|
353
|
+
def _acquire_read_core(self):
|
|
351
354
|
self._readers_active += 1
|
|
352
355
|
|
|
353
356
|
def _release_read_core(self):
|
|
354
|
-
if self._readers_active == 0:
|
|
357
|
+
if self._readers_active == 0:
|
|
355
358
|
raise RuntimeError("Unacquired read lock")
|
|
356
359
|
self._readers_active -= 1
|
|
357
|
-
if self._readers_active == 0:
|
|
360
|
+
if self._readers_active == 0:
|
|
358
361
|
self.write.condition.notify()
|
|
359
362
|
|
|
360
363
|
def _on_reader_abort(self):
|
|
@@ -365,7 +368,7 @@ class AsyncRWLockWrite(AsyncRWLockWithProxyBase):
|
|
|
365
368
|
|
|
366
369
|
def _acquire_write_core(self):
|
|
367
370
|
self._writer_active = True
|
|
368
|
-
|
|
371
|
+
|
|
369
372
|
def _release_write_core(self):
|
|
370
373
|
if not self._writer_active:
|
|
371
374
|
raise RuntimeError("Unacquired write lock")
|
|
@@ -390,7 +393,7 @@ class AsyncRWLockWrite(AsyncRWLockWithProxyBase):
|
|
|
390
393
|
|
|
391
394
|
def _is_read_locked(self) -> bool:
|
|
392
395
|
return self._readers_active > 0
|
|
393
|
-
|
|
396
|
+
|
|
394
397
|
class AsyncRWLockWriteReentrantWriter(AsyncRWLockWrite):
|
|
395
398
|
"""
|
|
396
399
|
Write-preferring Asynchronous Read-Write Lock with Task-Reentrancy.
|
|
@@ -434,36 +437,47 @@ class AsyncRWLockWriteReentrantWriter(AsyncRWLockWrite):
|
|
|
434
437
|
self._writer_id: Optional[asyncio.Task] = None
|
|
435
438
|
self._write_count: int = 0
|
|
436
439
|
|
|
440
|
+
def _is_current_writer(self) -> bool:
|
|
441
|
+
return self._writer_id is asyncio.current_task()
|
|
442
|
+
|
|
443
|
+
def _set_current_writer(self) -> None:
|
|
444
|
+
self._writer_id = asyncio.current_task()
|
|
445
|
+
|
|
446
|
+
def _clear_current_writer(self) -> None:
|
|
447
|
+
self._writer_id = None
|
|
448
|
+
|
|
437
449
|
def _can_read(self) -> bool:
|
|
438
|
-
if self._writer_id is
|
|
439
|
-
return
|
|
440
|
-
return self.
|
|
450
|
+
if self._writer_id is not None:
|
|
451
|
+
return self._is_current_writer()
|
|
452
|
+
return self.write.those_waiting == 0
|
|
441
453
|
|
|
442
454
|
def _can_write(self) -> bool:
|
|
443
|
-
|
|
455
|
+
if self._writer_id is not None:
|
|
456
|
+
return self._is_current_writer()
|
|
457
|
+
return self._readers_active == 0
|
|
444
458
|
|
|
445
459
|
def _acquire_write_core(self):
|
|
446
|
-
self.
|
|
460
|
+
self._set_current_writer()
|
|
447
461
|
self._write_count += 1
|
|
448
462
|
|
|
449
463
|
def _release_write_core(self):
|
|
450
|
-
if
|
|
464
|
+
if not self._is_current_writer():
|
|
451
465
|
raise RuntimeError("Permission denied")
|
|
452
466
|
self._write_count -= 1
|
|
453
467
|
if self._write_count == 0:
|
|
454
|
-
self.
|
|
468
|
+
self._clear_current_writer()
|
|
455
469
|
self._on_writer_abort()
|
|
456
470
|
|
|
457
471
|
def _downgrade_core(self):
|
|
458
|
-
if
|
|
472
|
+
if not self._is_current_writer():
|
|
459
473
|
raise RuntimeError("Permission denied")
|
|
460
474
|
if self._write_count > 1:
|
|
461
|
-
raise RuntimeError("Cannot downgrade a nested write lock.")
|
|
462
|
-
self.
|
|
475
|
+
raise RuntimeError("Cannot downgrade a nested write lock. Release inner locks first.")
|
|
476
|
+
self._clear_current_writer()
|
|
463
477
|
self._write_count = 0
|
|
464
478
|
self._readers_active += 1
|
|
465
479
|
self.read.condition.notify_all()
|
|
466
|
-
|
|
480
|
+
|
|
467
481
|
def _is_write_locked(self) -> bool:
|
|
468
482
|
return self._writer_id is not None
|
|
469
483
|
|
|
@@ -504,43 +518,44 @@ class AsyncRWLockRead(AsyncRWLockWithProxyBase):
|
|
|
504
518
|
self._writer_active = False
|
|
505
519
|
self._readers_active = 0
|
|
506
520
|
|
|
507
|
-
def _can_read(self) -> bool:
|
|
521
|
+
def _can_read(self) -> bool:
|
|
508
522
|
return not self._writer_active
|
|
509
523
|
|
|
510
|
-
def _acquire_read_core(self):
|
|
524
|
+
def _acquire_read_core(self):
|
|
511
525
|
self._readers_active += 1
|
|
512
526
|
|
|
513
527
|
def _release_read_core(self):
|
|
514
|
-
if self._readers_active == 0:
|
|
528
|
+
if self._readers_active == 0:
|
|
515
529
|
raise RuntimeError("Unacquired read lock")
|
|
516
530
|
self._readers_active -= 1
|
|
517
|
-
if self._readers_active == 0:
|
|
531
|
+
if self._readers_active == 0:
|
|
518
532
|
self.write.condition.notify()
|
|
519
533
|
|
|
520
534
|
def _on_reader_abort(self):
|
|
521
|
-
if self.read.those_waiting == 0 and self._readers_active == 0:
|
|
535
|
+
if self.read.those_waiting == 0 and self._readers_active == 0:
|
|
522
536
|
self.write.condition.notify()
|
|
523
537
|
|
|
524
538
|
def _can_write(self) -> bool:
|
|
525
539
|
return not self._writer_active and self._readers_active == 0 and self.read.those_waiting == 0
|
|
526
540
|
|
|
527
|
-
def _acquire_write_core(self):
|
|
541
|
+
def _acquire_write_core(self):
|
|
528
542
|
self._writer_active = True
|
|
529
543
|
|
|
530
544
|
def _release_write_core(self):
|
|
531
|
-
if not self._writer_active:
|
|
545
|
+
if not self._writer_active:
|
|
532
546
|
raise RuntimeError("Unacquired write lock")
|
|
533
547
|
self._writer_active = False
|
|
534
548
|
self._on_writer_abort()
|
|
549
|
+
|
|
535
550
|
def _downgrade_core(self):
|
|
536
|
-
if not self._writer_active:
|
|
551
|
+
if not self._writer_active:
|
|
537
552
|
raise RuntimeError("Cannot downgrade unlocked lock")
|
|
538
553
|
self._writer_active = False
|
|
539
554
|
self._readers_active += 1
|
|
540
555
|
self.read.condition.notify_all()
|
|
541
556
|
|
|
542
557
|
def _on_writer_abort(self):
|
|
543
|
-
if self.read.those_waiting > 0:
|
|
558
|
+
if self.read.those_waiting > 0:
|
|
544
559
|
self.read.condition.notify_all()
|
|
545
560
|
else:
|
|
546
561
|
self.write.condition.notify()
|
|
@@ -550,7 +565,7 @@ class AsyncRWLockRead(AsyncRWLockWithProxyBase):
|
|
|
550
565
|
|
|
551
566
|
def _is_read_locked(self) -> bool:
|
|
552
567
|
return self._readers_active > 0
|
|
553
|
-
|
|
568
|
+
|
|
554
569
|
class AsyncRWLockReadReentrantWriter(AsyncRWLockRead):
|
|
555
570
|
"""
|
|
556
571
|
Read-preferring Asynchronous Read-Write Lock with Task-Reentrancy.
|
|
@@ -592,32 +607,43 @@ class AsyncRWLockReadReentrantWriter(AsyncRWLockRead):
|
|
|
592
607
|
self._writer_id: Optional[asyncio.Task] = None
|
|
593
608
|
self._write_count: int = 0
|
|
594
609
|
|
|
610
|
+
def _is_current_writer(self) -> bool:
|
|
611
|
+
return self._writer_id is asyncio.current_task()
|
|
612
|
+
|
|
613
|
+
def _set_current_writer(self) -> None:
|
|
614
|
+
self._writer_id = asyncio.current_task()
|
|
615
|
+
|
|
616
|
+
def _clear_current_writer(self) -> None:
|
|
617
|
+
self._writer_id = None
|
|
618
|
+
|
|
595
619
|
def _can_read(self) -> bool:
|
|
596
|
-
if self._writer_id is
|
|
597
|
-
return
|
|
598
|
-
return
|
|
620
|
+
if self._writer_id is not None:
|
|
621
|
+
return self._is_current_writer()
|
|
622
|
+
return True
|
|
599
623
|
|
|
600
624
|
def _can_write(self) -> bool:
|
|
601
|
-
|
|
625
|
+
if self._writer_id is not None:
|
|
626
|
+
return self._is_current_writer()
|
|
627
|
+
return self._readers_active == 0 and self.read.those_waiting == 0
|
|
602
628
|
|
|
603
629
|
def _acquire_write_core(self):
|
|
604
|
-
self.
|
|
630
|
+
self._set_current_writer()
|
|
605
631
|
self._write_count += 1
|
|
606
632
|
|
|
607
633
|
def _release_write_core(self):
|
|
608
|
-
if
|
|
634
|
+
if not self._is_current_writer():
|
|
609
635
|
raise RuntimeError("Permission denied")
|
|
610
636
|
self._write_count -= 1
|
|
611
637
|
if self._write_count == 0:
|
|
612
|
-
self.
|
|
638
|
+
self._clear_current_writer()
|
|
613
639
|
self._on_writer_abort()
|
|
614
640
|
|
|
615
641
|
def _downgrade_core(self):
|
|
616
|
-
if
|
|
642
|
+
if not self._is_current_writer():
|
|
617
643
|
raise RuntimeError("Permission denied")
|
|
618
644
|
if self._write_count > 1:
|
|
619
|
-
raise RuntimeError("Cannot downgrade a nested write lock.")
|
|
620
|
-
self.
|
|
645
|
+
raise RuntimeError("Cannot downgrade a nested write lock. Release inner locks first.")
|
|
646
|
+
self._clear_current_writer()
|
|
621
647
|
self._write_count = 0
|
|
622
648
|
self._readers_active += 1
|
|
623
649
|
self.read.condition.notify_all()
|
|
@@ -668,11 +694,11 @@ class AsyncRWLockFair(AsyncRWLockWithProxyBase):
|
|
|
668
694
|
return False
|
|
669
695
|
return self._readers_turn or self.write.those_waiting == 0
|
|
670
696
|
|
|
671
|
-
def _acquire_read_core(self):
|
|
697
|
+
def _acquire_read_core(self):
|
|
672
698
|
self._readers_active += 1
|
|
673
699
|
|
|
674
700
|
def _release_read_core(self):
|
|
675
|
-
if self._readers_active == 0:
|
|
701
|
+
if self._readers_active == 0:
|
|
676
702
|
raise RuntimeError("Unacquired read lock")
|
|
677
703
|
self._readers_active -= 1
|
|
678
704
|
if self._readers_active == 0:
|
|
@@ -685,9 +711,9 @@ class AsyncRWLockFair(AsyncRWLockWithProxyBase):
|
|
|
685
711
|
self.write.condition.notify()
|
|
686
712
|
|
|
687
713
|
def _can_write(self) -> bool:
|
|
688
|
-
if self._writer_active or self._readers_active > 0:
|
|
714
|
+
if self._writer_active or self._readers_active > 0:
|
|
689
715
|
return False
|
|
690
|
-
if self._readers_turn and self.read.those_waiting > 0:
|
|
716
|
+
if self._readers_turn and self.read.those_waiting > 0:
|
|
691
717
|
return False
|
|
692
718
|
return True
|
|
693
719
|
|
|
@@ -695,13 +721,13 @@ class AsyncRWLockFair(AsyncRWLockWithProxyBase):
|
|
|
695
721
|
self._writer_active = True
|
|
696
722
|
|
|
697
723
|
def _release_write_core(self):
|
|
698
|
-
if not self._writer_active:
|
|
724
|
+
if not self._writer_active:
|
|
699
725
|
raise RuntimeError("Unacquired write lock")
|
|
700
726
|
self._writer_active = False
|
|
701
727
|
self._on_writer_abort()
|
|
702
728
|
|
|
703
729
|
def _downgrade_core(self):
|
|
704
|
-
if not self._writer_active:
|
|
730
|
+
if not self._writer_active:
|
|
705
731
|
raise RuntimeError("Cannot downgrade unlocked lock")
|
|
706
732
|
self._writer_active = False
|
|
707
733
|
self._readers_active += 1
|
|
@@ -712,7 +738,7 @@ class AsyncRWLockFair(AsyncRWLockWithProxyBase):
|
|
|
712
738
|
if self.read.those_waiting > 0:
|
|
713
739
|
self._readers_turn = True
|
|
714
740
|
self.read.condition.notify_all()
|
|
715
|
-
else:
|
|
741
|
+
else:
|
|
716
742
|
self.write.condition.notify()
|
|
717
743
|
|
|
718
744
|
def _is_write_locked(self) -> bool:
|
|
@@ -720,7 +746,7 @@ class AsyncRWLockFair(AsyncRWLockWithProxyBase):
|
|
|
720
746
|
|
|
721
747
|
def _is_read_locked(self) -> bool:
|
|
722
748
|
return self._readers_active > 0
|
|
723
|
-
|
|
749
|
+
|
|
724
750
|
class AsyncRWLockFairReentrantWriter(AsyncRWLockFair):
|
|
725
751
|
"""
|
|
726
752
|
Fair (First-In-First-Out) Asynchronous Read-Write Lock with Task-Reentrancy.
|
|
@@ -762,46 +788,57 @@ class AsyncRWLockFairReentrantWriter(AsyncRWLockFair):
|
|
|
762
788
|
self._writer_id: Optional[asyncio.Task] = None
|
|
763
789
|
self._write_count: int = 0
|
|
764
790
|
|
|
791
|
+
def _is_current_writer(self) -> bool:
|
|
792
|
+
return self._writer_id is asyncio.current_task()
|
|
793
|
+
|
|
794
|
+
def _set_current_writer(self) -> None:
|
|
795
|
+
self._writer_id = asyncio.current_task()
|
|
796
|
+
|
|
797
|
+
def _clear_current_writer(self) -> None:
|
|
798
|
+
self._writer_id = None
|
|
799
|
+
|
|
765
800
|
def _can_read(self) -> bool:
|
|
766
|
-
if self._writer_id is asyncio.current_task():
|
|
767
|
-
return True
|
|
768
801
|
if self._writer_id is not None:
|
|
802
|
+
if self._is_current_writer():
|
|
803
|
+
return True
|
|
769
804
|
return False
|
|
770
805
|
return self._readers_turn or self.write.those_waiting == 0
|
|
771
806
|
|
|
772
807
|
def _can_write(self) -> bool:
|
|
773
|
-
if self._writer_id is
|
|
774
|
-
|
|
775
|
-
|
|
808
|
+
if self._writer_id is not None:
|
|
809
|
+
if self._is_current_writer():
|
|
810
|
+
return True
|
|
811
|
+
return False
|
|
812
|
+
if self._readers_active > 0:
|
|
776
813
|
return False
|
|
777
|
-
if self._readers_turn and self.read.those_waiting > 0:
|
|
814
|
+
if self._readers_turn and self.read.those_waiting > 0:
|
|
778
815
|
return False
|
|
779
816
|
return True
|
|
780
817
|
|
|
781
818
|
def _acquire_write_core(self):
|
|
782
|
-
self.
|
|
819
|
+
self._set_current_writer()
|
|
783
820
|
self._write_count += 1
|
|
784
821
|
|
|
785
822
|
def _release_write_core(self):
|
|
786
|
-
if
|
|
823
|
+
if not self._is_current_writer():
|
|
787
824
|
raise RuntimeError("Permission denied")
|
|
788
825
|
self._write_count -= 1
|
|
789
826
|
if self._write_count == 0:
|
|
790
|
-
self.
|
|
827
|
+
self._clear_current_writer()
|
|
791
828
|
self._on_writer_abort()
|
|
792
829
|
|
|
793
830
|
def _downgrade_core(self):
|
|
794
|
-
if
|
|
831
|
+
if not self._is_current_writer():
|
|
795
832
|
raise RuntimeError("Permission denied")
|
|
796
833
|
if self._write_count > 1:
|
|
797
|
-
raise RuntimeError("Cannot downgrade a nested write lock.")
|
|
798
|
-
self.
|
|
834
|
+
raise RuntimeError("Cannot downgrade a nested write lock. Release inner locks first.")
|
|
835
|
+
self._clear_current_writer()
|
|
799
836
|
self._write_count = 0
|
|
800
837
|
self._readers_active += 1
|
|
801
838
|
self._readers_turn = True
|
|
802
839
|
self.read.condition.notify_all()
|
|
803
840
|
|
|
804
|
-
def _is_write_locked(self) -> bool:
|
|
841
|
+
def _is_write_locked(self) -> bool:
|
|
805
842
|
return self._writer_id is not None
|
|
806
843
|
|
|
807
844
|
# Standart Lock Adapter
|
|
@@ -861,7 +898,7 @@ class AsyncRWConditionBase(ABC):
|
|
|
861
898
|
"""
|
|
862
899
|
__slots__ = ('_lock', 'read', 'write')
|
|
863
900
|
|
|
864
|
-
def __init__(self, lock: Optional[
|
|
901
|
+
def __init__(self, lock: Optional[AsyncLockable] = None):
|
|
865
902
|
self._lock = AsyncLock() if lock is None else lock
|
|
866
903
|
self.write: AsyncConditionLockable = asyncio.Condition(self._lock)
|
|
867
904
|
self.read: AsyncConditionLockable = self.write
|
|
@@ -1274,7 +1311,7 @@ class AsyncCondition(AsyncRWConditionBase):
|
|
|
1274
1311
|
```
|
|
1275
1312
|
"""
|
|
1276
1313
|
__slots__ = ()
|
|
1277
|
-
def __init__(self, lock: Optional[
|
|
1314
|
+
def __init__(self, lock: Optional[AsyncLockable] = None):
|
|
1278
1315
|
if lock is not None and isinstance(lock, AsyncRWLockWithProxyBase):
|
|
1279
1316
|
raise TypeError("Standard 'AsyncCondition' adapters cannot be used with proxy-based RWLocks. Use 'AsyncRWCondition' instead.")
|
|
1280
1317
|
super().__init__(lock)
|
|
@@ -37,7 +37,7 @@ from abc import ABC, abstractmethod
|
|
|
37
37
|
from typing import Callable, Protocol, Optional
|
|
38
38
|
from types import TracebackType
|
|
39
39
|
|
|
40
|
-
__version__ = '3.
|
|
40
|
+
__version__ = '3.2'
|
|
41
41
|
__all__ = (
|
|
42
42
|
'Lockable', 'LockDowngradable', 'RWLockBase', 'RWLockWithProxyBase',
|
|
43
43
|
'RWLockProxy', 'RWLockReaderProxy', 'RWLockWriterProxy',
|
|
@@ -304,7 +304,7 @@ class RWLockWriterProxy(RWLockProxy):
|
|
|
304
304
|
Proxy specifically handling Writer logic, extending capabilities with
|
|
305
305
|
atomic state degradation (downgrading).
|
|
306
306
|
"""
|
|
307
|
-
__slots__ = ('_read_lock', '_downgrade_core', '
|
|
307
|
+
__slots__ = ('_read_lock', '_downgrade_core', '_downgraded_thread_id')
|
|
308
308
|
def __init__(self, rwlock: RWLockWithProxyBase):
|
|
309
309
|
super().__init__(
|
|
310
310
|
lock=rwlock._lock,
|
|
@@ -316,7 +316,7 @@ class RWLockWriterProxy(RWLockProxy):
|
|
|
316
316
|
)
|
|
317
317
|
self._read_lock = rwlock.read
|
|
318
318
|
self._downgrade_core = rwlock._downgrade_core
|
|
319
|
-
self.
|
|
319
|
+
self._downgraded_thread_id: Optional[int] = None
|
|
320
320
|
|
|
321
321
|
def downgrade(self):
|
|
322
322
|
"""
|
|
@@ -330,7 +330,7 @@ class RWLockWriterProxy(RWLockProxy):
|
|
|
330
330
|
"""
|
|
331
331
|
with self._lock:
|
|
332
332
|
self._downgrade_core()
|
|
333
|
-
self.
|
|
333
|
+
self._downgraded_thread_id = threading.get_ident()
|
|
334
334
|
|
|
335
335
|
def release(self):
|
|
336
336
|
"""
|
|
@@ -339,15 +339,13 @@ class RWLockWriterProxy(RWLockProxy):
|
|
|
339
339
|
and routes the release to the reader core to prevent deadlocks.
|
|
340
340
|
"""
|
|
341
341
|
with self._lock:
|
|
342
|
-
if self.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
self._read_lock._release_core()
|
|
347
|
-
return
|
|
342
|
+
if self._downgraded_thread_id == threading.get_ident():
|
|
343
|
+
self._downgraded_thread_id = None
|
|
344
|
+
self._read_lock._release_core()
|
|
345
|
+
return
|
|
348
346
|
self._release_core()
|
|
349
347
|
|
|
350
|
-
#
|
|
348
|
+
# Read and Write Lock
|
|
351
349
|
class RWLockWrite(RWLockWithProxyBase):
|
|
352
350
|
"""
|
|
353
351
|
Write-preferring Read-Write Lock.
|
|
@@ -431,7 +429,7 @@ class RWLockWrite(RWLockWithProxyBase):
|
|
|
431
429
|
|
|
432
430
|
def _is_read_locked(self) -> bool:
|
|
433
431
|
return self._readers_active > 0
|
|
434
|
-
|
|
432
|
+
|
|
435
433
|
class RWLockWriteReentrantWriter(RWLockWrite):
|
|
436
434
|
"""
|
|
437
435
|
Write-preferring Read-Write Lock with Write-Reentrancy support.
|
|
@@ -475,36 +473,47 @@ class RWLockWriteReentrantWriter(RWLockWrite):
|
|
|
475
473
|
self._writer_id: Optional[int] = None
|
|
476
474
|
self._write_count: int = 0
|
|
477
475
|
|
|
476
|
+
def _is_current_writer(self) -> bool:
|
|
477
|
+
return self._writer_id == threading.get_ident()
|
|
478
|
+
|
|
479
|
+
def _set_current_writer(self) -> None:
|
|
480
|
+
self._writer_id = threading.get_ident()
|
|
481
|
+
|
|
482
|
+
def _clear_current_writer(self) -> None:
|
|
483
|
+
self._writer_id = None
|
|
484
|
+
|
|
478
485
|
def _can_read(self) -> bool:
|
|
479
|
-
if self._writer_id
|
|
480
|
-
return
|
|
481
|
-
return self.
|
|
486
|
+
if self._writer_id is not None:
|
|
487
|
+
return self._is_current_writer()
|
|
488
|
+
return self.write.those_waiting == 0
|
|
482
489
|
|
|
483
490
|
def _can_write(self) -> bool:
|
|
484
|
-
|
|
491
|
+
if self._writer_id is not None:
|
|
492
|
+
return self._is_current_writer()
|
|
493
|
+
return self._readers_active == 0
|
|
485
494
|
|
|
486
495
|
def _acquire_write_core(self):
|
|
487
|
-
self.
|
|
496
|
+
self._set_current_writer()
|
|
488
497
|
self._write_count += 1
|
|
489
498
|
|
|
490
499
|
def _release_write_core(self):
|
|
491
|
-
if self.
|
|
500
|
+
if not self._is_current_writer():
|
|
492
501
|
raise RuntimeError("Permission denied")
|
|
493
502
|
self._write_count -= 1
|
|
494
503
|
if self._write_count == 0:
|
|
495
|
-
self.
|
|
504
|
+
self._clear_current_writer()
|
|
496
505
|
self._on_writer_abort()
|
|
497
506
|
|
|
498
507
|
def _downgrade_core(self):
|
|
499
|
-
if self.
|
|
508
|
+
if not self._is_current_writer():
|
|
500
509
|
raise RuntimeError("Permission denied")
|
|
501
510
|
if self._write_count > 1:
|
|
502
511
|
raise RuntimeError("Cannot downgrade a nested write lock. Release inner locks first.")
|
|
503
|
-
self.
|
|
512
|
+
self._clear_current_writer()
|
|
504
513
|
self._write_count = 0
|
|
505
514
|
self._readers_active += 1
|
|
506
515
|
self.read.condition.notify_all()
|
|
507
|
-
|
|
516
|
+
|
|
508
517
|
def _is_write_locked(self) -> bool:
|
|
509
518
|
return self._writer_id is not None
|
|
510
519
|
|
|
@@ -634,32 +643,43 @@ class RWLockReadReentrantWriter(RWLockRead):
|
|
|
634
643
|
self._writer_id: Optional[int] = None
|
|
635
644
|
self._write_count: int = 0
|
|
636
645
|
|
|
646
|
+
def _is_current_writer(self) -> bool:
|
|
647
|
+
return self._writer_id == threading.get_ident()
|
|
648
|
+
|
|
649
|
+
def _set_current_writer(self) -> None:
|
|
650
|
+
self._writer_id = threading.get_ident()
|
|
651
|
+
|
|
652
|
+
def _clear_current_writer(self) -> None:
|
|
653
|
+
self._writer_id = None
|
|
654
|
+
|
|
637
655
|
def _can_read(self) -> bool:
|
|
638
|
-
if self._writer_id
|
|
639
|
-
return
|
|
640
|
-
return
|
|
656
|
+
if self._writer_id is not None:
|
|
657
|
+
return self._is_current_writer()
|
|
658
|
+
return True
|
|
641
659
|
|
|
642
660
|
def _can_write(self) -> bool:
|
|
643
|
-
|
|
661
|
+
if self._writer_id is not None:
|
|
662
|
+
return self._is_current_writer()
|
|
663
|
+
return self._readers_active == 0 and self.read.those_waiting == 0
|
|
644
664
|
|
|
645
665
|
def _acquire_write_core(self):
|
|
646
|
-
self.
|
|
666
|
+
self._set_current_writer()
|
|
647
667
|
self._write_count += 1
|
|
648
668
|
|
|
649
669
|
def _release_write_core(self):
|
|
650
|
-
if self.
|
|
670
|
+
if not self._is_current_writer():
|
|
651
671
|
raise RuntimeError("Permission denied")
|
|
652
672
|
self._write_count -= 1
|
|
653
673
|
if self._write_count == 0:
|
|
654
|
-
self.
|
|
674
|
+
self._clear_current_writer()
|
|
655
675
|
self._on_writer_abort()
|
|
656
676
|
|
|
657
677
|
def _downgrade_core(self):
|
|
658
|
-
if self.
|
|
678
|
+
if not self._is_current_writer():
|
|
659
679
|
raise RuntimeError("Permission denied")
|
|
660
680
|
if self._write_count > 1:
|
|
661
681
|
raise RuntimeError("Cannot downgrade a nested write lock. Release inner locks first.")
|
|
662
|
-
self.
|
|
682
|
+
self._clear_current_writer()
|
|
663
683
|
self._write_count = 0
|
|
664
684
|
self._readers_active += 1
|
|
665
685
|
self.read.condition.notify_all()
|
|
@@ -762,7 +782,7 @@ class RWLockFair(RWLockWithProxyBase):
|
|
|
762
782
|
|
|
763
783
|
def _is_read_locked(self) -> bool:
|
|
764
784
|
return self._readers_active > 0
|
|
765
|
-
|
|
785
|
+
|
|
766
786
|
class RWLockFairReentrantWriter(RWLockFair):
|
|
767
787
|
"""
|
|
768
788
|
Fair (First-In-First-Out) Read-Write Lock with Write-Reentrancy support.
|
|
@@ -804,40 +824,51 @@ class RWLockFairReentrantWriter(RWLockFair):
|
|
|
804
824
|
self._writer_id: Optional[int] = None
|
|
805
825
|
self._write_count: int = 0
|
|
806
826
|
|
|
827
|
+
def _is_current_writer(self) -> bool:
|
|
828
|
+
return self._writer_id == threading.get_ident()
|
|
829
|
+
|
|
830
|
+
def _set_current_writer(self) -> None:
|
|
831
|
+
self._writer_id = threading.get_ident()
|
|
832
|
+
|
|
833
|
+
def _clear_current_writer(self) -> None:
|
|
834
|
+
self._writer_id = None
|
|
835
|
+
|
|
807
836
|
def _can_read(self) -> bool:
|
|
808
|
-
if self._writer_id
|
|
809
|
-
|
|
810
|
-
|
|
837
|
+
if self._writer_id is not None:
|
|
838
|
+
if self._is_current_writer():
|
|
839
|
+
return True
|
|
811
840
|
return False
|
|
812
841
|
return self._readers_turn or self.write.those_waiting == 0
|
|
813
842
|
|
|
814
843
|
def _can_write(self) -> bool:
|
|
815
|
-
if self._writer_id
|
|
816
|
-
|
|
817
|
-
|
|
844
|
+
if self._writer_id is not None:
|
|
845
|
+
if self._is_current_writer():
|
|
846
|
+
return True
|
|
847
|
+
return False
|
|
848
|
+
if self._readers_active > 0:
|
|
818
849
|
return False
|
|
819
850
|
if self._readers_turn and self.read.those_waiting > 0:
|
|
820
851
|
return False
|
|
821
852
|
return True
|
|
822
853
|
|
|
823
854
|
def _acquire_write_core(self):
|
|
824
|
-
self.
|
|
855
|
+
self._set_current_writer()
|
|
825
856
|
self._write_count += 1
|
|
826
857
|
|
|
827
858
|
def _release_write_core(self):
|
|
828
|
-
if self.
|
|
859
|
+
if not self._is_current_writer():
|
|
829
860
|
raise RuntimeError("Permission denied")
|
|
830
861
|
self._write_count -= 1
|
|
831
862
|
if self._write_count == 0:
|
|
832
|
-
self.
|
|
863
|
+
self._clear_current_writer()
|
|
833
864
|
self._on_writer_abort()
|
|
834
865
|
|
|
835
866
|
def _downgrade_core(self):
|
|
836
|
-
if self.
|
|
867
|
+
if not self._is_current_writer():
|
|
837
868
|
raise RuntimeError("Permission denied")
|
|
838
869
|
if self._write_count > 1:
|
|
839
870
|
raise RuntimeError("Cannot downgrade a nested write lock. Release inner locks first.")
|
|
840
|
-
self.
|
|
871
|
+
self._clear_current_writer()
|
|
841
872
|
self._write_count = 0
|
|
842
873
|
self._readers_active += 1
|
|
843
874
|
self._readers_turn = True
|
|
@@ -902,7 +933,7 @@ class RWConditionBase(ABC):
|
|
|
902
933
|
`.write` proxy. Designed to act as a common type hint and contract.
|
|
903
934
|
"""
|
|
904
935
|
__slots__ = ('_lock', 'read', 'write')
|
|
905
|
-
def __init__(self, lock: Optional[
|
|
936
|
+
def __init__(self, lock: Optional[Lockable] = None):
|
|
906
937
|
self._lock = Lock() if lock is None else lock
|
|
907
938
|
self.write: ConditionLockable = threading.Condition(self._lock)
|
|
908
939
|
self.read: ConditionLockable = self.write
|
|
@@ -1238,7 +1269,7 @@ class RWCondition(RWConditionWithProxyBase):
|
|
|
1238
1269
|
self._waiters = deque()
|
|
1239
1270
|
self._internal_lock = threading.Lock()
|
|
1240
1271
|
super().__init__(lock)
|
|
1241
|
-
|
|
1272
|
+
|
|
1242
1273
|
def _is_owned_read(self) -> bool:
|
|
1243
1274
|
return self._lock._is_read_locked()
|
|
1244
1275
|
|
|
@@ -1317,7 +1348,7 @@ class Condition(RWConditionBase):
|
|
|
1317
1348
|
```
|
|
1318
1349
|
"""
|
|
1319
1350
|
__slots__ = ()
|
|
1320
|
-
def __init__(self, lock: Optional[
|
|
1351
|
+
def __init__(self, lock: Optional[Lockable] = None):
|
|
1321
1352
|
if lock is not None and isinstance(lock, RWLockWithProxyBase):
|
|
1322
1353
|
raise TypeError("Standard 'Condition' adapters cannot be used with proxy-based RWLocks. Use 'RWCondition' instead.")
|
|
1323
1354
|
super().__init__(lock)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rwlocker
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2
|
|
4
4
|
Summary: Advanced, High-Performance, and State-Machine Based Synchronous/Asynchronous Read-Write Locks.
|
|
5
5
|
Author-email: TahsinCr <TahsinCrs@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -112,29 +112,33 @@ If you use the lock objects directly like a standard lock without specifying the
|
|
|
112
112
|
|
|
113
113
|
## 📊 Performance and Benchmark Results
|
|
114
114
|
|
|
115
|
-
`rwlocker`
|
|
115
|
+
The performance results demonstrate `rwlocker`'s true potential during Network and Database I/O operations where Python's GIL (Global Interpreter Lock) is released or context-switched.
|
|
116
116
|
|
|
117
|
-
**
|
|
117
|
+
**🖥️ Test Environment:** All tests were executed on an **Intel Core i7-12700H (2.4GHz)** processor running **EndeavourOS (Arch-based Linux)**, using **Python 3.14.3** and the experimental **Free-Threading (3.14.3t)** interpreters.
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
While standard locks queue readers single-file and choke the system, `rwlocker` allows readers to access the data simultaneously. This achieves **~37x FASTER** speed and throughput in **Threading** and **~30x FASTER** in **Asyncio**.
|
|
121
|
-
* **⚖️ Balanced Scenario (50 Readers, 50 Writers):**
|
|
122
|
-
Thanks to the Fair state machine, read operations are squeezed in parallel between write queues. It increases performance by **2x** compared to standard locks without creating a system bottleneck.
|
|
123
|
-
* **🛡️ Write-Heavy Scenario (2 Readers, 100 Writers):**
|
|
124
|
-
Even though write operations inherently cannot be executed concurrently (in parallel), thanks to `rwlocker`'s zero-allocation smart proxy architecture, it runs **7-8% faster** than standard `C`-based locks. Even the O(1) cost "ReentrantWriter" (reentrancy) feature adds almost no overhead to performance.
|
|
119
|
+
**🧪 Methodology:** To ensure absolute precision and zero margin of error, all reader and writer entities (threads or async tasks) are spawned in advance and held at a starting line using a synchronization `Event`. Once the event triggers, they execute simultaneously. The workloads strictly follow an `IOBoundScenario` that enforces a precise `time.sleep(0.001)` or `asyncio.sleep(0.001)` delay to accurately simulate real network/database I/O latency.
|
|
125
120
|
|
|
126
|
-
|
|
121
|
+
### 1. Read-Write Lock (RWLock) Benchmarks
|
|
127
122
|
|
|
128
|
-
Standard
|
|
123
|
+
Standard locks force threads to queue single-file even if they are only reading data. `rwlocker` unleashes concurrent read access.
|
|
129
124
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
* **🔀 Balanced Pub/Sub (50 Writers, 50 Readers):**
|
|
133
|
-
In mixed waiting and waking scenarios, our Condition locks with the `Write-Pref` strategy ran **~2x FASTER** than the standard library.
|
|
134
|
-
* **📉 Write-Heavy Limit (Stress Test - 100 Writers, 2 Readers):**
|
|
135
|
-
In this brutal scenario where writers constantly block each other and call `notify()`, C-based standard locks utilize their raw speed advantage. `rwlocker`'s Write-Pref model holds its ground neck-and-neck (1.0x) with the standard lock, while the Fair and Read-Pref models intentionally slow down (0.5x - 0.7x) for the sake of maintaining fairness.
|
|
125
|
+
**Synchronous (Thread) RWLock Performance:**
|
|
126
|
+
In the Read-Heavy scenario, standard C-based locks choke the system, whereas `rwlocker` achieves up to **~35x speedup**. Even in Write-Heavy workloads where parallelism isn't inherently possible, `rwlocker` matches or slightly outperforms the C-baseline thanks to its zero-allocation fast-paths.
|
|
136
127
|
|
|
137
|
-
|
|
128
|
+
**Asynchronous (Asyncio) RWLock Performance:**
|
|
129
|
+
`rwlocker` achieves **~30x faster throughput** by allowing thousands of reader tasks to await I/O simultaneously without blocking one another.
|
|
130
|
+
|
|
131
|
+
### 2. Condition Variable (RWCondition) Benchmarks
|
|
132
|
+
|
|
133
|
+
Standard `Condition` variables iterate through all sleeping threads/tasks one by one `O(N)` during a broadcast (`notify_all`), causing massive CPU spikes and "Cache Stampedes". `rwlocker` completely eradicates this with its pure `O(1)` queueing architecture.
|
|
134
|
+
|
|
135
|
+
**Synchronous (Thread) RWCondition Performance:**
|
|
136
|
+
When 100 sleeping readers are awakened simultaneously, `rwlocker` processes them instantly without locking the OS. This architectural leap results in a mind-blowing **~45x speedup** compared to the standard library's `threading.Condition`.
|
|
137
|
+
|
|
138
|
+
**Asynchronous (Asyncio) RWCondition Performance:**
|
|
139
|
+
In event-driven caching systems, waking up hundreds of waiting web requests simultaneously is a major bottleneck. The `AsyncRWCondition` completely bypasses standard asyncio constraints, reaching up to **~45x higher throughput** during massive broadcast scenarios, completely saving the loop from freezing.
|
|
140
|
+
|
|
141
|
+
*(Note: All lock, adapter, and condition classes have passed a massive suite of **315 different unit tests** covering reentrancy, deadlock, timeout, OS interrupts, O(N) leaks, and cancellation safety scenarios with 0 errors, and this entire test suite was completed in just **13.2 seconds**.)*
|
|
138
142
|
|
|
139
143
|
|
|
140
144
|
<br/>
|
|
@@ -469,7 +473,7 @@ Email: TahsinCrs@gmail.com
|
|
|
469
473
|
|
|
470
474
|
[issues-url]: https://github.com/TahsinCr/python-rwlocker/issues
|
|
471
475
|
|
|
472
|
-
[examples-url]: https://github.com/TahsinCr/python-rwlocker/
|
|
476
|
+
[examples-url]: https://github.com/TahsinCr/python-rwlocker/tree/main/examples
|
|
473
477
|
|
|
474
478
|
[license-url]: https://github.com/TahsinCr/python-rwlocker/blob/main/LICENSE
|
|
475
479
|
|
|
@@ -482,11 +486,3 @@ Email: TahsinCrs@gmail.com
|
|
|
482
486
|
[linkedin-url]: https://linkedin.com/in/TahsinCr
|
|
483
487
|
|
|
484
488
|
[x-url]: https://twitter.com/TahsinCrs
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
<!-- File URL -->
|
|
489
|
-
|
|
490
|
-
[lang-tr-url]: https://github.com/TahsinCr/python-rwlocker/blob/main/README_TR.md
|
|
491
|
-
|
|
492
|
-
[lang-en-url]: https://github.com/TahsinCr/python-rwlocker/blob/main/README.md
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|