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.
@@ -1,19 +1,18 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rwlocker
3
- Version: 3.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 :: 5 - Production/Stable
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 thousands of threads or tasks to access data simultaneously without blocking each other. It unleashes the true potential of the system, especially during I/O (Network/Disk/Database) operations where the GIL (Global Interpreter Lock) is released.
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
- Standard `Condition` structures in the library wake up all waiting threads/tasks with an O(N) time complexity (scanning them one by one) when `notify_all()` is called. This creates a **"Cache Stampede"** that locks up the processor in scenarios where hundreds of readers wake up simultaneously. `rwlocker` operates entirely on a `deque` (Thread) and `dict` (Asyncio) based queue architecture. It eliminates stampedes at the architectural level by waking up waiting tasks with pure **O(1) time complexity** without blocking the OS or event-loop.
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 the exact same API logic.
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
- * **Safe Reentrancy:** O(1) memory pointer tracking allowing the same thread or task to repeatedly acquire a write lock without causing a Deadlock.
74
- * **Pure O(1) Condition Variables:** Unlike standard `Condition` structures, it does not iterate through the waiting list one by one during `notify_all()` calls (bypassing O(N) scanning costs). Thanks to its customized C-level micro-queue architecture, it wakes up thousands of tasks instantly without causing a "Cache Stampede" and choking the CPU.
75
- * **Asynchronous Cancellation Safety & Shielding:** Full resilience against task cancellations (`CancelledError`) in the `asyncio` environment. If a task is cancelled while waiting for a lock or inside `Condition.wait()`, the system state is never corrupted. The lock is safely recovered, no "zombie" waiters are left behind, and wait queues remain perfectly clean.
76
- * **100% Drop-in Replacement:** You can inject your advanced locks (`RWLockFair`, etc.) and conditions (`RWCondition`, etc.) directly into third-party libraries (SQLAlchemy, requests, FastAPI, etc.) expecting standard `threading.Lock`, `asyncio.Lock`, `threading.Condition`, or `asyncio.Condition` instances without making any code changes. Standard API calls (e.g., `lock.acquire()`, `cond.wait()`) are automatically and safely routed to the `.write` (exclusive) proxy.
77
- * **"Happy Path" Performance Isolation:** A next-generation architecture that completely bypasses O(N) cost garbage cleanup operations upon successful lock and queue wake-ups. It provides absolute protection against OS-Interrupts and timeouts while completing successful wake-ups with zero CPU overhead.
78
- * **Standard Adapters:** Includes standard wrappers for Dependency Injection workflows where you want to maintain the exact same architectural signature (`.read` and `.write`) but do not require advanced lock strategies.
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. Prevents writer starvation. | To prevent writers from being overwhelmed in read-heavy systems. |
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. Prevents starvation for both sides with stricter ordering. | In high-frequency, bidirectional traffic (MAVLink, WebSockets, etc.) where deterministic writer latency matters. |
92
- > 💡 **Condition Compatibility:** The `RWCondition` and `AsyncRWCondition` classes in the library are designed to encapsulate all the lock strategies mentioned above (Dependency Injection). You can choose the lock that best fits your system and transform it into a state machine running at pure O(1) speed.
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 the C-based standard `threading.Lock` will be slightly faster. **RWLock's true battlefield is I/O operations.**
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. **Strict Nested Write Locks:**
104
- In `ReentrantWriter` variants, only "Write" locks can be nested. If a writer wants to acquire a reader lock, it cannot do so implicitly; it must explicitly call the `.downgrade()` method. This is a strict architectural decision made to prevent deadlocks at the structural level.
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
- If you use the `Fair` strategy, the system forces a strict order-based context switch between readers and writers to guarantee that no one starves (No Starvation). Especially in **`RWCondition`** uses and write-heavy scenarios, this effort to maintain fair order causes a certain slowdown compared to the standard, rule-less C-based `Condition` object (this is why Fair scores 0.50x in benchmarks). This is not a bug or a lack of optimization; it is the engineering price paid to ensure "fairness".
107
- 5. **Condition Memory vs CPU Trade-off:**
108
- While a standard `threading.Condition` keeps a simple C-level counter in the background, `rwlocker` stores a tiny `Lock` or `asyncio.Future` object in memory for each waiting task/thread to guarantee O(1) wake-up speed. This completely resolves CPU bottlenecks (Cache Stampede), but in extreme cases where tens of thousands of tasks are waiting, it creates a small memory footprint in RAM.
109
- 6. **Drop-in Security Assumption:**
110
- If you use the lock objects directly like a standard lock without specifying the `.read` or `.write` proxies (e.g., `with lock:` or `await cond.wait()`), the system automatically acquires the **Write (Exclusive)** lock to ensure backward compatibility and absolute data security. This is a "Secure by Default" approach established to prevent external libraries from corrupting data.
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 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.
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:** 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 across 10 consecutive iterations to accurately simulate real network/database I/O latency.
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 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.
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
- `rwlocker` achieves **~30x faster throughput** by allowing thousands of reader tasks to await I/O simultaneously without blocking one another.
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
- 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.
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
- When 100 sleeping readers are awakened simultaneously, `rwlocker` processes them instantly without locking the OS. This architectural leap results in a **~45x speedup** compared to the standard library's `threading.Condition`.
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
- 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.
141
-
142
- *(Note: All lock, adapter, and condition classes have passed a massive suite of **412 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 **15.7 seconds**.)*
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 external dependencies.
173
+ * No runtime dependencies.
174
+ * Optional benchmark plotting: `pip install rwlocker[benchmark]`.
152
175
  * Only Python Standard Library (`threading`, `asyncio`, `typing`, `collections`).
153
- * Fully compatible with Python 3.9+.
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
- Prevents readers from waiting for each other in a web server handling thousands of requests.
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
- # Since we downgraded, we must now release the READ lock.
238
- self._lock.read.release()
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
- Prevents hundreds of tasks waking up simultaneously to refresh an expired token (Thundering Herd stampede) from crashing the auth server.
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 prevents both sides from starving.
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 prevents read and write intensities from choking each other.
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
- If thousands of tasks try to fetch an expired token from the database simultaneously, the DB crashes. With `AsyncRWCondition`, while 1 task updates the data, the other 999 tasks safely sleep without choking the CPU (at O(1) speed) and are awakened all at once afterward.
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 thousands of concurrent requests."""
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
- """Runs exclusively when triggered via a webhook."""
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
- await asyncio.sleep(0.5) # Slow Database query simulation
346
- self._config = {"theme": "dark", "version": 2}
347
- self._is_refreshing = False
348
-
349
- # Wakes up THOUSANDS of waiting reader tasks at O(1) speed. No stampede!
350
- self._cond.write.notify_all()
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 prevent Producers and Consumers from crushing each other
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.read:
413
+ with self._cond.write:
381
414
  # Wait safely if there are no jobs in the queue
382
- self._cond.read.wait_for(lambda: len(self._queue) > 0)
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. 100% Drop-in Replacement Compatibility
423
+ #### 7. Lock-Style Interface Compatibility
391
424
 
392
- Inject the power of `rwlocker` into your system without changing your legacy code or third-party libraries that expect a standard `threading.Lock` or `asyncio.Lock`.
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