Skip to content

Commit 4636b39

Browse files
committed
Fence concurrent import observations
1 parent 444a001 commit 4636b39

20 files changed

Lines changed: 1227 additions & 177 deletions

File tree

crates/ctx-cli/src/commands/import.rs

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -316,30 +316,67 @@ pub(crate) struct InventoryTotals {
316316
pub(crate) enum SourcePreinventory {
317317
#[default]
318318
None,
319-
CodexSessionCatalog(CatalogSummary),
320-
SourceImportFiles(Vec<SourceImportFile>),
321-
SourceRoot(SourceImportFile),
319+
CodexSessionCatalog {
320+
summary: CatalogSummary,
321+
inventory_generation: u64,
322+
},
323+
SourceImportFiles {
324+
files: Vec<SourceImportFile>,
325+
inventory_generation: u64,
326+
},
327+
SourceRoot {
328+
file: SourceImportFile,
329+
inventory_generation: u64,
330+
},
322331
}
323332

324333
impl SourcePreinventory {
325334
pub(crate) fn codex_session_catalog(&self) -> Option<&CatalogSummary> {
326335
match self {
327-
Self::CodexSessionCatalog(summary) => Some(summary),
328-
Self::None | Self::SourceImportFiles(_) | Self::SourceRoot(_) => None,
336+
Self::CodexSessionCatalog { summary, .. } => Some(summary),
337+
Self::None | Self::SourceImportFiles { .. } | Self::SourceRoot { .. } => None,
329338
}
330339
}
331340

332341
pub(crate) fn source_import_files(&self) -> Option<&[SourceImportFile]> {
333342
match self {
334-
Self::SourceImportFiles(files) => Some(files),
335-
Self::None | Self::CodexSessionCatalog(_) | Self::SourceRoot(_) => None,
343+
Self::SourceImportFiles { files, .. } => Some(files),
344+
Self::None | Self::CodexSessionCatalog { .. } | Self::SourceRoot { .. } => None,
336345
}
337346
}
338347

339348
pub(crate) fn source_root_file(&self) -> Option<&SourceImportFile> {
340349
match self {
341-
Self::SourceRoot(file) => Some(file),
342-
Self::None | Self::CodexSessionCatalog(_) | Self::SourceImportFiles(_) => None,
350+
Self::SourceRoot { file, .. } => Some(file),
351+
Self::None | Self::CodexSessionCatalog { .. } | Self::SourceImportFiles { .. } => None,
352+
}
353+
}
354+
355+
pub(crate) fn source_root_observation(&self) -> Option<(&SourceImportFile, u64)> {
356+
match self {
357+
Self::SourceRoot {
358+
file,
359+
inventory_generation,
360+
} => Some((file, *inventory_generation)),
361+
Self::None | Self::CodexSessionCatalog { .. } | Self::SourceImportFiles { .. } => None,
362+
}
363+
}
364+
365+
pub(crate) fn inventory_generation(&self) -> Option<u64> {
366+
match self {
367+
Self::None => None,
368+
Self::CodexSessionCatalog {
369+
inventory_generation,
370+
..
371+
}
372+
| Self::SourceImportFiles {
373+
inventory_generation,
374+
..
375+
}
376+
| Self::SourceRoot {
377+
inventory_generation,
378+
..
379+
} => Some(*inventory_generation),
343380
}
344381
}
345382
}

crates/ctx-cli/src/commands/import/catalog.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,16 @@ pub(crate) fn import_incremental_codex_session_tree(
1414
record_id: Uuid,
1515
progress: Option<CodexSessionImportProgressCallback>,
1616
preinventory_catalog: Option<&CatalogSummary>,
17+
preinventory_generation: Option<u64>,
1718
force_selection: bool,
1819
) -> Result<ProviderImportSummary> {
1920
let source_root = source.path.display().to_string();
21+
let inventory_generation = match preinventory_generation {
22+
Some(generation) => generation,
23+
None => {
24+
store.allocate_catalog_inventory_generation(CaptureProvider::Codex, &source_root)?
25+
}
26+
};
2027
let mut summary = ProviderImportSummary::default();
2128
if let Some(catalog) = preinventory_catalog {
2229
summary.failed += catalog.failed_sessions;
@@ -27,6 +34,7 @@ pub(crate) fn import_incremental_codex_session_tree(
2734
store,
2835
CodexSessionCatalogOptions {
2936
source_root: Some(source.path.clone()),
37+
observation_generation: Some(inventory_generation),
3038
..CodexSessionCatalogOptions::default()
3139
},
3240
)
@@ -76,6 +84,7 @@ pub(crate) fn import_incremental_codex_session_tree(
7684
std::slice::from_ref(session),
7785
&error,
7886
catalog_import_error_status(&err),
87+
inventory_generation,
7988
)?;
8089
return Err(err);
8190
}
@@ -104,6 +113,7 @@ pub(crate) fn import_incremental_codex_session_tree(
104113
std::slice::from_ref(session),
105114
&err.to_string(),
106115
catalog_import_error_status(&err),
116+
inventory_generation,
107117
)?;
108118
return Err(err);
109119
}
@@ -129,6 +139,7 @@ pub(crate) fn import_incremental_codex_session_tree(
129139
utc_now().timestamp_millis(),
130140
status,
131141
error.as_deref(),
142+
inventory_generation,
132143
)?;
133144
summary.merge_from(tail_summary);
134145
} else {
@@ -160,6 +171,7 @@ pub(crate) fn import_incremental_codex_session_tree(
160171
std::slice::from_ref(session),
161172
&error,
162173
catalog_import_error_status(&err),
174+
inventory_generation,
163175
)?;
164176
if failure_scope == ImportFailureScope::System {
165177
return Err(err);
@@ -171,7 +183,12 @@ pub(crate) fn import_incremental_codex_session_tree(
171183
continue;
172184
}
173185
};
174-
mark_catalog_sessions_result(store, std::slice::from_ref(session), &file_summary)?;
186+
mark_catalog_sessions_result(
187+
store,
188+
std::slice::from_ref(session),
189+
&file_summary,
190+
inventory_generation,
191+
)?;
175192
summary.merge_from(file_summary);
176193
}
177194
}
@@ -196,6 +213,7 @@ pub(crate) fn mark_catalog_sessions_result(
196213
store: &Store,
197214
sessions: &[CatalogSession],
198215
summary: &ProviderImportSummary,
216+
inventory_generation: u64,
199217
) -> Result<()> {
200218
let indexed_at_ms = utc_now().timestamp_millis();
201219
let event_count = if sessions.len() == 1 {
@@ -217,6 +235,7 @@ pub(crate) fn mark_catalog_sessions_result(
217235
indexed_at_ms,
218236
status,
219237
error.as_deref(),
238+
inventory_generation,
220239
)?;
221240
}
222241
Ok(())
@@ -229,6 +248,7 @@ pub(crate) fn mark_catalog_session_result(
229248
indexed_at_ms: i64,
230249
status: CatalogIndexedStatus,
231250
error: Option<&str>,
251+
inventory_generation: u64,
232252
) -> Result<()> {
233253
let file_sha256 = if status == CatalogIndexedStatus::Indexed {
234254
let hash = sha256_file_prefix_hex(Path::new(&session.source_path), session.file_size_bytes)
@@ -242,6 +262,7 @@ pub(crate) fn mark_catalog_session_result(
242262
std::slice::from_ref(session),
243263
&durable_error,
244264
catalog_import_error_status(&err),
265+
inventory_generation,
245266
)?;
246267
return Err(err);
247268
}
@@ -257,6 +278,7 @@ pub(crate) fn mark_catalog_session_result(
257278
file_size_bytes: session.file_size_bytes,
258279
file_modified_at_ms: session.file_modified_at_ms,
259280
import_revision: session.import_revision,
281+
inventory_generation,
260282
file_sha256: file_sha256.as_deref(),
261283
event_count,
262284
indexed_at_ms,
@@ -304,6 +326,7 @@ pub(crate) fn mark_catalog_sessions_error(
304326
sessions: &[CatalogSession],
305327
error: &str,
306328
status: CatalogIndexedStatus,
329+
inventory_generation: u64,
307330
) -> Result<()> {
308331
let indexed_at_ms = utc_now().timestamp_millis();
309332
for session in sessions {
@@ -315,6 +338,7 @@ pub(crate) fn mark_catalog_sessions_error(
315338
file_size_bytes: session.file_size_bytes,
316339
file_modified_at_ms: session.file_modified_at_ms,
317340
import_revision: session.import_revision,
341+
inventory_generation,
318342
file_sha256: None,
319343
event_count: None,
320344
indexed_at_ms,

crates/ctx-cli/src/commands/import/inventory.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ pub(crate) fn inventory_import_sources(
6363
.source_bytes
6464
.saturating_add(plan.stats.bytes);
6565
match &plan.preinventory {
66-
SourcePreinventory::SourceImportFiles(files) => {
66+
SourcePreinventory::SourceImportFiles { files, .. } => {
6767
inventory.totals.source_import_files += files.len();
6868
}
69-
SourcePreinventory::SourceRoot(_) => {
69+
SourcePreinventory::SourceRoot { .. } => {
7070
inventory.totals.source_import_files += 1;
7171
}
72-
SourcePreinventory::None | SourcePreinventory::CodexSessionCatalog(_) => {}
72+
SourcePreinventory::None | SourcePreinventory::CodexSessionCatalog { .. } => {}
7373
}
7474
if let Some((summary, source_json)) = cataloged {
7575
inventory.catalog.add(&summary);
@@ -103,11 +103,15 @@ fn inventory_import_source(
103103
source: SourceInfo,
104104
) -> Result<(PlannedImportSource, Option<(CatalogSummary, Value)>)> {
105105
if is_incremental_codex_session_tree(&source) {
106+
let source_root = source.path.display().to_string();
107+
let inventory_generation =
108+
store.allocate_catalog_inventory_generation(CaptureProvider::Codex, &source_root)?;
106109
let summary = catalog_codex_session_tree(
107110
&source.path,
108111
store,
109112
CodexSessionCatalogOptions {
110113
source_root: Some(source.path.clone()),
114+
observation_generation: Some(inventory_generation),
111115
..CodexSessionCatalogOptions::default()
112116
},
113117
)
@@ -120,7 +124,10 @@ fn inventory_import_source(
120124
let plan = PlannedImportSource {
121125
source,
122126
stats,
123-
preinventory: SourcePreinventory::CodexSessionCatalog(summary.clone()),
127+
preinventory: SourcePreinventory::CodexSessionCatalog {
128+
summary: summary.clone(),
129+
inventory_generation,
130+
},
124131
};
125132
let source_json = json!({
126133
"provider": plan.source.provider.as_str(),
@@ -138,29 +145,46 @@ fn inventory_import_source(
138145
}
139146

140147
if source_uses_import_file_manifest(&source) {
148+
let source_root = source.path.display().to_string();
149+
let inventory_generation =
150+
store.allocate_source_import_inventory_generation(source.provider, &source_root)?;
141151
let files = collect_source_import_files(&source)
142152
.with_context(|| format!("inventory import files from {}", source.path.display()))?;
143-
persist_source_import_files(store, &source, &files)?;
153+
persist_source_import_files(store, &source, inventory_generation, &files)?;
144154
let stats = source_stats_from_import_files(&files);
145155
return Ok((
146156
PlannedImportSource {
147157
source,
148158
stats,
149-
preinventory: SourcePreinventory::SourceImportFiles(files),
159+
preinventory: SourcePreinventory::SourceImportFiles {
160+
files,
161+
inventory_generation,
162+
},
150163
},
151164
None,
152165
));
153166
}
154167

168+
let source_root = source.path.display().to_string();
169+
let inventory_generation =
170+
store.allocate_source_import_inventory_generation(source.provider, &source_root)?;
155171
let stats = source_stats(&source.path)
156172
.with_context(|| format!("inventory import source {}", source.path.display()))?;
157173
let root_file = source_root_import_file(&source, stats)?;
158-
persist_source_import_files(store, &source, std::slice::from_ref(&root_file))?;
174+
persist_source_import_files(
175+
store,
176+
&source,
177+
inventory_generation,
178+
std::slice::from_ref(&root_file),
179+
)?;
159180
Ok((
160181
PlannedImportSource {
161182
source,
162183
stats,
163-
preinventory: SourcePreinventory::SourceRoot(root_file),
184+
preinventory: SourcePreinventory::SourceRoot {
185+
file: root_file,
186+
inventory_generation,
187+
},
164188
},
165189
None,
166190
))

crates/ctx-cli/src/commands/import/manifest.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::commands::import::catalog::system_time_ms;
66
pub(crate) fn persist_source_import_files(
77
store: &Store,
88
source: &SourceInfo,
9+
inventory_generation: u64,
910
files: &[SourceImportFile],
1011
) -> Result<()> {
1112
let source_root = source.path.display().to_string();
@@ -16,12 +17,13 @@ pub(crate) fn persist_source_import_files(
1617
let observed_at_ms = utc_now().timestamp_millis();
1718
store.begin_immediate_batch()?;
1819
let persist = (|| -> Result<()> {
19-
store.upsert_source_import_files(files)?;
20+
store.upsert_source_import_files(inventory_generation, files)?;
2021
store.mark_source_import_missing_paths_stale(
2122
source.provider,
2223
&source_root,
2324
&current_paths,
2425
observed_at_ms,
26+
inventory_generation,
2527
)?;
2628
Ok(())
2729
})();

0 commit comments

Comments
 (0)