馃悰 fix(fork): preserve parent ownership - #653
Merged
Merged
Conversation
gaborbernat
force-pushed
the
fix/634-fork-ownership
branch
from
July 14, 2026 07:35
3134f0a to
ff61d31
Compare
gaborbernat
marked this pull request as ready for review
July 14, 2026 07:44
gaborbernat
force-pushed
the
fix/640-async-cancellation
branch
from
July 14, 2026 07:46
e9782a8 to
63a038a
Compare
Track held and provisional descriptors across process forks so child cleanup cannot change ownership that belongs to its parent. Reinitialize inherited lock state before child code runs. Keep acquisition and rollback failures observable when descriptor registration also fails, and avoid closing descriptor numbers that cannot be identity-checked at the fork boundary. Fixes #634
gaborbernat
force-pushed
the
fix/634-fork-ownership
branch
from
July 14, 2026 07:46
ff61d31 to
9cfc800
Compare
gaborbernat
added a commit
that referenced
this pull request
Jul 14, 2026
SQLite forbids a process from using or closing a database connection inherited through `fork()`. Read-write locks kept one connection for the object's lifetime, so a child could close its parent's handle during interpreter shutdown and corrupt the database. Each outer acquisition now owns its connection, and a forked child abandons inherited handles according to [SQLite's fork guidance](https://sqlite.org/howtocorrupt.html#_carrying_an_open_database_connection_across_a_fork_). Closes #635. The child rejects an active database by canonical path or inode until `exec()`. CPython 3.10 and 3.11 keep inherited base connections alive through a raw-reference escrow; later CPython versions use the guarded subclass finalizer. The implementation follows the [CPython 3.10 `_sqlite` lifecycle](https://github.com/python/cpython/blob/v3.10.20/Modules/_sqlite/connection.c) and [current CPython source](https://github.com/python/cpython/blob/main/Modules/_sqlite/connection.c). A child forked from a SQLite callback waits for the call to leave SQLite, or exits with status 70 if an earlier audit hook prevents coordination. Fork registration uses a reentrant gate because connection finalizers can register another lock while the current thread owns that gate. PyPy can retain unsafe process-wide SQLite state after tracked connections close. A PyPy child rejects read-write locks when the parent used SQLite after importing filelock; importing filelock in the child cannot detect earlier parent use. The public acquisition interfaces stay unchanged. Final release closes the per-acquisition connection. A local 2,000-acquisition benchmark measured 5.7 microseconds for the former persistent connection and 45.6 microseconds for the per-acquisition design. This replaces #654, which GitHub closed after merging #653 deleted its base branch `fix/634-fork-ownership`. The branch now sits on `main` and carries the same work. It also carries the `close_failure` fixture change from #656, without which the `pypy3.11` job fails here. Landing #656 first drops that commit from this branch on the next rebase.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A forked child inherits a parent's descriptors and Python lock state. Releasing an inherited native lock can unlock the
shared open-file description, while releasing a soft lock can unlink the parent's marker. Same-path non-singleton soft
read/write locks could also disappear from the fork registry before child cleanup. This caused the ownership violation
reported in #634.
Track held and provisional descriptors by object identity and creator PID. The child checks the descriptor identity
before closing it, without issuing an OS unlock or removing a marker, then clears the inherited in-memory lock state. An
unavailable identity leaves the descriptor untouched because an earlier child callback may have reused its number.
Coordinate descriptor transitions with fork callbacks across threads and concurrent coroutine tasks. Registration and
rollback failures remain observable, including cancellation paths inherited from
#652. Reentrant singleton construction during a parent callback reports
a
RuntimeError. Child cache insertion rejects an instance whose construction crossed the fork boundary.This branch depends on #652 so fork tracking can wrap its cancellation-safe async transitions. Rebase it onto
mainafter merging #652.
Fixes #634.