Files
dicklesworthstone__coding_a…/scripts/patches/gh473-parent-registration-retry.patch

97 lines
4.5 KiB
Diff

--- a/src/indexer/mod.rs
+++ b/src/indexer/mod.rs
@@ -33307,18 +33307,25 @@
kind: AgentKind::Cli,
};
- let agent_id = if cache_enabled {
- cache.get_or_insert_agent(writer, &agent)?
- } else {
- writer.ensure_agent(&agent)?
- };
+ // These idempotent parent upserts precede the canonical
+ // chunk transaction. They need their own bounded contention
+ // retry when a warm writer encounters a new identity (GH473).
+ let agent_id = with_concurrent_retry(SERIAL_CHUNK_CONTENTION_RETRIES, || {
+ if cache_enabled {
+ cache.get_or_insert_agent(writer, &agent)
+ } else {
+ writer.ensure_agent(&agent)
+ }
+ })?;
let workspace_id = if let Some(ws) = &conv.workspace {
- if cache_enabled {
- Some(cache.get_or_insert_workspace(writer, ws, None)?)
- } else {
- Some(writer.ensure_workspace(ws, None)?)
- }
+ Some(with_concurrent_retry(SERIAL_CHUNK_CONTENTION_RETRIES, || {
+ if cache_enabled {
+ cache.get_or_insert_workspace(writer, ws, None)
+ } else {
+ writer.ensure_workspace(ws, None)
+ }
+ })?)
} else {
None
};
--- a/src/indexer/gh473_tests.rs
+++ b/src/indexer/gh473_tests.rs
@@ -371,6 +371,46 @@
storage.close().unwrap();
}
+ fn warm_writer_parent_registration_retries(workspace_only: bool) {
+ let tmp = TempDir::new().unwrap();
+ let path = tmp.path().join("parent-retry.db");
+ let storage = FrankenStorage::open(&path).unwrap();
+ let mut conversation = norm_conv(Some("gh473-parent"), vec![norm_msg(0, 100)]);
+ conversation.workspace = Some(tmp.path().join("workspace"));
+ let completion = crate::storage::sqlite::SourceIngestLedgerEntry {
+ key: "source_ingest_v1:gh473-parent".into(),
+ observation: "complete".into(),
+ };
+ persist::with_ephemeral_writer(&storage, false, "GH473 warm setup", |writer| {
+ if workspace_only {
+ writer.ensure_agent(&crate::model::types::Agent {
+ id: None,
+ slug: conversation.agent_slug.clone(),
+ name: conversation.agent_slug.clone(),
+ version: None,
+ kind: crate::model::types::AgentKind::Cli,
+ })?;
+ }
+ Ok(())
+ }).unwrap();
+ assert!(storage.ephemeral_writer_preflight_verified());
+ let (result, retries) = with_held_writer(&path, true, || {
+ persist::persist_conversations_batched_inner(
+ &storage, None, std::slice::from_ref(&conversation),
+ LexicalPopulationStrategy::DeferredAuthoritativeDbRebuild,
+ false, false, None, persist::PersistHeartbeat::NONE, Some(&completion),
+ )
+ });
+ let result = result.unwrap();
+ assert_eq!(retries, 1, "warm setup must retry the uncached parent write");
+ assert_eq!((result.inserted_conversations, result.inserted_messages), (1, 1));
+ assert_eq!(scalar(&storage, "SELECT COUNT(*) FROM workspaces"), 1);
+ assert_eq!(scalar(&storage, "SELECT COUNT(*) FROM conversations"), 1);
+ assert_eq!(scalar(&storage, "SELECT COUNT(*) FROM messages"), 1);
+ assert_eq!(storage.source_ingest_ledger_entries().unwrap().get(&completion.key), Some(&completion.observation));
+ storage.close().unwrap();
+ }
+
#[test]
fn writer_preflight_contention_regression() {
const CHILD: &str = "CASS_TEST_GH473_PREFLIGHT_CHILD";
@@ -383,6 +423,8 @@
}
permanent_error_is_not_retried();
writer_body_errors_are_never_replayed();
+ warm_writer_parent_registration_retries(false);
+ warm_writer_parent_registration_retries(true);
canonical_contention_preserves_completion_and_lexical_state();
canonical_write_and_replay_with_pinned_reader(expected);
return;