Repository navigation
Port binaryTable / fileCache lock recovery and repair (DID-matlab 4a0f9a5) #66
Description
Follow-up to #64. DID-matlab commit 4a0f9a5 ("fileCache: reconcile stale rows and stop leaking the lock on error paths") hardens the MATLAB fileCache against a class of failure that leaves the on-disk index and the file directory disagreeing after a crash mid-write. PR #65 bumped the bridge sync hashes for binaryTable and fileCache to 4a0f9a5 and marked both out_of_sync with a note pointing at this issue — this is that issue.
What MATLAB 4a0f9a5 added
binaryTable.resetLockState(obj) — clears the in-memory hasLock flag without touching the on-disk lock file. Lets a caller that has already released the lock through the disk-layer helpers safely reuse the BinaryTable handle without a spurious "already locked" state.
fileCache.check(obj, 'Repair', tf, 'RemoveOrphans', tf) → report — audits the cache and, optionally:
- Repair: rewrites index rows whose file has vanished (stale-row reconciliation)
- RemoveOrphans: deletes files under the cache directory that no index row references
- Report:
{staleRows, orphans, repaired}
Hardening on the existing write paths:
addFiletolerates stale rows whose file is missing, and wraps the (copy → index write) sequence in a try/catch that rolls back the row and callsresetLockStateon failureresizeAndAddeviction rewrites the index before deleting files, so a crash mid-eviction leaves orphan files (recoverable viaRemoveOrphans) rather than a stale row (unrecoverable without a re-read)clearforce-resets the in-memory lock at the end
getCache('reset') — clears the persistent singleton without ending the session, used by the recovery flow so a check result the caller wants applied takes effect on the next getCache().
What Python needs
- Add
BinaryTable.reset_lock_state(obj)(public), matching MATLAB. Python'sBinaryTableusesportalockerrather than a separatehasLockflag today, so this method's contract is "release my in-memory belief that I hold the lock", coupled with the fixes below in the addFile / clear paths. - Add
FileCache.check(repair=False, remove_orphans=False) -> dictmirroring the MATLAB signature and return shape. - Port the addFile / resizeAndAdd / clear hardening once the shape of Python's failure paths is audited — MATLAB's guarantees rely on
resetLockState, so port the primitive first. - Add a public
get_cache('reset')equivalent indid/common.py. The module global_cached_cacheis currently private; the recovery flow needs a public way to reset it.
Non-blocking, but not vanishing
The lock-recovery surface is only exercised after a crash mid-write; nothing in normal use trips it. That is why #65 deferred it rather than bloating the file-series PR. But the bridge YAML records binaryTable and fileCache as out_of_sync until this ships, and the two python_name: ~ entries below name what is missing:
did_matlab_python_bridge_file.yaml:binaryTable.resetLockStatedid_matlab_python_bridge_file.yaml:fileCache.checkdid_matlab_python_bridge_util.yaml:getCache(sync hash4a0f9a5, note about the missing reset form)
Reading the MATLAB 4a0f9a5 diff is the fastest way in; there is no cross-language contract question here — the on-disk formats are already shared, this is only about behaviour on the process side after a fault.
Trigger for prioritisation
This becomes urgent when either:
- A Python process is observed to hit "already locked" on a cache it just released (the invariant
resetLockStaterestores) - The
.fileCacheInfoindex and the cache directory are observed to disagree in a way that repeats reads cannot self-heal (the invariantcheck(Repair=True)restores)
Until then it is documentation-tracked drift, not a production issue.
Activity
Merge pull request #83 from VH-Lab/claude/matlab-series-fallback-para…
stevevanhooser commentedon Sep 9, 2026
Done in #83 (merged 2026-09-08, DID-python
01347ec). Confirmed onmain:BinaryTable.reset_lock_state—src/did/file.py, covered byTestBinaryTableResetLockStateintests/test_file_cache.pyFileCache.check(repair=False, remove_orphans=False)—src/did/file.pyFileCache.add_file/resize_and_add/clearhardening — try/except that rolls back the row and resets the lock; index-rewrite-before-delete on eviction; force-reset on clearget_cache('reset')—src/did/common.py, covered bytest_reset_returns_a_fresh_cachePorted 2026-09-08 (DID-python#66; DID-matlab 4a0f9a5)and clears theout_of_syncflags onbinaryTable/fileCache/getCacheClosing as completed; the PR just wasn't linked at merge time.
Generated by Claude Code
Generated by Claude Code