Repository navigation
fix(utilities): stop starving a waiting writer in RWLock - #8774
MindOfqc (MindOfqc) wants to merge 4 commits into
Conversation
Strix Security ReviewWarning This pull request has 81 commits after the last Strix review ( ✅ No open security findings. PR summary: Fixes an RWLock liveness bug by making readers wait behind a writer that is already queued (`_waiting_writers`), and adding per-thread read-depth tracking so a thread that already holds a read lock can re-enter it instead of deadlocking behind that writer. Two regression tests accompany the change. Review summaryReviewed the RWLock writer-priority change ( Ruled out (4)
Review 1 on this PR · Reviewed by Strix |
fbec5b8 to
a2586c8
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3875309. Configure here.
| self._cond.wait() | ||
| self._writer = True | ||
| finally: | ||
| self._waiting_writers -= 1 |
There was a problem hiding this comment.
Aborted writer starves waiting readers
Medium Severity
w_acquire drops _waiting_writers in finally when Condition.wait() raises, but never calls notify_all. Readers already blocked in r_acquire on that counter stay asleep. New readers can keep _readers above zero, so those waiters may never wake.
Reviewed by Cursor Bugbot for commit 3875309. Configure here.


Problem
RWLockdocuments writer priority ("Writers are prioritized when waiting",lib/crewai/src/crewai/utilities/rw_lock.py:14-17), butr_acquireonly checks_writer, so a writer that is already blocked inside_cond.wait()is invisible to readers arriving afterwards. Under a steady stream of readers_readers > 0never drops back to zero inw_acquire, and the writer can be starved indefinitely.RWLockbacks the event bus (events/event_bus.py:153), the execution event record (state/event_record.py:108) and the agent cache handler, so the starved writer is a real write out of the hot path (handler registration, event node insertion).Fixes #8773.
Change
w_acquirecounts itself in_waiting_writersbefore blocking and decrements in afinally.r_acquirealso waits while_waiting_writers > 0, so a queued writer is not overtaken.test_nested_read_locks_same_threaddocuments — would deadlock against its own lock as soon as a writer queues up.Tests
Added to
lib/crewai/tests/utilities/events/test_rw_lock.py:test_waiting_writer_blocks_new_readers— a reader that arrives while a writer waits must not acquire the lock.test_nested_read_locks_work_while_a_writer_waits— the re-entrant read path must keep working once a writer is queued.Red (HEAD):
1 failed, 13 passed—AssertionError: a reader that arrives while a writer waits must not acquire the lockGreen:
test_rw_lock.py→14 passedRegression (
tests/utilities/events,tests/events,test_event_record.py):269 passedPrepared with an AI coding agent.
Note
Medium Risk
Touches shared concurrency primitives backing the event bus and cache handler; behavior change is narrowly scoped and covered by new thread tests.
Overview
Aligns
RWLockbehavior with its documented writer priority by tracking queued writers and blocking new readers while a writer is waiting, which prevents indefinite writer starvation when readers keep arriving.w_acquireincrements_waiting_writersbefore waiting and clears it in afinally.r_acquirewaits when_writeror_waiting_writers > 0, except on the same thread via thread-local_read_depthso nested read locks do not deadlock once a writer is queued.Adds regression tests for reader-vs-waiting-writer ordering and nested reads under a queued writer.
Reviewed by Cursor Bugbot for commit 0ae4a43. Bugbot is set up for automated code reviews on this repo. Configure here.