Summary
External import writes sessions.json from a background thread, outside the store writer, so concurrent index updates can be lost.
Where
StartExternalImport (crates/runtime/src/app/sessions.rs) runs import_thread for every selected thread inside cx.unblock(...).
import_thread (crates/services/src/import/mod.rs) calls Store::append_event and Store::upsert_meta directly.
Store::upsert_meta (crates/services/src/store.rs) is an unlocked read-modify-write of the whole sessions.json (read_file → modify → persist_index). The store writer task does the same for every live session update.
Scenario
While a large import runs, any other thread finishing a turn (or being renamed, archived, settled) persists its meta through the writer. If the importer read the file before that write and persists after it, the other update is silently dropped, and vice versa.
Found by reading the code; not reproduced.
Related work
guivieiras/tcode has an unmerged offline T3 Code history importer on local/integration (5ee044b, 6269075, 27b94d7, ca058bc) that goes through the same import path, so it inherits this race.
Expected
All index writes go through one owner. Route import writes through the store writer (a batched upsert fits: see #521), or take the same lock.
Summary
External import writes
sessions.jsonfrom a background thread, outside the store writer, so concurrent index updates can be lost.Where
StartExternalImport(crates/runtime/src/app/sessions.rs) runsimport_threadfor every selected thread insidecx.unblock(...).import_thread(crates/services/src/import/mod.rs) callsStore::append_eventandStore::upsert_metadirectly.Store::upsert_meta(crates/services/src/store.rs) is an unlocked read-modify-write of the wholesessions.json(read_file→ modify →persist_index). The store writer task does the same for every live session update.Scenario
While a large import runs, any other thread finishing a turn (or being renamed, archived, settled) persists its meta through the writer. If the importer read the file before that write and persists after it, the other update is silently dropped, and vice versa.
Found by reading the code; not reproduced.
Related work
guivieiras/tcode has an unmerged offline T3 Code history importer on
local/integration(5ee044b, 6269075, 27b94d7, ca058bc) that goes through the same import path, so it inherits this race.Expected
All index writes go through one owner. Route import writes through the store writer (a batched upsert fits: see #521), or take the same lock.