fix: Prevent concurrent write corruption in savings.jsonl#195
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests.
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
Confidence Score: 4/5The change is safe to merge; it adds write serialisation for a non-critical stats file and degrades gracefully to an unlocked write on Windows. The core locking logic is correct and the lock is properly released via file-close. The two issues — an intra-function import and flock OSErrors being absorbed by the outer handler — are quality improvements rather than correctness blockers for a low-stakes stats file. src/semble/stats.py — the flock error-handling path and module-level import placement are worth a second look.
|
stephantul
left a comment
There was a problem hiding this comment.
Some comments, not sure how easy it is.
| stats_file.parent.mkdir(parents=True, exist_ok=True) | ||
| with stats_file.open("a") as f: | ||
| try: | ||
| import fcntl |
There was a problem hiding this comment.
One issue I can see here is that this import is always attempted (and always fails) on Windows and systems that do not support it.
Maybe move it somewhere else or cache the result of the import operation? I honestly don't know how long a failed import takes, but this is tried on every save.
| fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) | ||
| except ImportError: # pragma: no cover | ||
| pass # Windows has no fcntl, write unlocked | ||
| except OSError: # pragma: no cover |
There was a problem hiding this comment.
Is there some way to distinguish between these failure cases? I.e., just not being supported or a lock contention? The former should not be retried.
Multiple MCP server processes appending to savings.jsonl simultaneously caused a small amount of lines to contain interleaved JSON. Added a lock for unix, Windows can technically still write malformed lines but it won't break the CLI, just show a warning (if we want to do it for windows we need to add a dependency which I don't really want to do for this).