store: lock-refresh-write pattern to prevent duplicate UUIDs
All write paths (upsert_node, upsert_provenance, delete_node, rename_node, ingest_units) now hold StoreLock across the full refresh→check→write cycle. This prevents the race where two concurrent processes both see a key as "new" and create separate UUIDs for it. Adds append_nodes_unlocked() and append_relations_unlocked() for callers already holding the lock. Adds refresh_nodes() to replay log tail under lock before deciding create vs update. Also adds find_duplicates() for detecting existing duplicates in the log (replays full log, groups live nodes by key).
This commit is contained in:
parent
8bbc246b3d
commit
37ae37667b
3 changed files with 121 additions and 10 deletions
|
|
@ -191,7 +191,11 @@ impl Store {
|
|||
}
|
||||
|
||||
/// Process parsed memory units: diff against existing nodes, persist changes.
|
||||
/// Holds StoreLock across refresh + check + write to prevent duplicate UUIDs.
|
||||
fn ingest_units(&mut self, units: &[MemoryUnit], filename: &str) -> Result<(usize, usize), String> {
|
||||
let _lock = types::StoreLock::acquire()?;
|
||||
self.refresh_nodes()?;
|
||||
|
||||
let node_type = classify_filename(filename);
|
||||
let mut new_nodes = Vec::new();
|
||||
let mut updated_nodes = Vec::new();
|
||||
|
|
@ -218,14 +222,14 @@ impl Store {
|
|||
}
|
||||
|
||||
if !new_nodes.is_empty() {
|
||||
self.append_nodes(&new_nodes)?;
|
||||
self.append_nodes_unlocked(&new_nodes)?;
|
||||
for node in &new_nodes {
|
||||
self.uuid_to_key.insert(node.uuid, node.key.clone());
|
||||
self.nodes.insert(node.key.clone(), node.clone());
|
||||
}
|
||||
}
|
||||
if !updated_nodes.is_empty() {
|
||||
self.append_nodes(&updated_nodes)?;
|
||||
self.append_nodes_unlocked(&updated_nodes)?;
|
||||
for node in &updated_nodes {
|
||||
self.nodes.insert(node.key.clone(), node.clone());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue