fix(state): WAL lock guard follows the handle's lifecycle
Three gaps in the #110544 guard, all reported in its review and reproduced: - A writer reopened by _reopen_after_close_locked (teardown/worker race, #94736) came back with no guard: the next stray close + foreign close deleted its WAL again. - _try_wal_checkpoint refreshed the guard outside self._lock; landing after close() it pinned an OFD lock with no connection behind it, so a foreign `PRAGMA journal_mode=DELETE` saw `database is locked` forever. - Refcounts keyed on (fd, inode) treated a recycled fd number as a surviving lock: A+B live, close A, C reuses A's fd, close B left C recorded as guarded while a foreign EXCLUSIVE succeeded. The guard now counts handles per inode, re-locks every matching descriptor on each hold (OFD re-lock is idempotent), and unlocks on the last handle only; the reopen path holds it; the checkpoint refresh runs under self._lock and skips a closed handle. The macOS holder scan folds case so a case-only alias of the sidecar path on APFS still matches.
This commit is contained in:
@@ -865,6 +865,8 @@ class SessionDB(
|
||||
f"in flight (a session-teardown path called close() before "
|
||||
f"this worker finished — #94736) and the automatic reopen failed: {exc}"
|
||||
) from exc
|
||||
if self._wal_active: # a reopened writer is a live generation holder like the first open
|
||||
self._wal_lock_guard = _lockguard.hold(self.db_path)
|
||||
|
||||
def _execute_write(
|
||||
self, fn: Callable[[sqlite3.Connection], T], patience_s: Optional[float] = None,
|
||||
@@ -1330,10 +1332,12 @@ class SessionDB(
|
||||
"""
|
||||
if self._quarantine_reason() is not None:
|
||||
return
|
||||
if self._wal_lock_guard:
|
||||
_lockguard.hold(self.db_path, self._wal_lock_guard) # a -shm minted after open
|
||||
try:
|
||||
with self._lock:
|
||||
if self._conn is None:
|
||||
return # closed underneath the timer: nothing to checkpoint, nothing to re-guard
|
||||
if self._wal_lock_guard:
|
||||
_lockguard.hold(self.db_path, self._wal_lock_guard) # a -shm minted after open
|
||||
result = self._conn.execute("PRAGMA wal_checkpoint(PASSIVE)").fetchone()
|
||||
if result and result[1] > 0:
|
||||
logger.debug("WAL checkpoint: %d/%d pages checkpointed", result[2], result[1])
|
||||
|
||||
Reference in New Issue
Block a user