You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor(mutation): split lock-free prepare from serial commit for directory ingest
Stage 4 of the parallel-add roadmap (#151). `openkb add <dir>` now routes
through a worker-safe prepare / serial commit split: prepare converts into
private .openkb/staging/prepare output without the KB mutation lock and without
touching official raw/, wiki/, or .openkb/ state; the serial owner commits under
kb_ingest_lock, resolving the final name and publishing. `--jobs` (Stage 5) is
not included.
Batch coordinator (openkb/cli.py):
- add_directory_serial runs a serial prepare -> commit loop. `add` already holds
kb_ingest_lock for its whole body via @_with_kb_lock, so prepare runs under
that one outer lock; the reaper (first lock acquisition, before this batch's
staging exists) cannot collide with a live prepare. Per-file failure continues
the batch; DirtyRollbackError stops it.
Prepare (openkb/add_prepare.py, openkb/converter.py):
- convert_document_for_prepare: lock-free conversion into private staging under
a placeholder doc_name (sanitized stem); returns ConvertResult without
registering the hash or resolving the final name.
- prepare_document owns the staging-dir lifecycle (rmtree on interrupt).
prepare/commit are coordinator-internal, called only by add_directory_serial.
Serial commit (openkb/cli.py):
- commit_prepared_document requires kb_ingest_lock held (reentrant acquire).
- The prepared branch of _add_single_file_locked re-validates under the lock
because prepare ran without it: re-decides skip from live registry state,
re-hashes the source, and re-converts when the source changed or prepare had
short-circuited with no artifacts (the stale-prepare contract).
- _retarget_prepared_document_artifacts renames staged raw/source/images from
the placeholder name to the owner-resolved final name.
Reaper (openkb/locks.py):
- _reap_prepare_staging reclaims orphaned prepare staging at first exclusive
acquisition; skips symlinks, unlinks stray files, and logs INFO on success /
WARNING on failure. No per-prepare marker is needed: directory add holds the
lock across its whole batch, so a live batch's staging is never visible to
another reaper.
Tests: prepare writes only private staging and takes no lock; commit resolves the
final name under the owner and requires its lock; the reaper reaps orphans and
skips symlinks; stale skip and source-changed (TOCTOU) re-prepare at commit;
`add <dir>` end-to-end lands every file via prepare/commit; a prepare failure
isolates the file while the batch continues.
0 commit comments