rwlocker 3.3__tar.gz → 3.4.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.3/rwlocker.egg-info → rwlocker-3.4.2}/PKG-INFO +145 -58
- {rwlocker-3.3 → rwlocker-3.4.2}/README-pypi.md +138 -54
- {rwlocker-3.3 → rwlocker-3.4.2}/pyproject.toml +11 -6
- rwlocker-3.4.2/rwlocker/__init__.py +52 -0
- {rwlocker-3.3 → rwlocker-3.4.2}/rwlocker/async_rwlock.py +91 -38
- {rwlocker-3.3 → rwlocker-3.4.2}/rwlocker/base.py +11 -15
- {rwlocker-3.3 → rwlocker-3.4.2}/rwlocker/mixins.py +57 -10
- rwlocker-3.4.2/rwlocker/py.typed +1 -0
- {rwlocker-3.3 → rwlocker-3.4.2}/rwlocker/queues.py +5 -10
- {rwlocker-3.3 → rwlocker-3.4.2}/rwlocker/thread_rwlock.py +91 -40
- {rwlocker-3.3 → rwlocker-3.4.2/rwlocker.egg-info}/PKG-INFO +145 -58
- {rwlocker-3.3 → rwlocker-3.4.2}/rwlocker.egg-info/SOURCES.txt +2 -0
- rwlocker-3.4.2/rwlocker.egg-info/requires.txt +5 -0
- rwlocker-3.3/rwlocker/__init__.py +0 -67
- {rwlocker-3.3 → rwlocker-3.4.2}/LICENSE +0 -0
- {rwlocker-3.3 → rwlocker-3.4.2}/MANIFEST.in +0 -0
- {rwlocker-3.3 → rwlocker-3.4.2}/rwlocker.egg-info/dependency_links.txt +0 -0
- {rwlocker-3.3 → rwlocker-3.4.2}/rwlocker.egg-info/top_level.txt +0 -0
- {rwlocker-3.3 → rwlocker-3.4.2}/setup.cfg +0 -0
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rwlocker
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.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
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/TahsinCr/python-rwlocker
|
|
8
8
|
Project-URL: Repository, https://github.com/TahsinCr/python-rwlocker
|
|
9
9
|
Project-URL: Bug Tracker, https://github.com/TahsinCr/python-rwlocker/issues
|
|
10
10
|
Project-URL: Changelog, https://github.com/TahsinCr/python-rwlocker/blob/main/CHANGELOG.md
|
|
11
11
|
Keywords: rwlock,read-write-lock,thread,concurrency,lock,thread-safe,async,thundering-herd,rwcondition,condition
|
|
12
|
-
Classifier: Development Status ::
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
15
|
Classifier: Typing :: Typed
|
|
16
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
17
16
|
Classifier: Operating System :: OS Independent
|
|
18
17
|
Classifier: Programming Language :: Python :: 3
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -25,6 +24,10 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
25
24
|
Requires-Python: >=3.9
|
|
26
25
|
Description-Content-Type: text/markdown
|
|
27
26
|
License-File: LICENSE
|
|
27
|
+
Provides-Extra: benchmark
|
|
28
|
+
Requires-Dist: pandas; extra == "benchmark"
|
|
29
|
+
Requires-Dist: matplotlib; extra == "benchmark"
|
|
30
|
+
Requires-Dist: seaborn; extra == "benchmark"
|
|
28
31
|
Dynamic: license-file
|
|
29
32
|
|
|
30
33
|
[![Contributors][contributors-shield]][contributors-url]
|
|
@@ -34,6 +37,8 @@ Dynamic: license-file
|
|
|
34
37
|
[![MIT License][license-shield]][license-url]
|
|
35
38
|
[![LinkedIn][linkedin-shield]][linkedin-url]
|
|
36
39
|
|
|
40
|
+
[English][lang-en-url] | [Türkçe][lang-tr-url] | [Русский][lang-ru-url]
|
|
41
|
+
|
|
37
42
|
|
|
38
43
|
|
|
39
44
|
<!-- About -->
|
|
@@ -60,22 +65,22 @@ Advanced, High-Performance, and State-Machine Based Synchronous/Asynchronous Rea
|
|
|
60
65
|
|
|
61
66
|
Standard locks in Python (`Lock`, `RLock`) are **Exclusive** locks. Even if 100 readers (e.g., threads fetching data from a database) arrive at the gate, they are forced to execute these operations sequentially, one by one.
|
|
62
67
|
|
|
63
|
-
`rwlocker`, on the other hand, is based on a **Shared** reading logic. While writer locks are exclusive, reader locks allow
|
|
68
|
+
`rwlocker`, on the other hand, is based on a **Shared** reading logic. While writer locks are exclusive, reader locks allow multiple threads or tasks to access data concurrently when their critical sections permit shared access. This can improve throughput when shared critical sections spend time in I/O or otherwise yield, but it does not guarantee a speedup for every workload.
|
|
64
69
|
|
|
65
70
|
### 🚀 What about Condition Variables?
|
|
66
|
-
|
|
71
|
+
`rwlocker` conditions keep waiters in `collections.deque` queues. Enqueue and FIFO dequeue are amortized O(1); notifying N waiters is O(N), because each waiter must be signalled. Removing a timed-out or cancelled waiter can also take O(N). The queue operations do not block the event loop while a task is waiting.
|
|
67
72
|
|
|
68
73
|
### ✨ Key Features
|
|
69
74
|
|
|
70
|
-
* **Both Thread and Asyncio Support:** You can manage both standard OS threads (`rwlocker.thread_rwlock`) and event-loop based tasks (`rwlocker.async_rwlock`) using
|
|
75
|
+
* **Both Thread and Asyncio Support:** You can manage both standard OS threads (`rwlocker.thread_rwlock`) and event-loop based tasks (`rwlocker.async_rwlock`) using corresponding synchronous and asynchronous interfaces.
|
|
71
76
|
* **Smart Proxy Architecture:** Intuitive usage of `with` and `async with` context managers via `.read` and `.write` proxies.
|
|
72
77
|
* **Atomic Downgrading:** The ability to instantly downgrade a Write lock to a Read lock (`downgrade()`) without completely releasing the lock, preventing other writers from slipping in.
|
|
73
|
-
* **
|
|
74
|
-
* **
|
|
75
|
-
* **
|
|
76
|
-
* **
|
|
77
|
-
* **
|
|
78
|
-
* **Standard Adapters:**
|
|
78
|
+
* **Writer Reentrancy:** `ReentrantWriter` variants allow the owning thread or task to acquire nested write locks and a read lock; other readers join only after `.downgrade()` opens shared access. A current reader may reacquire `.read` while writers wait; acquiring `.write` while holding `.read` raises `RuntimeError` to prevent self-deadlock.
|
|
79
|
+
* **Queued Condition Waiters:** Waiters are stored in FIFO deques. Normal enqueue/dequeue operations are amortized O(1); notifying all waiters and removing an arbitrary timed-out waiter are O(N).
|
|
80
|
+
* **Async Cancellation Cleanup:** Cancellation removes a task's waiter and condition waits reacquire the associated lock before propagating cancellation.
|
|
81
|
+
* **Lock-Style Interface:** Direct lock operations use the exclusive `.write` proxy. Shared locking is available through `.read`; some signatures and observable behavior differ from the standard lock classes, so compatibility should be checked for each integration.
|
|
82
|
+
* **Uncontended Fast Paths:** An uncontended acquisition does not allocate a waiter object. Contended acquisition and notification still perform normal Python and scheduler work.
|
|
83
|
+
* **Standard Adapters:** Provides lock and condition wrappers with the same high-level `.read` and `.write` access shape for Dependency Injection workflows that do not need the RWLock scheduling strategies.
|
|
79
84
|
* *Lock Adapters:* `Lock` (Thread), `AsyncLock` (Asyncio)
|
|
80
85
|
* *Condition Adapters:* `Condition` (Thread), `AsyncCondition` (Asyncio)
|
|
81
86
|
|
|
@@ -85,62 +90,79 @@ You can select the right lock strategy based on your system's bottleneck profile
|
|
|
85
90
|
|
|
86
91
|
| Strategy Type | Class Name (Thread / Async) | Description | When to Use? |
|
|
87
92
|
| --- | --- | --- | --- |
|
|
88
|
-
| **Writer-Preferring** | `RWLockWrite` / `AsyncRWLockWrite` | Forbids new readers from entering if there is a waiting writer.
|
|
93
|
+
| **Writer-Preferring** | `RWLockWrite` / `AsyncRWLockWrite` | Forbids new readers from entering if there is a waiting writer. Prioritizes waiting writers and can reduce writer starvation while they continue making progress. | To prevent writers from being overwhelmed in read-heavy systems. |
|
|
89
94
|
| **Reader-Preferring** | `RWLockRead` / `AsyncRWLockRead` | Continuously allows new readers in, even if writers are waiting. Provides maximum parallelism. | In cache structures where write operations are very rare or non-critical. |
|
|
90
95
|
| **Reader-Phase Fair** | `RWLockReaderPhaseFair` / `AsyncRWLockReaderPhaseFair` | Alternates between reader and writer phases, but late readers may still join an already-open reader phase for higher read throughput. | When you want bounded fairness without fully freezing each reader batch. |
|
|
91
|
-
| **Fair** | `RWLockFair` / `AsyncRWLockFair` | Freezes each reader phase at phase start so late readers cannot cut in front of an already-queued writer.
|
|
92
|
-
> 💡 **Condition Compatibility:**
|
|
96
|
+
| **Fair** | `RWLockFair` / `AsyncRWLockFair` | Freezes each reader phase at phase start so late readers cannot cut in front of an already-queued writer. Designed to reduce starvation when lock holders continue to make progress. | In bidirectional traffic where ordered writer access matters. |
|
|
97
|
+
> 💡 **Condition Compatibility:** `RWCondition` and `AsyncRWCondition` accept the lock strategies above. Choose a strategy based on its reader/writer scheduling behavior.
|
|
93
98
|
<br/>
|
|
94
99
|
|
|
95
100
|
## ⚙️ Architectural Limitations
|
|
96
101
|
|
|
97
102
|
Engineering facts developers need to know when using this library:
|
|
98
103
|
|
|
104
|
+
Set `RWLOCKER_PYTHON_STANDARD` and `RWLOCKER_PYTHON_FREE_THREADED` to choose interpreters for benchmark collection.
|
|
105
|
+
|
|
99
106
|
1. **The CPU-Bound vs I/O-Bound Reality:**
|
|
100
|
-
`rwlocker` derives its power from the moments when Python's GIL (Global Interpreter Lock) is released (Network requests, Database queries, File I/O, etc.). If you are looking for a lock for purely heavy mathematical computations (CPU-Bound) that do not involve I/O yields like `time.sleep()`, you will not achieve true parallelism due to the GIL, and
|
|
107
|
+
`rwlocker` derives its power from the moments when Python's GIL (Global Interpreter Lock) is released (Network requests, Database queries, File I/O, etc.). If you are looking for a lock for purely heavy mathematical computations (CPU-Bound) that do not involve I/O yields like `time.sleep()`, you will not achieve true parallelism due to the GIL, and a standard `threading.Lock` may have lower acquisition overhead. These locks are most useful when read sections can overlap while waiting for I/O or other operations that yield.
|
|
101
108
|
2. **Circular References:**
|
|
102
109
|
Lock classes establish a circular reference graph (Lock -> Proxy -> Lock) when creating smart proxy objects (`.read` and `.write`). This design is intentional. Memory cleanup (Garbage Collection) is safely handled by Python's Cyclic GC engine, not by `__del__`.
|
|
103
|
-
3. **
|
|
104
|
-
|
|
110
|
+
3. **Reentrant Writer Behavior:**
|
|
111
|
+
`ReentrantWriter` variants allow the owning thread/task to acquire nested write locks and a read lock. Other readers can join only after `.downgrade()` opens shared access. A current reader may reacquire `.read` while writers wait; acquiring `.write` while holding `.read` raises `RuntimeError` to prevent self-deadlock.
|
|
105
112
|
4. **The Cost of Fairness:**
|
|
106
|
-
|
|
107
|
-
5. **Condition
|
|
108
|
-
|
|
109
|
-
6. **
|
|
110
|
-
|
|
113
|
+
The `Fair` strategy schedules queued readers and writers in phases to reduce starvation while participants continue making progress. This ordering can reduce throughput in some workloads. In the recorded condition workload, the Fair variant was slower than the exclusive-lock standard `threading.Condition` baseline; that comparison includes different locking semantics and does not isolate fairness overhead.
|
|
114
|
+
5. **Condition Queue Costs:**
|
|
115
|
+
Each waiting thread/task has a waiter object. `notify_all()` signals every waiter and therefore takes O(N) time; arbitrary timeout/cancellation removal also takes O(N). Memory use grows with the number of waiters. `Condition.wait()` requires exactly one lock acquisition; nested acquisition depths raise `RuntimeError` because recursive depth is not saved and restored.
|
|
116
|
+
6. **Exclusive Fallback:**
|
|
117
|
+
Direct lock operations (such as `with lock:`) use the exclusive write proxy. This is a concurrency default, not a security boundary; use `.read` and `.write` explicitly when access mode matters.
|
|
111
118
|
|
|
112
119
|
<br/>
|
|
113
120
|
|
|
114
121
|
## 📊 Performance and Benchmark Results
|
|
115
122
|
|
|
116
|
-
The
|
|
123
|
+
The charts below report end-to-end results for the included network/database-style I/O workloads. They compare complete workloads and do not isolate lock acquisition or notification cost.
|
|
117
124
|
|
|
118
125
|
**🖥️ 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.
|
|
119
126
|
|
|
120
|
-
**🧪 Methodology:**
|
|
127
|
+
**🧪 Methodology:** Workers are created before the timed run and released through a synchronization barrier/event. I/O-bound scenarios use a 1 ms sleep per operation for 10 iterations. Results are workload measurements and can vary with scheduling, interpreter, and machine load; they do not have zero measurement error. Reporting runs store each elapsed time, variance, median absolute deviation, quartiles, and a bootstrap 95% confidence interval for the mean.
|
|
121
128
|
|
|
122
129
|
### 1. Read-Write Lock (RWLock) Benchmarks
|
|
123
130
|
|
|
124
131
|
Standard locks force threads to queue single-file even if they are only reading data. `rwlocker` unleashes concurrent read access.
|
|
125
132
|
|
|
126
133
|
**Synchronous (Thread) RWLock Performance:**
|
|
127
|
-
In the
|
|
134
|
+
In the committed read-heavy workload data, shared readers complete sooner than readers serialized by a standard exclusive lock (up to ~35x in the recorded run). This measures workload completion time from different locking semantics, not primitive acquisition speed. The write-heavy results are workload-specific and are not evidence of a general speed advantage.
|
|
135
|
+
<p align="center">
|
|
136
|
+
<img src="./figures/sync_rwlock.svg" alt="Sync RWLock Benchmark" width="100%"/>
|
|
137
|
+
</p>
|
|
138
|
+
<br>
|
|
128
139
|
|
|
129
140
|
**Asynchronous (Asyncio) RWLock Performance:**
|
|
130
|
-
|
|
141
|
+
Asynchronous tasks run on one event loop in this benchmark. The plots show the available interpreter environments rather than selecting the best result. Read-heavy throughput reflects the ability of multiple readers to await I/O concurrently.
|
|
142
|
+
<p align="center">
|
|
143
|
+
<img src="./figures/async_rwlock.svg" alt="Async RWLock Benchmark" width="100%"/>
|
|
144
|
+
</p>
|
|
145
|
+
<br>
|
|
131
146
|
|
|
132
147
|
### 2. Condition Variable (RWCondition) Benchmarks
|
|
133
148
|
|
|
134
|
-
|
|
149
|
+
These condition benchmarks measure end-to-end read/write workload throughput, including shared-read concurrency, predicate checks, scheduling, and the simulated I/O. They do not isolate `notify_all()` overhead. The queue signals each waiter, so `notify_all()` is O(N).
|
|
135
150
|
|
|
136
151
|
**Synchronous (Thread) RWCondition Performance:**
|
|
137
|
-
|
|
152
|
+
The recorded ~45x result compares the complete RWCondition workload with the exclusive-lock `threading.Condition` baseline. It does not mean that `notify_all()` itself is 45x faster.
|
|
153
|
+
<p align="center">
|
|
154
|
+
<img src="./figures/sync_rwcondition.svg" alt="Sync RWCondition Benchmark" width="100%"/>
|
|
155
|
+
</p>
|
|
156
|
+
<br>
|
|
138
157
|
|
|
139
158
|
**Asynchronous (Asyncio) RWCondition Performance:**
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
159
|
+
The async condition result is also an end-to-end workload comparison, not a measurement of isolated notification cost. The event loop still processes each waiter.
|
|
160
|
+
<p align="center">
|
|
161
|
+
<img src="./figures/async_rwcondition.svg" alt="Async RWCondition Benchmark" width="100%"/>
|
|
162
|
+
</p>
|
|
163
|
+
<br>
|
|
143
164
|
|
|
165
|
+
The test suite covers lock strategies, conditions, timeouts, reentrancy, and cancellation. Passing tests are useful regression evidence, but do not by themselves prove correctness for every schedule or workload.
|
|
144
166
|
|
|
145
167
|
<br/>
|
|
146
168
|
|
|
@@ -148,9 +170,10 @@ In event-driven caching systems, waking up hundreds of waiting web requests simu
|
|
|
148
170
|
|
|
149
171
|
### 🛠️ Dependencies
|
|
150
172
|
|
|
151
|
-
* No
|
|
173
|
+
* No runtime dependencies.
|
|
174
|
+
* Optional benchmark plotting: `pip install rwlocker[benchmark]`.
|
|
152
175
|
* Only Python Standard Library (`threading`, `asyncio`, `typing`, `collections`).
|
|
153
|
-
*
|
|
176
|
+
* Supports Python 3.9–3.14; these versions are included in the CI test matrix.
|
|
154
177
|
|
|
155
178
|
### 📦 Installation
|
|
156
179
|
|
|
@@ -172,7 +195,7 @@ The library has zero external dependencies and works directly with Python's core
|
|
|
172
195
|
|
|
173
196
|
#### 1. High-Concurrency In-Memory Cache (Read-Heavy)
|
|
174
197
|
|
|
175
|
-
|
|
198
|
+
Allows independent readers to proceed concurrently in a web server workload.
|
|
176
199
|
|
|
177
200
|
```python
|
|
178
201
|
import threading
|
|
@@ -234,8 +257,9 @@ class TransactionLedger:
|
|
|
234
257
|
self._dispatch_audit_event(self._balance)
|
|
235
258
|
|
|
236
259
|
finally:
|
|
237
|
-
#
|
|
238
|
-
|
|
260
|
+
# The writer proxy routes release to read after downgrade and
|
|
261
|
+
# still releases write correctly if an earlier operation failed.
|
|
262
|
+
self._lock.write.release()
|
|
239
263
|
|
|
240
264
|
def _dispatch_audit_event(self, balance: float):
|
|
241
265
|
print(f"Audit Report Dispatched. New Balance: {balance}")
|
|
@@ -245,7 +269,7 @@ class TransactionLedger:
|
|
|
245
269
|
|
|
246
270
|
#### 3. JWT Token Refresh (Thundering Herd Solution)
|
|
247
271
|
|
|
248
|
-
|
|
272
|
+
Coordinates concurrent tasks that need to refresh an expired token.
|
|
249
273
|
|
|
250
274
|
```python
|
|
251
275
|
import asyncio
|
|
@@ -280,7 +304,7 @@ class AuthTokenManager:
|
|
|
280
304
|
|
|
281
305
|
#### 4. High-Frequency Telemetry (Fair Distribution)
|
|
282
306
|
|
|
283
|
-
Data arrives from a sensor 100 times per second (Write), and 200 WebSockets read this data (Read). The Fair architecture
|
|
307
|
+
Data arrives from a sensor 100 times per second (Write), and 200 WebSockets read this data (Read). The Fair architecture is intended to reduce starvation risk while lock holders continue making progress.
|
|
284
308
|
|
|
285
309
|
```python
|
|
286
310
|
import asyncio
|
|
@@ -289,7 +313,7 @@ from rwlocker.async_rwlock import AsyncRWLockFair
|
|
|
289
313
|
|
|
290
314
|
class TelemetryDispatcher:
|
|
291
315
|
def __init__(self):
|
|
292
|
-
# Fair
|
|
316
|
+
# Fair schedules queued readers and writers in phases while holders continue making progress.
|
|
293
317
|
self._lock = AsyncRWLockFair()
|
|
294
318
|
self._state = {"alt": 0.0, "lat": 0.0, "lon": 0.0}
|
|
295
319
|
|
|
@@ -316,7 +340,7 @@ class TelemetryDispatcher:
|
|
|
316
340
|
|
|
317
341
|
#### 5. Event-Driven Cache Refresh (Thundering Herd Protection)
|
|
318
342
|
|
|
319
|
-
|
|
343
|
+
Condition waiters sleep without blocking the event loop. `notify_all()` signals each waiter and has O(N) cost; total workload throughput also depends on reader concurrency and task scheduling.
|
|
320
344
|
|
|
321
345
|
```python
|
|
322
346
|
import asyncio
|
|
@@ -326,28 +350,37 @@ class GlobalConfigCache:
|
|
|
326
350
|
def __init__(self):
|
|
327
351
|
# We use a Read-Pref lock because reading is extremely dense
|
|
328
352
|
self._cond = AsyncRWCondition(AsyncRWLockRead())
|
|
329
|
-
self._config = {}
|
|
353
|
+
self._config = {"theme": "light", "version": 1}
|
|
330
354
|
self._is_refreshing = False
|
|
331
355
|
|
|
332
356
|
async def get_config(self) -> dict:
|
|
333
|
-
"""Called by
|
|
357
|
+
"""Called by concurrent requests."""
|
|
334
358
|
async with self._cond.read:
|
|
335
359
|
# If a DB update is in progress, sleep and wait safely instead of hammering the DB.
|
|
336
360
|
# The wait_for method automatically handles Spurious Wakeup scenarios.
|
|
337
361
|
await self._cond.read.wait_for(lambda: not self._is_refreshing)
|
|
338
|
-
return self._config
|
|
362
|
+
return dict(self._config)
|
|
339
363
|
|
|
340
364
|
async def force_refresh_from_db(self) -> None:
|
|
341
|
-
"""
|
|
365
|
+
"""Serializes refresh requests triggered via a webhook."""
|
|
342
366
|
async with self._cond.write:
|
|
367
|
+
await self._cond.write.wait_for(lambda: not self._is_refreshing)
|
|
343
368
|
self._is_refreshing = True
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
369
|
+
|
|
370
|
+
try:
|
|
371
|
+
# Simulate a slow database query without holding the lock.
|
|
372
|
+
await asyncio.sleep(0.5)
|
|
373
|
+
async with self._cond.write:
|
|
374
|
+
self._config = {"theme": "dark", "version": self._config["version"] + 1}
|
|
375
|
+
self._is_refreshing = False
|
|
376
|
+
# Signals each queued waiter (O(N)).
|
|
377
|
+
self._cond.write.notify_all()
|
|
378
|
+
except BaseException:
|
|
379
|
+
async with self._cond.write:
|
|
380
|
+
if self._is_refreshing:
|
|
381
|
+
self._is_refreshing = False
|
|
382
|
+
self._cond.write.notify_all()
|
|
383
|
+
raise
|
|
351
384
|
```
|
|
352
385
|
|
|
353
386
|
|
|
@@ -362,7 +395,7 @@ from rwlocker.thread_rwlock import RWLockFair, RWCondition
|
|
|
362
395
|
|
|
363
396
|
class ImageProcessingQueue:
|
|
364
397
|
def __init__(self):
|
|
365
|
-
# Fair strategy to
|
|
398
|
+
# Fair strategy to schedule waiting producers and consumers in phases
|
|
366
399
|
self._cond = RWCondition(RWLockFair())
|
|
367
400
|
self._queue = deque()
|
|
368
401
|
|
|
@@ -377,9 +410,9 @@ class ImageProcessingQueue:
|
|
|
377
410
|
|
|
378
411
|
def consume_job(self):
|
|
379
412
|
"""Consumer: Sleeps until a job arrives, then picks it up."""
|
|
380
|
-
with self._cond.
|
|
413
|
+
with self._cond.write:
|
|
381
414
|
# Wait safely if there are no jobs in the queue
|
|
382
|
-
self._cond.
|
|
415
|
+
self._cond.write.wait_for(lambda: len(self._queue) > 0)
|
|
383
416
|
job = self._queue.popleft()
|
|
384
417
|
|
|
385
418
|
# Perform the heavy processing AFTER releasing the lock.
|
|
@@ -387,9 +420,9 @@ class ImageProcessingQueue:
|
|
|
387
420
|
|
|
388
421
|
```
|
|
389
422
|
|
|
390
|
-
#### 7.
|
|
423
|
+
#### 7. Lock-Style Interface Compatibility
|
|
391
424
|
|
|
392
|
-
|
|
425
|
+
Direct lock methods route to the exclusive `.write` proxy. Check method signatures and observable semantics before passing an instance to code written for standard lock or condition classes; `.read` and `.write` expose the modes explicitly.
|
|
393
426
|
|
|
394
427
|
```python
|
|
395
428
|
import threading
|
|
@@ -421,6 +454,50 @@ See the [open issues][issues-url] for a full list of proposed features (and know
|
|
|
421
454
|
|
|
422
455
|
<br/>
|
|
423
456
|
|
|
457
|
+
## 🤝 Contributing
|
|
458
|
+
|
|
459
|
+
The open-source community is the perfect place to push the boundaries of such low-level, high-performance libraries. Any contributions you make to render `rwlocker` faster, safer, or more capable are greatly appreciated!
|
|
460
|
+
|
|
461
|
+
We are especially looking forward to your contributions in the following areas:
|
|
462
|
+
|
|
463
|
+
* ⚡ **Performance Optimizations:** Algorithmic approaches that will further reduce overhead costs.
|
|
464
|
+
* 🏗️ **Scenario-Specific Enhancements:** Creating and developing variants of lock engines optimized for different scenarios.
|
|
465
|
+
* 🐛 **Edge-Case Testing:** New and rigorous unit tests to detect deadlock or starvation scenarios.
|
|
466
|
+
|
|
467
|
+
If you have a great idea or solution, please follow the steps below to create a **Pull Request (PR)**. You can also open an Issue with the "enhancement" tag to suggest a new feature.
|
|
468
|
+
|
|
469
|
+
Don't forget to give the project a **Star (⭐)** on the top right if you found it useful. Thanks for your support!
|
|
470
|
+
|
|
471
|
+
### 🛠️ Contribution Steps
|
|
472
|
+
|
|
473
|
+
1. **Fork** the project to your own account.
|
|
474
|
+
2. Create your Feature Branch:
|
|
475
|
+
```sh
|
|
476
|
+
git checkout -b feature/AmazingFeature
|
|
477
|
+
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
3. **Commit** your changes (Make sure to use descriptive messages):
|
|
482
|
+
```sh
|
|
483
|
+
git commit -m 'feat: Added a new O(1) cost optimization for AsyncRWLock'
|
|
484
|
+
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
4. **Push** to the Branch:
|
|
489
|
+
```sh
|
|
490
|
+
git push origin feature/AmazingFeature
|
|
491
|
+
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
5. Open a **Pull Request** on this repository.
|
|
496
|
+
|
|
497
|
+
> ⚠️ **Important Developer Note:** The `rwlocker` architecture is highly sensitive to *deadlock*, *OS-Interrupts*, and *reentrancy* scenarios. Before opening a PR, run the test suite on the supported Python versions listed in CI and review concurrency changes for scheduling-dependent behavior.
|
|
498
|
+
|
|
499
|
+
<br/>
|
|
500
|
+
|
|
424
501
|
## 🙏 Acknowledgments and License
|
|
425
502
|
|
|
426
503
|
This project is fully open-source under the **MIT License** ([License][license-url]).
|
|
@@ -487,3 +564,13 @@ Email: TahsinCrs@gmail.com
|
|
|
487
564
|
[linkedin-url]: https://linkedin.com/in/TahsinCr
|
|
488
565
|
|
|
489
566
|
[x-url]: https://twitter.com/TahsinCrs
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
<!-- File URL -->
|
|
571
|
+
|
|
572
|
+
[lang-tr-url]: https://github.com/TahsinCr/python-rwlocker/blob/main/README_tr.md
|
|
573
|
+
|
|
574
|
+
[lang-en-url]: https://github.com/TahsinCr/python-rwlocker/blob/main/README.md
|
|
575
|
+
|
|
576
|
+
[lang-ru-url]: https://github.com/TahsinCr/python-rwlocker/blob/main/README_ru.md
|