Skip to main content
← Back to list
01Issue
FeatureClosedSwamp CLI
Assigneesstack72

#246 Reader-lock or lock-free read path for data list/get/search/query

Opened by stack72 · 5/5/2026

Read-only data operations (data list, data get, data search, data query) currently acquire exclusive per-model locks via acquireModelLocks in src/cli/repo_context.ts, causing 2–5s latency contention when an agent runs frequent reads alongside an active workflow.

Original report: swamp-club#235 item 5 (carved out of the bundle PR for that issue so the design work could happen separately).

Prerequisite: A design note in design/datastores.md covering shared/exclusive lock semantics OR proof that read paths can be lock-free given current cache.db atomicity guarantees.

Two implementation paths to evaluate:

  1. Extend DistributedLock interface in src/domain/datastore/distributed_lock.ts with acquireShared() / acquireExclusive() methods; update all implementations.
  2. Add a 'lock-free read' path that bypasses model locks entirely for read-only commands, relying on existing snapshot/version semantics for consistency.

Path 2 is simpler but requires careful analysis of whether cache.db writes are atomic from a concurrent reader's perspective. Pick one with the design note before code lands.

02Bog Flow
OPENTRIAGEDIN PROGRESSCLOSED+ 1 MOREASSIGNEDCLASSIFICATION

Closed

5/6/2026, 5:33:47 PM

No activity in this phase yet.

03Sludge Pulse
stack72 assigned stack725/6/2026, 5:21:04 PM
Editable. Press Enter to edit.

stack72 commented 5/6/2026, 5:32:41 PM

Triage Findings — Recommend Closing

Codebase analysis shows the premise of this issue is incorrect. The four data read commands (data list, data get, data search, data query) already use a lock-free read path.

Evidence

  • All four commands call requireInitializedRepoReadOnly() (src/cli/repo_context.ts:235–322), which acquires no locks — neither global nor per-model.
  • The "Waiting for per-model lock(s) to be released" message (repo_context.ts:707) only fires from waitForPerModelLocks, which is called exclusively by:
    • requireInitializedRepo (structural commands like data delete, data rename)
    • acquireModelLocks (write commands like model method run, workflow run)
  • Content reads use atomic write-to-temp-then-rename, so concurrent readers never see partial files.
  • SQLite catalog (_catalog.db) uses DatabaseSync (blocking reads), which is naturally concurrent-safe for readers.

Original Report Context

The 2–5s latency reported in swamp-club#235 item 5 was likely observed from concurrent write commands (workflow runs, model method runs) contending with each other via per-model locks — not from the data read commands themselves.

Conclusion

Neither of the two proposed implementation paths is needed:

  1. Extending DistributedLock with acquireShared()/acquireExclusive() — moot since reads don't acquire locks.
  2. Lock-free read path bypassing model locks — this is already the current behavior.

The design note prerequisite in design/datastores.md is also unnecessary for reads, though the existing design doc already covers the lock lifecycle thoroughly (lines 441–563).

If write-vs-write per-model lock contention is a real problem, that would be a separate issue with different scope.

Sign in to post a ripple.