Skip to content

Lock the description store while a run writes to it [patch] - #202

Merged
matt-edmondson merged 2 commits into
mainfrom
fix/164-lock-store-across-runs
Oct 9, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
fix/164-lock-store-across-runs

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #164

What was wrong

Each process loads the whole store when it starts, and every save writes that whole in-memory copy back. When two runs overlapped, whichever saved last won:

  • Two Scans over different folders lost one folder's descriptions.
  • An Import or Configure made during a Scan was undone by the Scan's next per-image save.

The lock in Scan only serialized threads inside one process.

Change

This is option 1 from the issue, which the triage recommended.

  • StoreLock. A new class that takes an exclusive lock file (FileShare.None) beside the store, at AppData.Path/ImageDescriber.lock. TryAcquire returns null while another run holds it.
  • Which verbs lock. BaseVerb.WritesStore is overridden to true on Scan, Import and Configure. For those verbs, BaseVerb<T>.Run() does three things:
    • takes the lock for the whole run;
    • reloads the store from disk under the lock, so a copy that went stale after start-up, or during a Menu session, is never written back;
    • if the lock is held, prints "Another ImageDescriber run is updating the description store. Wait for it to finish and try again." and returns without running.
  • Search, Stats and Export only read the store, so they don't take the lock.

Tests

StoreLockTests covers three things:

  • A second acquire fails while the first is held, and succeeds after it is released.
  • Exactly the three writing verbs report WritesStore.
  • An Import started while another run holds the lock prints the busy message and never runs. The test can tell because its input file is missing, so a run would have printed "File not found".

With the lock wiring removed from BaseVerb, the two verb tests fail. The full suite passes: 90 passed, 1 skipped.

Note on #200

#200 ("Exit non-zero when … a verb fails") touches BaseVerb.Run too. The two branches merge cleanly; I checked with git merge-tree. Whichever lands second may want the busy refusal to set Failed = true, so a script sees the refused run as a failure.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TTeaDNAaTEviJbNWuEi5hP


Generated by Claude Code

Every run loaded the whole store at start and saved its whole in-memory copy
back, so two Scans over different folders, or an Import or Configure during
a Scan, overwrote each other's additions without a word.

Scan, Import and Configure now hold an exclusive lock file beside the store
for the whole run and reload the store under it. A second writing run fails
fast with a clear message instead of losing work. Search, Stats and Export
only read, so they don't take the lock.

Fixes #164

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTeaDNAaTEviJbNWuEi5hP
Comment thread ImageDescriber/StoreLock.cs Fixed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTeaDNAaTEviJbNWuEi5hP
@sonarqubecloud

sonarqubecloud Bot commented Oct 8, 2026

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 7c62bd7 into main Oct 9, 2026
14 checks passed
@matt-edmondson
matt-edmondson deleted the fix/164-lock-store-across-runs branch October 9, 2026 08:03
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.

Two concurrent runs (e.g. two Scans, or Import during a Scan) silently overwrite each other's descriptions in persistent_state.json

2 participants