Repository navigation
Cache.load() leaves the cache partially initialized when WAL replay fails #341
Description
What happened?
Cache.load() updates the cache's internal state before replaying the write-ahead log (WAL). If WriteAheadLog.replay() raises an OSError, the method exits with an exception, but some internal attributes have already been changed.
Specifically, self.db, self.filename, and self.wal are assigned before WAL replay completes. As a result, a failed load can leave the cache appearing initialized even though the WAL entries have not been fully recovered.
If the application catches the exception and continues using the same cache instance, subsequent API calls may operate on an incomplete database instead of retrying initialization.
This can lead to committed writes being temporarily invisible. If subsequent writes trigger compaction, the incomplete state may potentially be persisted, causing permanent data loss.
Steps to reproduce
- Create a database with a valid snapshot and a WAL containing a committed write that has not yet been included in the snapshot.
- Create a fresh Cache instance.
- Temporarily make
WriteAheadLog.replay()raise anOSError, simulating a transient WAL read failure. - Call
cache.load(filename)and catch the exception. - Restore normal WAL replay behavior.
- Inspect the cache state and attempt to load the same database again or access it through the public API.
import json
import tempfile
from pathlib import Path
from seriousdb.cache import Cache
from seriousdb.wal import WriteAheadLog
with tempfile.TemporaryDirectory() as tmp:
filename = str(Path(tmp) / "test.db")
# Create a valid database snapshot.
Path(filename).write_text(
json.dumps({"existing": "snapshot data"}),
encoding="utf-8",
)
original_replay = WriteAheadLog.replay
def fail_replay_once(wal):
WriteAheadLog.replay = original_replay
raise OSError("simulated transient WAL read failure")
WriteAheadLog.replay = fail_replay_once
cache = Cache()
try:
cache.load(filename)
except OSError as exc:
print(f"Load failed as expected: {exc}")
finally:
WriteAheadLog.replay = original_replay
print("Cache state after failed load:")
print(f" db: {cache.db}")
print(f" filename: {cache.filename}")
print(f" wal: {cache.wal}")
# Current implementation: these assertions demonstrate the bug.
assert cache.db is not None, "Expected to reproduce partial initialization"
assert cache.filename == filename
assert cache.wal is not None
print("\nBUG REPRODUCED: cache appears initialized despite load failure.")After the exception, inspect cache.db, cache.filename, and cache.wal. The cache may have been partially initialized despite the failed load.
What did you expect?
If loading fails during WAL replay, the cache should not be left in a state that appears successfully initialized but contains only partially recovered data.
A subsequent load attempt should be able to recover all committed writes, and the cache should not silently serve incomplete data or persist an incomplete database state.
Environment
seriousdb version: 1ea220c
Operating system: WSL (Ubuntu distro)
Run method: uv
Additional context
If possible I want to be assigned to this issue.
Activity
mattsva commentedon Oct 12, 2026
I could reprocess it with
nix-shell -p python3 uvthen the given code with uv and seriousdb 1ea220c.Thanks for reporting and I assign you @TynK-M .
mattsva commentedon Oct 12, 2026
@TynK-M If you do have a PR, you can ask me for review if you want.
mattsva commentedon Oct 12, 2026
#342 closes this.
@TynK-M next time please say smth like "closes #PR" or "fixes #PR" in the PR description, to close the issue automatically.