diff --git a/hermes_state_dbfile.py b/hermes_state_dbfile.py index 9759b7eeb8..b0acf2141f 100644 --- a/hermes_state_dbfile.py +++ b/hermes_state_dbfile.py @@ -165,6 +165,10 @@ def _fd_is_truly_unlinked(fd_path: str, watched_path: str) -> bool: names — the guard keeps failing closed.""" try: fd_stat = os.stat(fd_path) + except FileNotFoundError: + # The descriptor was closed after /proc was read. It cannot keep a + # retired generation alive, so do not turn this scan race into a halt. + return False except OSError: return True return _identity_is_truly_unlinked((fd_stat.st_dev, fd_stat.st_ino), watched_path) diff --git a/tests/hermes_state/test_deleted_wal_generation_guard.py b/tests/hermes_state/test_deleted_wal_generation_guard.py index a5ab040822..bfa16643ee 100644 --- a/tests/hermes_state/test_deleted_wal_generation_guard.py +++ b/tests/hermes_state/test_deleted_wal_generation_guard.py @@ -160,6 +160,22 @@ def test_iter_holders_ignores_live_unhashed_dentry(tmp_path, force_wal, monkeypa db.close() +def test_iter_holders_ignores_descriptor_closed_during_scan(tmp_path, monkeypatch): + """A descriptor gone after ``readlink`` cannot hold a retired generation.""" + path = tmp_path / "state.db" + wal = Path(str(path) + "-wal") + wal.write_bytes(b"current generation") + vanished_fd = tmp_path / "closed-writable-opener-fd" + monkeypatch.setattr(hermes_state_dbfile.sys, "platform", "linux") + monkeypatch.setattr( + hermes_state_dbfile, + "_iter_proc_fd_targets", + lambda: iter([(os.getpid(), str(wal) + " (deleted)", str(vanished_fd))]), + ) + + assert iter_deleted_sqlite_sidecar_holders(path) == [] + + @pytest.mark.skipif( not sys.platform.startswith("linux"), reason="deleted-WAL write halt uses Linux unlink semantics",