[3.3] - 04.04.2026
The "Fairness, Protocol Isolation & Benchmark Reliability" update. This version re-introduces carefully scoped internal mixins without changing the public monolithic API, formalizes the distinction between Reader-Phase Fair and Strict Fair scheduling, isolates protocol and queue infrastructure to reduce cross-runtime import cost, hardens downgrade routing in both Thread and Async implementations, and substantially upgrades the benchmark/reporting toolchain and documentation surface.
Added
- Reader-Phase Fair Scheduling Family:
- Added
RWLockReaderPhaseFair/AsyncRWLockReaderPhaseFairand theirReentrantWritervariants as first-class public scheduling strategies. - These variants explicitly preserve the "reader phase may continue to admit later readers" behavior, separating it from strict fair semantics.
- This makes the lock family easier to reason about by giving the old phase-oriented behavior an honest, descriptive public name.
- Added
- Shared Base & Protocol Module (
rwlocker/base.py):- Introduced a central base module to house
Lockable,ConditionLockable, and their async counterparts together with the sharedRWLockBase,AsyncRWLockBase,RWConditionBase, andAsyncRWConditionBaseabstractions. - Added a minimal async future protocol used by queue implementations.
- This removes duplicated protocol and base-class declarations from
thread_rwlock.pyandasync_rwlock.pywhile keeping the runtime-specific modules independent from one another.
- Introduced a central base module to house
- Runtime-Agnostic Queue Module (
rwlocker/queues.py):- Added reusable
ThreadWaitQueue,AsyncWaitQueue,ThreadConditionQueue, andAsyncConditionQueueprimitives as a dedicated internal infrastructure layer. - The queue module is now built around injected primitives/factories rather than direct top-level
threading/asyncioimports, reducing cross-runtime import coupling.
- Added reusable
- Reintroduced Internal State-Machine Mixins (
rwlocker/mixins.py):- Re-added internal mixins to centralize identical non-public logic shared by thread and async lock families.
- The mixins only contain internal
_helpers and state-machine rules; all user-facing methods remain in the concrete monolithic modules. - Follow-up performance measurements showed that the mixin-based indirection affected hot paths only at a negligible level relative to the reliability and maintenance gains, so the shared internal structure was restored.
- This restores maintainability benefits without moving public API methods behind mixin indirection.
- Dedicated Benchmark Base Test Suite:
- Added
tests/benchmark_base_test.pyto validate baseline selection, warmup handling, target rotation, aggregation, and garbage-collection behavior in the benchmark framework.
- Added
Updated
- Strict Fair vs Reader-Phase Fair Semantics:
- Reworked the Fair family so
RWLockFair/AsyncRWLockFairrepresent the strict fair contract. - A reader phase in strict fair mode now snapshots the waiting readers at phase start and prevents late-reader barging ahead of queued writers.
- The older throughput-leaning phase behavior remains available via
ReaderPhaseFair.
- Reworked the Fair family so
- Downgrade Routing and Release Safety (Thread & Async):
- Replaced multi-entry downgrade tracking with a single downgrade-owner marker in async writer proxies.
- Preserved the thread-side single downgrade owner model and hardened it further.
- A successful new write acquisition now clears stale downgrade state, preventing incorrect release routing after flows such as:
write.acquire() -> downgrade() -> read.release() -> write.acquire() -> write.release()
- Async Base Lock Compatibility:
- Corrected the base async context-manager exit path by implementing
__aexit__correctly. - This restores proper drop-in replacement behavior for
async with lock:andasync with cond:.
- Corrected the base async context-manager exit path by implementing
- Condition Infrastructure:
RWConditionandAsyncRWConditionnow use the new queue layer for waiter management while preserving the same external API.- Thread-side condition waiters continue to use a dedicated micro-lock for queue integrity, while async conditions stay event-loop-native.
- Cleaner Internal DRY Boundaries:
- Shared reentrant-writer logic, fair-phase logic, and condition queue hooks were consolidated into mixins only where thread and async behavior truly matched.
- Runtime-specific owner tracking, cancellation behavior, and public method surfaces remain in the concrete modules.
- Base-Class Extraction from Monolithic Modules:
- The shared
RWLockBase/RWConditionBasehierarchy, together with the async base hierarchy, was moved out of the monolithic thread and async modules intorwlocker/base.py. - This keeps the public API intact while reducing duplication and making the concrete modules more focused on runtime-specific proxy and state-machine behavior.
- The shared
- Benchmark Framework Overhaul:
- The benchmark system was reorganized around reusable base classes and scenario objects.
BenchmarkConfignow exposes clear profiles such asfaster(),interactive(), andreporting().- Benchmark handlers were generalized so results can be printed or collected structurally for figure generation.
- Benchmark Console Output:
- Reworked terminal printing to be more compact and readable with better column sizing, baseline highlighting, wrapped long names, and optional ANSI colors.
- Baseline rows are now clearly marked with
BL, and metadata such asOpsis surfaced more cleanly.
- Benchmark Coverage for the Expanded Lock Family:
- Thread and async benchmark scripts now include the
ReaderPhaseFairandFairfamilies together with their reentrant variants. - Condition benchmarks were expanded in the same spirit so all primary scheduling strategies can be compared consistently.
- Thread and async benchmark scripts now include the
- Benchmark Plot Generation:
- Refined the figure-generation pipeline and plotting style handling.
- Plot configuration, label formatting, category mapping, and annotation logic were cleaned up for more legible generated charts.
Fixed
- Late Reader Barging in Strict Fair Locks:
- Fixed the core behavioral bug where late readers could still slip into what was supposed to be a strict fair reader phase.
- Strict fair locks now reserve the phase for the readers already queued at phase start.
- Downgrade Poisoning Bug After Manual Read Release:
- Fixed both thread and async cases where manually releasing the downgraded read side could poison a future
write.release()call. - Regression tests now cover this path explicitly.
- Fixed both thread and async cases where manually releasing the downgraded read side could poison a future
- Async Condition Cancellation / Waiter Cleanup Safety:
- Hardened async condition waiting so waiter cleanup remains correct if release fails or a task is cancelled while waiting.
- This prevents stale waiters and protects the lock state from corruption under
CancelledError.
- Massive
notify_all()Reliability Under RWCondition:- Reworked the condition/fairness interaction so large thread wake-up storms remain stable even under Fair locks.
- Thread condition tests that stress
notify_all()with 100 waiters now pass reliably instead of hanging or timing out.
- Async Benchmark Execution Bug:
- Fixed async benchmark workers so scenarios that return synchronously no longer trigger
TypeError: 'NoneType' object can't be awaited.
- Fixed async benchmark workers so scenarios that return synchronously no longer trigger
- Read-Preference Writer Wakeup Edge Case:
- Eliminated unnecessary writer wakeups in read-preferring paths when readers should continue to dominate.
- Condition Test Stability:
- Updated thread condition tests to fail fast with time-bounded joins instead of appearing to hang indefinitely.
Documentation
- README & PyPI Documentation Refresh:
- Updated
README.md,README_tr.md,README_ru.md, andREADME-pypi.mdto reflect the 3.3 API, benchmark methodology, and lock-family distinctions.
- Updated
- Concrete Class Examples Completed:
- Added or corrected example blocks for concrete
RWLock*,AsyncRWLock*,RWCondition*, andAsyncRWCondition*classes, including the Fair variants that previously lacked the same level of example coverage.
- Added or corrected example blocks for concrete
- Package-Level Documentation Corrections:
- Updated the root package documentation to describe
Reader-Phase FairandStrict Fairseparately. - Corrected outdated class-name references and aligned downgrade behavior notes with the new single-marker routing model.
- Updated the root package documentation to describe
- Async Downgrade Documentation Accuracy:
- Removed incorrect
await lock.write.downgrade()-style guidance. - Async downgrade remains a synchronous state transition, and the docs now reflect that consistently.
- Removed incorrect
Testing
- Expanded Lock Regression Tests:
- Added or strengthened tests for:
- late reader barging behavior,
- reader-phase
notify_all()usage, - downgrade-started reader phases,
- stale downgrade marker cleanup,
- cross-thread / cross-task release protection,
- cancellation safety in async wait paths.
- Added or strengthened tests for:
- Expanded Condition Stress Tests:
- Strengthened condition tests around timeout handling,
notify(n),notify_all(), and downgrade release safety for both thread and async variants.
- Strengthened condition tests around timeout handling,
- Validation Result:
- The full local test suite now runs cleanly at 412 tests for version 3.3.