From 75a1d081e465c531f396a0d3b22d18a45f942f9a Mon Sep 17 00:00:00 2001 From: Xiaoyang Han Date: Thu, 8 Oct 2026 14:29:01 +0800 Subject: [PATCH] fix(v3): allow core registration readers during trusted migration Trusted migration could hold mono while waiting for ACCESS EXCLUSIVE on the core namespace/policy tables, as ordinary route admission or bootstrap held registration reads and waited for mono. Acquire SHARE ROW EXCLUSIVE on these two immutable registration tables: continue excluding concurrent writes and conflicting DDL while allowing AccessShare readers. Preserve the complete lock set, five-second production lock timeout, authentication and physical rechecks, historical Q structural locks, trigger OIDs and controlled stamp updates. Extend the existing compiled-family missing400/500 replay regression with a separate real transaction that reads both registration tables and holds its AccessShare locks across migration; retain every descriptor, ledger, history, owner, OID and old-SID assertion. Bound the stale-reader test synchronization at30 seconds, observe an early HTTP response with status/body immediately, and abort its pending task on admission timeout; preserve all issuance-replay and stale/current-reader assertions. The native log proved the migration deadlock cycle and the earlier four-second barrier timeout, but did not identify the second backend query or establish the HTTP outcome. No retry, serial execution, ignored tests, production deadline changes, SQL-definition changes or performance claims. Nightly formatting, scoped source proof, candidate whitespace and bidirectional patch checks pass; native candidate execution remains pending. --- .../snapshot_descriptor_upgrade_tests.rs | 13 +++++++++ .../router/snapshot_reader_retention_tests.rs | 28 +++++++++++++++---- .../qualified_native_runtime_upgrade.rs | 5 +++- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/api/router/snapshot_descriptor_upgrade_tests.rs b/src/api/router/snapshot_descriptor_upgrade_tests.rs index 9cb37b3b..c57bc57b 100644 --- a/src/api/router/snapshot_descriptor_upgrade_tests.rs +++ b/src/api/router/snapshot_descriptor_upgrade_tests.rs @@ -85,7 +85,20 @@ async fn captured_87b_descriptor_upgrade_preserves_owners_oids_and_replays_missi ) .await .unwrap(); + let reader = core.begin().await.unwrap(); + let registrations = reader + .query_one_raw(Statement::from_string( + DbBackend::Postgres, + "SELECT (SELECT count(*) FROM mst2_metadata_namespace) AS namespaces, + (SELECT count(*) FROM mst2_qualified_family_policy) AS policies", + )) + .await + .unwrap() + .unwrap(); + assert_eq!(registrations.try_get::("", "namespaces").unwrap(), 2); + assert_eq!(registrations.try_get::("", "policies").unwrap(), 1); apply_migrations(core, false).await.unwrap(); + reader.rollback().await.unwrap(); assert_upgrade_ledgers(core).await; assert_eq!(descriptor_matches_stored(&fixture).await, 1); assert_eq!(policy(core).await, stamp); diff --git a/src/api/router/snapshot_reader_retention_tests.rs b/src/api/router/snapshot_reader_retention_tests.rs index 52890bd7..3feadd29 100644 --- a/src/api/router/snapshot_reader_retention_tests.rs +++ b/src/api/router/snapshot_reader_retention_tests.rs @@ -266,14 +266,32 @@ async fn stale_actual_reader_cannot_read_or_finish_a_reissued_uuid() { ), ); let app = fixture.app.clone(); - let pending = tokio::spawn(with_rooted_reader_barriers( + let mut pending = tokio::spawn(with_rooted_reader_barriers( admitted.clone(), resume.clone(), async move { app.oneshot(request).await.unwrap() }, )); - tokio::time::timeout(Duration::from_secs(4), admitted.wait()) - .await - .unwrap(); + // Admission includes a database lock wait of up to five seconds and a + // committed owner. Observe an early HTTP failure as well as the barrier. + let admission = tokio::time::timeout(Duration::from_secs(30), async { + tokio::select! { + _ = admitted.wait() => {} + response = &mut pending => { + let response = response.unwrap(); + let status = response.status(); + let body = to_bytes(response.into_body(), 16 * 1024).await.unwrap(); + panic!( + "reader request ended before its admission barrier: {status}: {}", + String::from_utf8_lossy(&body) + ); + } + } + }) + .await; + if let Err(error) = admission { + pending.abort(); + panic!("reader admission barrier timed out: {error}"); + } let txn = transaction(&fixture).await; let owner=txn.query_one_raw(Statement::from_string(DbBackend::Postgres, "SELECT operation_id::text,reader_issuance,to_jsonb(r) AS owner FROM mst2_metadata_reader_operation r WHERE state='ACTIVE'")) @@ -333,7 +351,7 @@ async fn stale_actual_reader_cannot_read_or_finish_a_reissued_uuid() { [old.0.clone().into(),next.into(),source_id.into(),source_root.into(),source_generation.into(),source_certificate.into()])).await.unwrap().is_empty()); txn.commit().await.unwrap(); resume.wait().await; - let response = tokio::time::timeout(Duration::from_secs(4), pending) + let response = tokio::time::timeout(Duration::from_secs(30), pending) .await .unwrap() .unwrap(); diff --git a/src/jupiter/migration/qualified_native_runtime_upgrade.rs b/src/jupiter/migration/qualified_native_runtime_upgrade.rs index 85cd256b..0aeaba5f 100644 --- a/src/jupiter/migration/qualified_native_runtime_upgrade.rs +++ b/src/jupiter/migration/qualified_native_runtime_upgrade.rs @@ -391,8 +391,11 @@ async fn upgrade_to( )) .await?; } + // Route admission reads these immutable registrations before waiting for + // mono. Exclude concurrent writers and DDL while allowing those reads to + // complete without a relation-lock/mono-lock cycle. connection.execute_unprepared(&format!( - "LOCK TABLE {c}.mst2_metadata_namespace,{c}.mst2_qualified_family_policy IN ACCESS EXCLUSIVE MODE" + "LOCK TABLE {c}.mst2_metadata_namespace,{c}.mst2_qualified_family_policy IN SHARE ROW EXCLUSIVE MODE" )).await?; let policy=connection.query_one_raw(Statement::from_string(DbBackend::Postgres, format!("SELECT implementation_fingerprint,expected_shape,authority_catalog FROM {c}.mst2_qualified_family_policy WHERE singleton=1")))