runtime_lifecycle.rs (4293B)
1 use std::{sync::Arc, time::Duration}; 2 3 use tera_ffi::TeraAppError; 4 5 mod support; 6 7 #[tokio::test] 8 async fn host_release_ordering_retains_close_and_finishes_within_deadline() { 9 let (_root, runtime) = support::runtime().await; 10 let host = Arc::new(runtime); 11 let closing_owner = Arc::clone(&host); 12 let close = tokio::spawn(async move { closing_owner.shutdown().await }); 13 drop(host); 14 15 let result = tokio::time::timeout(Duration::from_secs(1), close) 16 .await 17 .expect("mobile shutdown exceeded its host deadline") 18 .expect("shutdown task panicked") 19 .expect("shutdown failed"); 20 assert_eq!(result.state, "closed"); 21 assert!(!result.already_closed); 22 } 23 24 #[tokio::test] 25 async fn concurrent_host_references_converge_and_repeated_close_is_idempotent() { 26 let (_root, runtime) = support::runtime().await; 27 let runtime = Arc::new(runtime); 28 let first = Arc::clone(&runtime); 29 let second = Arc::clone(&runtime); 30 let (first, second) = tokio::join!(first.shutdown(), second.shutdown()); 31 32 for outcome in [&first, &second] { 33 assert!( 34 outcome.is_ok() 35 || matches!( 36 outcome, 37 Err(TeraAppError::Failure { report }) 38 if report.code == "client_close_in_progress" 39 ) 40 ); 41 } 42 let repeated = runtime.shutdown().await.expect("repeated close"); 43 assert!(repeated.already_closed); 44 } 45 46 struct CallbackGate { 47 entered: Arc<tokio::sync::Notify>, 48 released: Arc<(std::sync::Mutex<bool>, std::sync::Condvar)>, 49 } 50 51 impl tera_ffi::TeraRuntimeObserver for CallbackGate { 52 fn on_change(&self, _: tera_ffi::FfiRuntimeChangeRecord) { 53 self.entered.notify_one(); 54 let (released, wake) = &*self.released; 55 drop( 56 wake.wait_while(released.lock().unwrap(), |released| !*released) 57 .unwrap(), 58 ); 59 } 60 } 61 62 struct ReleaseCallback(Arc<(std::sync::Mutex<bool>, std::sync::Condvar)>); 63 64 impl Drop for ReleaseCallback { 65 fn drop(&mut self) { 66 *self.0.0.lock().unwrap() = true; 67 self.0.1.notify_all(); 68 } 69 } 70 71 #[tokio::test] 72 async fn ffi_close_retains_a_slow_native_callback_across_cancelled_and_repeated_waits() { 73 use std::{ 74 future::{Future, poll_fn}, 75 task::Poll, 76 }; 77 let (_root, runtime) = support::runtime().await; 78 let entered = Arc::new(tokio::sync::Notify::new()); 79 let released = Arc::new((std::sync::Mutex::new(false), std::sync::Condvar::new())); 80 let release = ReleaseCallback(Arc::clone(&released)); 81 let observer = runtime 82 .subscribe_changes(Box::new(CallbackGate { 83 entered: Arc::clone(&entered), 84 released, 85 })) 86 .unwrap(); 87 tokio::time::timeout(Duration::from_secs(5), entered.notified()) 88 .await 89 .unwrap(); 90 let mut close = Box::pin(runtime.shutdown()); 91 tokio::time::timeout(Duration::from_secs(5), async { 92 loop { 93 // Poll the actual exported close until storage has settled. Native 94 // callback ownership must still keep its result pending. 95 let outcome = poll_fn(|context| Poll::Ready(close.as_mut().poll(context))).await; 96 assert!( 97 outcome.is_pending(), 98 "FFI close returned before its native callback" 99 ); 100 if runtime.info().sdk_closed { 101 break; 102 } 103 tokio::task::yield_now().await; 104 } 105 }) 106 .await 107 .unwrap(); 108 assert!(!observer.is_active()); 109 assert_eq!( 110 runtime 111 .sdk_storage_status() 112 .await 113 .unwrap_err() 114 .report() 115 .code, 116 "client_closed" 117 ); 118 drop(close); 119 let mut repeat = Box::pin(runtime.shutdown()); 120 let outcome = poll_fn(|context| Poll::Ready(repeat.as_mut().poll(context))).await; 121 assert!( 122 outcome.is_pending(), 123 "Cancelled close must retain its callback drain" 124 ); 125 drop(release); 126 let receipt = tokio::time::timeout(Duration::from_secs(5), repeat) 127 .await 128 .unwrap() 129 .unwrap(); 130 assert_eq!(receipt.state, "closed"); 131 assert!(receipt.already_closed); 132 assert!(runtime.shutdown().await.unwrap().already_closed); 133 }