test: verify real orphan pin repair across owned restore process death

This commit is contained in:
2026-10-26 19:38:24 +09:00
parent 6f5a424661
commit b3209c2012
9 changed files with 321 additions and 5 deletions
@@ -25,6 +25,7 @@ pub const MAX_RESTORE_CHECKPOINT_ACK_BYTES: usize = 4096;
pub enum RestoreCheckpointStage {
PreparedNotCommitted,
CommittedPreterminal,
ActivatedPreterminal,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+6
View File
@@ -2871,6 +2871,12 @@ impl Runtime {
sanitize_worker_create_failure_message(&error),
));
}
#[cfg(feature = "restore-checkpoints")]
self.test_restore_checkpoint(
worker_ref,
operation_id,
crate::restore_checkpoints::RestoreCheckpointStage::ActivatedPreterminal,
);
if let Err(error) =
self.activate_restore_candidate_context(worker_ref, &candidate_context)
{
@@ -75,7 +75,8 @@ impl Runtime {
};
let expected = match config.stage {
RestoreCheckpointStage::PreparedNotCommitted => RestoreCommitFact::NotCommitted,
RestoreCheckpointStage::CommittedPreterminal => RestoreCommitFact::Committed,
RestoreCheckpointStage::CommittedPreterminal
| RestoreCheckpointStage::ActivatedPreterminal => RestoreCommitFact::Committed,
};
if config.worker_id != worker_ref.worker_id
|| pending.operation_id != operation_id
@@ -14,6 +14,7 @@ fn checkpoint_stages_ack_canonical_decision_and_eof_resumes_same_operation_once(
(RestoreCheckpointStage::PreparedNotCommitted, "release"),
(RestoreCheckpointStage::CommittedPreterminal, "eof"),
(RestoreCheckpointStage::CommittedPreterminal, "timeout"),
(RestoreCheckpointStage::ActivatedPreterminal, "release"),
] {
let dir = tempfile::tempdir().unwrap();
let backend = Arc::new(TestExecutionBackend::default());
@@ -79,14 +80,19 @@ fn checkpoint_stages_ack_canonical_decision_and_eof_resumes_same_operation_once(
match stage {
RestoreCheckpointStage::PreparedNotCommitted =>
RestoreCheckpointCommit::NotCommitted,
RestoreCheckpointStage::CommittedPreterminal => RestoreCheckpointCommit::Committed,
RestoreCheckpointStage::CommittedPreterminal
| RestoreCheckpointStage::ActivatedPreterminal => RestoreCheckpointCommit::Committed,
}
);
assert_eq!(
backend.restore_operation_ids.lock().unwrap().as_slice(),
&[ack.operation_id]
);
assert!(backend.activation_operation_ids.lock().unwrap().is_empty());
if stage == RestoreCheckpointStage::ActivatedPreterminal {
assert_eq!(backend.activation_operation_ids.lock().unwrap().as_slice(), &[ack.operation_id]);
} else {
assert!(backend.activation_operation_ids.lock().unwrap().is_empty());
}
assert!(received.try_recv().is_err());
let canonical = runtime_store(&runtime)
.read_durable_worker_record(&worker.worker_ref)
@@ -255,6 +261,8 @@ fn checkpoint_verification_requires_canonical_worker_operation_commit_and_absent
.restore_checkpoint_ack(&config, &worker.worker_ref, operation)
.is_err()
);
config.stage = RestoreCheckpointStage::ActivatedPreterminal;
assert!(runtime.restore_checkpoint_ack(&config, &worker.worker_ref, operation).is_err());
config.stage = RestoreCheckpointStage::PreparedNotCommitted;
runtime_store(&runtime).fail_next_worker_reconcile_sync();
assert!(
@@ -557,6 +557,40 @@ mod restore_candidate {
}
}
#[test]
#[serial_test::serial(worker_allocation)]
fn restore_run_reclamation_preserves_live_allocation_owner() {
let fixture = Fixture::no_model();
let factory = fixture.backend.factory.clone();
let request = fixture.request.clone();
let run = factory
.worker_restore_run_dir(&request.worker_ref, request.operation_id)
.unwrap();
fs::create_dir_all(&run).unwrap();
fs::write(run.join("retained-owner"), b"live allocation").unwrap();
let name = ProfileRuntimeWorkerFactory::runtime_worker_name_for_ref(&request.worker_ref);
let allocation = worker::runtime::worker_allocation::install_top_level(
name.clone(), std::process::id(), run.join("worker.sock"),
Vec::new(), fixture.segment_id,
).unwrap();
let failure = fixture.backend.run_on_adapter_runtime(async move {
match factory.prepare_restore(request).await {
Err(failure) => Ok(failure),
Ok(_) => Err("live allocation unexpectedly reclaimed".into()),
}
}).unwrap();
assert_eq!(failure.stage, RuntimeRestoreStage::Controller);
assert_eq!(failure.cause, "restore run directory still has an allocation owner");
let table = worker::runtime::worker_allocation::LockFileGuard::open(
&worker::runtime::worker_allocation::default_allocation_path().unwrap(),
).unwrap();
assert_eq!(table.data().find(&name).unwrap().pid, std::process::id());
assert_eq!(fs::read(run.join("retained-owner")).unwrap(), b"live allocation");
drop(table);
drop(allocation);
fixture.assert_quiet();
}
#[test]
#[serial_test::serial(worker_allocation)]
fn session_availability_requires_attachable_current_controller() {