Skip to content

fix(storage): preserve SQLite locks during pathname identity checks - #4720

Closed
ScriptedAlchemy wants to merge 5 commits into
masterfrom
fix/4513-path-identity-preserve-sqlite-locks
Closed

ScriptedAlchemy wants to merge 5 commits into
masterfrom
fix/4513-path-identity-preserve-sqlite-locks

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Root cause

On Unix, closing any raw descriptor for a database inode can release process-wide SQLite advisory locks, including those held by separate SQLite connections that cannot observe the close. The original pathname identity check opened and closed a temporary File after a SQLite connection was live. Further review found the same pattern in both reader and writer worker startup: each called OpenedDatabaseFile::pin(...).identity() and immediately dropped the temporary pin after SQLite opened the database.

This is the SQLite documented close()/POSIX lock corruption hazard. Refs #4513; this is a concrete partial fix, not resolution of the reported incident.

Fix

  • Keep Unix verify_current_path identity checks metadata-only, including both pathname checks around SQLITE_FCNTL_HAS_MOVED. The existing retained identity fence, attachment validation, cancellation and admission controls remain.
  • Replace the reader/writer startup's temporary file opens with live_path_identity, which uses std::fs::metadata and the existing dev/inode identity on Unix. Preserve typed refusals for missing/nonfile paths and the existing non-Unix handle-based identity behavior.
  • Do not change SQLite journal modes, locking policies, VFS, descriptor ownership, or authorization.

Regression and verification

Four Rust regression tests plus a subprocess helper now cover rollback and WAL writer/journal exclusion across identity checks; the newly added worker-startup regression checks cross-process writer exclusion through repeated live_path_identity calls, safe normal lock release, committed rows, integrity_check, and absent/nonfile paths. Other existing cases cover aliases and replacement refusal. The test child requires an execution marker, so zero-selected tests cannot pass silently.

Executed independent native Linux Python 3.13.5 / SQLite 3.46.1 subprocess probes. A second-process writer remained blocked across metadata-only stat; closing a separate raw descriptor while the original SQLite transaction was live incorrectly let the second process acquire the write lock. This validates the Unix mechanism, not the Rust implementation.

Source-level verification: independently inspected the four-file follow-up diff, relevant worker/locator lifetime paths, existing test harness, and publication ancestry. The follow-up was committed as d18cbd5 on this PR's existing branch via a non-force expected-head update.

Not executed: Bazel builds/tests, Rust compilation, rustfmt/Clippy, macOS/Windows validation or the live daemon's external sqlite3 reproduction. The execution environment has no Bazel or Rust toolchain. Hosted CI needs to run the isolated PR head, particularly:

bazel test //crates/tracedecay-rusqlite-runtime:unit_test --test_output=errors

Remaining acceptance

Do not close #4513 from this PR. Other retained raw-descriptor clone/release/drop lifetimes and multi-connection teardown require further audit; the reported read-only external sqlite3 workflow requires realistic end-to-end concurrency/integrity testing with the repo's SQLite build and macOS coverage. The separate PR #4732 addresses a distinct verified foreign-reader pin lifetime. The PR-repair automation handles CI, conflict repair, and authorized merges.


Devin Review

Use pathname metadata on Unix instead of opening and closing a raw
file descriptor, which releases process-owned SQLite record locks.
Keep the pinned identity and both HAS_MOVED-adjacent checks intact.

Add cross-process rollback and WAL lock regressions, normal-release
and integrity checks, and missing/replaced/alias identity coverage.

Refs #4513
@changeset-bot

changeset-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 7a4dabd

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

ScriptedAlchemy added a commit that referenced this pull request Nov 3, 2026
Keep verified foreign SQLite readers together with their inode pins when
moving into the shared host reader. Drop the SQLite connection before its
raw pin, and verify that cloned host handles retain both descriptors until
the final handle is released on Linux.

Refs #4513; complements #4720 without addressing the writer pin lifecycle.
Replace ephemeral raw file opens in both SQLite reader and writer startup
with a metadata-only identity check on Unix. Closing those descriptors while
SQLite is live releases process-wide POSIX locks without VFS bookkeeping.
Keep existing non-Unix identity handling and typed refusal paths.

Add a subprocess regression proving an independent SQLite writer stays
excluded across repeated worker identity checks, then commits intact.

Refs #4513.
Apply the exact Bazel rustfmt_check_2024 diff reported in repository gates run 41701101003. Preserve the cross-process SQLite exclusion and pathname assertions unchanged.
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Consolidated into #4742 (integrate/open-pull-requests) at the captured head shown in its description, as requested. Continue review and further fixes on #4742. This closure does not assert CI readiness; the replacement remains draft. Source branch retained.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

storage: opening a live sessions.db with the sqlite3 CLI makes the daemon report disk I/O errors / corrupt the file

1 participant