roder_core/runtime/
owner_handoff.rs1use super::Runtime;
3use std::sync::atomic::Ordering;
4
5impl Runtime {
6 pub async fn seal_idle_owner(&self) -> anyhow::Result<bool> {
15 let _admission = self.turn_admission.lock().await;
16 let lease = self
17 .execution_lease
18 .as_ref()
19 .ok_or_else(|| anyhow::anyhow!("owner handoff requires an execution lease"))?;
20 if !self.active_turns.read().await.is_empty() || !self.turn_drains.read().await.is_empty() {
21 return Ok(false);
22 }
23 anyhow::ensure!(
24 self.lifecycle_persistence_failures.load(Ordering::Acquire) == 0,
25 "owner handoff requires reconciliation of failed lifecycle persistence"
26 );
27 if !lease.seal_if_idle()? {
28 return Ok(false);
29 }
30 self.accepting_turns.store(false, Ordering::Release);
31 Ok(true)
32 }
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38 use crate::{RuntimeConfig, RuntimeExecutionLease, fake_provider::FakeInferenceEngine};
39 use roder_api::extension::ExtensionRegistryBuilder;
40 use std::{
41 sync::Arc,
42 time::{Duration, Instant},
43 };
44
45 #[tokio::test]
46 async fn expired_idle_owner_can_seal_but_persistence_failure_blocks_proof() {
47 let lease = Arc::new(RuntimeExecutionLease::new(Instant::now()));
48 let mut builder = ExtensionRegistryBuilder::new();
49 builder.inference_engine(Arc::new(FakeInferenceEngine));
50 let runtime = Runtime::new(builder.build().unwrap(), RuntimeConfig::default())
51 .unwrap()
52 .with_execution_lease(lease.clone());
53 runtime
54 .lifecycle_persistence_failures
55 .store(1, Ordering::Release);
56 assert!(runtime.seal_idle_owner().await.is_err());
57 runtime
58 .lifecycle_persistence_failures
59 .store(0, Ordering::Release);
60 assert!(runtime.seal_idle_owner().await.unwrap());
61 assert!(runtime.seal_idle_owner().await.unwrap());
62 assert!(runtime.ensure_execution_authority().is_err());
63 assert!(
64 lease
65 .renew(Instant::now() + Duration::from_secs(30))
66 .is_err()
67 );
68 }
69}