Skip to content

馃悰 fix(read-write): refuse cross-thread write release - #761

Merged
gaborbernat merged 1 commit into
tox-dev:mainfrom
feiiiiii5:fix/read-write-release-thread-pin
Oct 1, 2026
Merged

gaborbernat merged 1 commit into
tox-dev:mainfrom
feiiiiii5:fix/read-write-release-thread-pin

Conversation

@feiiiiii5

@feiiiiii5 feiiiiii5 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

ReadWriteLock and SoftReadWriteLock pin a write lock to the thread that acquired it, but release() did not check the caller. Another thread could drop the holder's lock and let a second writer in while the holder still believed it held it. ReadWriteLock runs its connection with check_same_thread=False, so SQLite raised nothing either. 馃悰

release() from a thread that does not own the write lock now raises RuntimeError in both classes. The ownership check and the release run under one lock, so a release queued behind an in-flight acquisition checks the new owner instead of the empty state it saw before the acquisition finished. release(force=True) and close() stay the deliberate cross-thread exits. SoftReadWriteLock also lets the hold's own heartbeat thread release, since on_compromise runs there.

That queued ReadWriteLock release used to succeed and now raises too. The async wrappers track ownership per task since #746, but their executor can run a release on a different worker than the acquire. That is the normal case for AsyncSoftReadWriteLock on the default executor, and the how-to shows the same multi-worker setup for AsyncReadWriteLock, so both wrappers release the backend with force=True.

feiiiiii5 pushed a commit to feiiiiii5/filelock that referenced this pull request Sep 30, 2026
@gaborbernat
gaborbernat force-pushed the fix/read-write-release-thread-pin branch from 050d3f1 to 5e5725b Compare October 1, 2026 05:01
@gaborbernat gaborbernat changed the title Raise when releasing a write lock from a thread that does not hold it 馃悰 fix(read-write): refuse cross-thread write release Oct 1, 2026
@gaborbernat gaborbernat added the bug label Oct 1, 2026
ReadWriteLock and SoftReadWriteLock pin a write lock to the thread that
acquired it, but release() did not check the caller, so another thread
could drop the holder's lock and let a second writer in.

Check ownership under the lock the release takes, so a release waiting
behind an acquisition sees the new owner. force=True and close() stay
the cross-thread exits, SoftReadWriteLock lets its own heartbeat thread
release for on_compromise, and the async wrappers use force=True since
their executor may release on another worker.
@gaborbernat
gaborbernat force-pushed the fix/read-write-release-thread-pin branch from 5e5725b to 3c4f3af Compare October 1, 2026 05:48
@gaborbernat
gaborbernat enabled auto-merge (squash) October 1, 2026 05:55
@gaborbernat
gaborbernat merged commit 34f4657 into tox-dev:main Oct 1, 2026
47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants