1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::fmt::Display;
4use std::future::Future;
5use std::panic::AssertUnwindSafe;
6use std::pin::pin;
7use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
8use std::sync::{Arc, LazyLock, OnceLock, Weak};
9use std::task::{Context, Waker};
10
11use futures::FutureExt;
12use tokio::runtime::{Builder as TokioRuntimeBuilder, Handle as TokioRuntimeHandle, Runtime as TokioRuntime};
13use tokio::sync::oneshot;
14use tokio::task::JoinHandle;
15use tracing::{debug, info};
16
17pub use super::RuntimeMode;
18use crate::config::XetConfig;
19use crate::error::RuntimeError;
20#[cfg(feature = "fd-track")]
21use crate::fd_diagnostics::{report_fd_count, track_fd_scope};
22use crate::logging::SystemMonitor;
23use crate::utils::ClosureGuard as CallbackGuard;
24
25const THREADPOOL_THREAD_ID_PREFIX: &str = "hf-xet"; const THREADPOOL_STACK_SIZE: usize = 8_000_000; const THREADPOOL_MAX_ASYNC_THREADS: usize = 32;
34
35fn get_num_tokio_worker_threads() -> usize {
39 use std::num::NonZeroUsize;
40
41 if let Ok(val) = std::env::var("TOKIO_WORKER_THREADS") {
43 match val.parse::<usize>() {
44 Ok(n) if n > 0 => {
45 info!("Using {n} async threads from TOKIO_WORKER_THREADS");
46 return n;
47 },
48 _ => {
49 use tracing::warn;
50
51 warn!(
52 value = %val,
53 "Invalid TOKIO_WORKER_THREADS; must be a positive integer. Falling back to auto."
54 );
55 },
56 }
57 }
58
59 let cores = std::thread::available_parallelism().map(NonZeroUsize::get).unwrap_or(1);
60
61 let n = cores.clamp(2, THREADPOOL_MAX_ASYNC_THREADS);
63 info!("Using {n} async threads for tokio runtime");
64 n
65}
66
67type OwnedRuntimeCell = Arc<std::sync::RwLock<Option<Arc<TokioRuntime>>>>;
68
69thread_local! {
73 static THREAD_THREADPOOL_REF: RefCell<Option<(u32, Weak<XetRuntime>)>> =
74 const { RefCell::new(None) };
75}
76
77static EXTERNAL_THREADPOOL_REGISTRY: LazyLock<std::sync::RwLock<HashMap<tokio::runtime::Id, Weak<XetRuntime>>>> =
80 LazyLock::new(|| std::sync::RwLock::new(HashMap::new()));
81
82static PRE_SHUTDOWN_DRAIN: OnceLock<fn()> = OnceLock::new();
94
95pub fn register_pre_shutdown_drain(drain: fn()) {
98 let _ = PRE_SHUTDOWN_DRAIN.set(drain);
99}
100
101#[derive(Debug)]
102enum RuntimeBackend {
103 External { handle_id: Option<tokio::runtime::Id> },
104 OwnedThreadPool { runtime: OwnedRuntimeCell },
105}
106
107#[derive(Debug)]
132pub struct XetRuntime {
133 backend: RuntimeBackend,
135
136 handle_ref: OnceLock<TokioRuntimeHandle>,
140
141 external_executor_count: AtomicUsize,
143
144 sigint_shutdown: AtomicBool,
146
147 creation_pid: u32,
149
150 system_monitor: Option<SystemMonitor>,
152}
153
154fn system_monitor_for_config(config: &XetConfig) -> Option<SystemMonitor> {
155 config
156 .system_monitor
157 .enabled
158 .then(|| {
159 SystemMonitor::follow_process(config.system_monitor.sample_interval, config.system_monitor.log_path.clone())
160 .ok()
161 })
162 .flatten()
163}
164
165impl XetRuntime {
166 pub fn new(config: &XetConfig) -> Result<Arc<Self>, RuntimeError> {
168 #[cfg(feature = "fd-track")]
169 let _fd_scope = track_fd_scope("XetRuntime::new");
170
171 let runtime = Arc::new(std::sync::RwLock::new(None));
172
173 let rt = Arc::new(Self {
174 backend: RuntimeBackend::OwnedThreadPool {
175 runtime: runtime.clone(),
176 },
177 handle_ref: OnceLock::new(),
178 external_executor_count: 0.into(),
179 sigint_shutdown: false.into(),
180 creation_pid: std::process::id(),
181 system_monitor: system_monitor_for_config(config),
182 });
183
184 let rt_weak = Arc::downgrade(&rt);
185 let pid = std::process::id();
186 let set_threadlocal_reference = move || {
187 THREAD_THREADPOOL_REF.set(Some((pid, rt_weak.clone())));
188 };
189
190 let thread_id = AtomicUsize::new(0);
191 let get_thread_name = move || {
192 let id = thread_id.fetch_add(1, Ordering::Relaxed);
193 format!("{THREADPOOL_THREAD_ID_PREFIX}-{id}")
194 };
195
196 let tokio_rt = TokioRuntimeBuilder::new_multi_thread()
197 .worker_threads(get_num_tokio_worker_threads())
198 .on_thread_start(set_threadlocal_reference)
199 .thread_keep_alive(std::time::Duration::from_millis(100))
200 .thread_name_fn(get_thread_name)
201 .thread_stack_size(THREADPOOL_STACK_SIZE)
202 .enable_all()
203 .build()
204 .map_err(RuntimeError::RuntimeInit)?;
205
206 let handle = tokio_rt.handle().clone();
207 let tokio_rt = Arc::new(tokio_rt);
208 *runtime.write().unwrap() = Some(tokio_rt);
209 rt.handle_ref.set(handle).unwrap();
210
211 #[cfg(feature = "fd-track")]
212 report_fd_count("XetRuntime::new complete");
213
214 Ok(rt)
215 }
216
217 pub fn from_external_with_config(
220 rt_handle: TokioRuntimeHandle,
221 config: &XetConfig,
222 ) -> Result<Arc<Self>, RuntimeError> {
223 #[cfg(feature = "fd-track")]
224 let _fd_scope = track_fd_scope("XetRuntime::from_external_with_config");
225
226 let id = rt_handle.id();
227
228 let mut reg = EXTERNAL_THREADPOOL_REGISTRY.write()?;
229 if let Some(existing) = reg.get(&id)
230 && existing.upgrade().is_some()
231 {
232 return Err(RuntimeError::ExternalAlreadyAttached(id));
233 }
234
235 let rt = Arc::new(Self {
236 backend: RuntimeBackend::External { handle_id: Some(id) },
237 handle_ref: rt_handle.into(),
238 external_executor_count: 0.into(),
239 sigint_shutdown: false.into(),
240 creation_pid: std::process::id(),
241 system_monitor: system_monitor_for_config(config),
242 });
243
244 reg.insert(id, Arc::downgrade(&rt));
245
246 #[cfg(feature = "fd-track")]
247 report_fd_count("XetRuntime::from_external_with_config complete");
248
249 Ok(rt)
250 }
251
252 pub fn from_external(rt_handle: TokioRuntimeHandle) -> Arc<Self> {
254 Arc::new(Self {
255 backend: RuntimeBackend::External { handle_id: None },
256 handle_ref: rt_handle.into(),
257 external_executor_count: 0.into(),
258 sigint_shutdown: false.into(),
259 creation_pid: std::process::id(),
260 system_monitor: None,
261 })
262 }
263
264 #[inline]
269 pub fn current_if_exists() -> Option<Arc<Self>> {
270 let pid = std::process::id();
271 THREAD_THREADPOOL_REF.with_borrow(|entry| {
272 entry
273 .as_ref()
274 .filter(|(entry_pid, _)| *entry_pid == pid)
275 .and_then(|(_, weak_pool)| weak_pool.upgrade())
276 })
277 }
278
279 #[inline]
280 pub fn handle(&self) -> TokioRuntimeHandle {
281 self.handle_ref.get().expect("Not initialized with handle set.").clone()
282 }
283
284 #[inline]
285 pub fn num_worker_threads(&self) -> usize {
286 self.handle().metrics().num_workers()
287 }
288
289 #[inline]
291 pub fn external_executor_count(&self) -> usize {
292 self.external_executor_count.load(Ordering::SeqCst)
293 }
294
295 pub fn perform_sigint_shutdown(&self) {
301 #[cfg(feature = "fd-track")]
302 let _fd_scope = track_fd_scope("XetRuntime::perform_sigint_shutdown");
303
304 self.sigint_shutdown.store(true, Ordering::SeqCst);
306
307 if cfg!(debug_assertions) {
308 eprintln!("SIGINT detected, shutting down.");
309 }
310
311 let Some(runtime_cell) = self.runtime_cell_if_owned() else {
313 if let Some(monitor) = &self.system_monitor {
314 let _ = monitor.stop();
315 }
316 return;
317 };
318
319 let maybe_runtime = match runtime_cell.write() {
322 Ok(mut guard) => guard.take(),
323 Err(poisoned) => {
324 eprintln!("WARNING: perform_sigint_shutdown encountered a poisoned runtime lock; continuing shutdown.");
325 poisoned.into_inner().take()
326 },
327 };
328
329 let Some(runtime) = maybe_runtime else {
330 eprintln!("WARNING: perform_sigint_shutdown called on runtime that has already been shut down.");
331 if let Some(monitor) = &self.system_monitor {
332 let _ = monitor.stop();
333 }
334 return;
335 };
336
337 drop(runtime);
340
341 if let Some(monitor) = &self.system_monitor {
343 let _ = monitor.stop();
344 }
345 }
346
347 pub fn discard_runtime(&self) {
349 let Some(runtime_cell) = self.runtime_cell_if_owned() else {
352 return;
353 };
354
355 let Ok(mut rt_lock) = runtime_cell.write() else {
361 return;
362 };
363
364 let Some(runtime) = rt_lock.take() else {
365 return;
366 };
367
368 std::mem::forget(runtime);
372 }
373
374 pub fn in_sigint_shutdown(&self) -> bool {
377 self.sigint_shutdown.load(Ordering::SeqCst)
378 }
379
380 fn check_sigint(&self) -> Result<(), RuntimeError> {
381 if self.in_sigint_shutdown() {
382 Err(RuntimeError::KeyboardInterrupt)
383 } else {
384 Ok(())
385 }
386 }
387
388 pub fn external_run_async_task<F>(&self, future: F) -> Result<F::Output, RuntimeError>
391 where
392 F: Future + Send + 'static,
393 F::Output: Send + 'static,
394 {
395 self.external_executor_count.fetch_add(1, Ordering::SeqCst);
396 let _executor_count_guard = CallbackGuard::new(|| {
397 self.external_executor_count.fetch_sub(1, Ordering::SeqCst);
398 });
399
400 self.handle().block_on(async move {
401 self.handle().spawn(future).await.map_err(RuntimeError::from)
404 })
405 }
406
407 pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
409 where
410 F: Future + Send + 'static,
411 F::Output: Send + 'static,
412 {
413 debug!("xet-runtime: spawn called, {}", self);
415 self.handle().spawn(future)
416 }
417
418 pub async fn bridge_async<T, F>(&self, task_name: &'static str, fut: F) -> Result<T, RuntimeError>
427 where
428 F: Future<Output = T> + Send + 'static,
429 T: Send + 'static,
430 {
431 self.check_sigint()?;
432 if std::process::id() != self.creation_pid {
433 return Err(RuntimeError::InvalidRuntime(format!(
434 "XetRuntime was created in process {} but is being used in process {}",
435 self.creation_pid,
436 std::process::id(),
437 )));
438 }
439 match &self.backend {
440 RuntimeBackend::External { .. } => Ok(fut.await),
441 RuntimeBackend::OwnedThreadPool { .. } => self.bridge_to_owned(task_name, fut).await,
442 }
443 }
444
445 pub fn bridge_sync<F>(&self, future: F) -> Result<F::Output, RuntimeError>
456 where
457 F: Future + Send + 'static,
458 F::Output: Send + 'static,
459 {
460 self.check_sigint()?;
461 if std::process::id() != self.creation_pid {
462 return Err(RuntimeError::InvalidRuntime(format!(
463 "XetRuntime was created in process {} but is being used in process {}",
464 self.creation_pid,
465 std::process::id(),
466 )));
467 }
468 if matches!(self.backend, RuntimeBackend::External { .. }) {
469 return Err(RuntimeError::InvalidRuntime(
470 "bridge_sync() cannot be called on an External-mode runtime; \
471 use the async API instead"
472 .into(),
473 ));
474 }
475
476 self.external_executor_count.fetch_add(1, Ordering::SeqCst);
477 let _executor_count_guard = CallbackGuard::new(|| {
478 self.external_executor_count.fetch_sub(1, Ordering::SeqCst);
479 });
480
481 let spawn_handle = self.handle();
482 self.handle()
483 .block_on(async move { spawn_handle.spawn(future).await.map_err(RuntimeError::from) })
484 }
485
486 async fn bridge_to_owned<T, F>(&self, task_name: &'static str, fut: F) -> Result<T, RuntimeError>
498 where
499 F: Future<Output = T> + Send + 'static,
500 T: Send + 'static,
501 {
502 let (tx, rx) = oneshot::channel();
503 self.spawn(async move {
504 let result = AssertUnwindSafe(fut).catch_unwind().await;
505 let _ = tx.send(result);
506 });
507 match rx.await {
508 Ok(Ok(value)) => Ok(value),
509 Ok(Err(panic_payload)) => {
510 let msg = if let Some(s) = panic_payload.downcast_ref::<&str>() {
511 format!("{task_name}: {s}")
512 } else if let Some(s) = panic_payload.downcast_ref::<String>() {
513 format!("{task_name}: {s}")
514 } else {
515 format!("{task_name}: <unknown panic>")
516 };
517 Err(RuntimeError::TaskPanic(msg))
518 },
519 Err(_) => Err(RuntimeError::TaskCanceled(task_name.to_string())),
520 }
521 }
522
523 #[inline]
524 fn runtime_cell_if_owned(&self) -> Option<&OwnedRuntimeCell> {
525 match &self.backend {
526 RuntimeBackend::OwnedThreadPool { runtime } => Some(runtime),
527 RuntimeBackend::External { .. } => None,
528 }
529 }
530
531 pub fn spawn_blocking<F, R>(self: &Arc<Self>, f: F) -> JoinHandle<R>
534 where
535 F: FnOnce() -> R + Send + 'static,
536 R: Send + 'static,
537 {
538 let pool_weak = Arc::downgrade(self);
539 self.handle().spawn_blocking(move || {
540 let pid = std::process::id();
541 THREAD_THREADPOOL_REF.set(Some((pid, pool_weak)));
542 f()
543 })
544 }
545
546 #[inline]
548 pub fn mode(&self) -> RuntimeMode {
549 match &self.backend {
550 RuntimeBackend::External { .. } => RuntimeMode::External,
551 RuntimeBackend::OwnedThreadPool { .. } => RuntimeMode::Owned,
552 }
553 }
554
555 pub fn from_validated_external(
557 rt_handle: TokioRuntimeHandle,
558 config: &XetConfig,
559 ) -> Result<Arc<Self>, RuntimeError> {
560 if !Self::handle_meets_requirements(&rt_handle) {
561 return Err(RuntimeError::InvalidRuntime(
562 "supplied tokio handle does not meet requirements \
563 (missing drivers or wrong flavor)"
564 .into(),
565 ));
566 }
567 Self::from_external_with_config(rt_handle, config)
568 }
569
570 pub fn handle_meets_requirements(handle: &TokioRuntimeHandle) -> bool {
587 if matches!(handle.runtime_flavor(), tokio::runtime::RuntimeFlavor::CurrentThread) {
588 return false;
589 }
590
591 let _guard = handle.enter();
592 let waker = Waker::noop();
593 let mut cx = Context::from_waker(waker);
594
595 let has_time = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
596 let mut sleep = pin!(tokio::time::sleep(std::time::Duration::ZERO));
597 let _ = sleep.as_mut().poll(&mut cx);
598 }))
599 .is_ok();
600
601 let has_io = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
602 let mut bind = pin!(tokio::net::TcpListener::bind("127.0.0.1:0"));
603 let _ = bind.as_mut().poll(&mut cx);
604 }))
605 .is_ok();
606
607 has_time && has_io
608 }
609}
610
611impl Drop for XetRuntime {
612 fn drop(&mut self) {
613 #[cfg(feature = "fd-track")]
614 let _fd_scope = track_fd_scope("XetRuntime::drop");
615
616 self.handle_ref.take();
617
618 if self.creation_pid != std::process::id() {
623 self.discard_runtime();
624 return;
625 }
626
627 if let RuntimeBackend::External { handle_id: Some(id) } = &self.backend {
628 if let Ok(mut reg) = EXTERNAL_THREADPOOL_REGISTRY.write() {
629 reg.remove(id);
630 }
631 return;
632 }
633
634 let in_async_context = TokioRuntimeHandle::try_current().is_ok();
639
640 if !in_async_context
648 && matches!(self.backend, RuntimeBackend::OwnedThreadPool { .. })
649 && let Some(drain) = PRE_SHUTDOWN_DRAIN.get()
650 {
651 drain();
652 }
653
654 if let RuntimeBackend::OwnedThreadPool { runtime } = &self.backend
655 && let Ok(mut guard) = runtime.write()
656 && let Some(rt_arc) = guard.take()
657 && let Ok(rt) = Arc::try_unwrap(rt_arc)
658 {
659 if in_async_context {
660 rt.shutdown_background();
661 } else {
662 rt.shutdown_timeout(std::time::Duration::from_secs(5));
663 }
664 }
665 }
666}
667
668impl Display for XetRuntime {
669 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
670 let metrics = match &self.backend {
671 RuntimeBackend::External { .. } => self.handle().metrics(),
672 RuntimeBackend::OwnedThreadPool { runtime } => {
673 let Ok(runtime_rlg) = runtime.try_read() else {
676 return write!(f, "Locked Tokio Runtime.");
677 };
678
679 let Some(ref runtime) = *runtime_rlg else {
680 return write!(f, "Terminated Tokio Runtime Handle; cancel_all_and_shutdown called.");
681 };
682 runtime.metrics()
683 },
684 };
685
686 write!(
687 f,
688 "pool: num_workers: {:?}, num_alive_tasks: {:?}, global_queue_depth: {:?}",
689 metrics.num_workers(),
690 metrics.num_alive_tasks(),
691 metrics.global_queue_depth()
692 )
693 }
694}
695
696#[cfg(test)]
697mod tests {
698 use super::*;
699 use crate::core::XetContext;
700
701 #[test]
702 fn test_bridge_async_owned_mode_runs_on_pool() {
703 let ctx = XetContext::default().unwrap();
704 assert_eq!(ctx.runtime.mode(), RuntimeMode::Owned);
705 let rt = ctx.runtime.clone();
706 let result = ctx
707 .runtime
708 .bridge_sync(async move { rt.bridge_async("test", async { 42 }).await.unwrap() });
709 assert_eq!(result.unwrap(), 42);
710 }
711
712 #[test]
713 fn test_bridge_async_external_mode_runs_directly() {
714 let tokio_rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
715 let config = XetConfig::new();
716 let rt = XetRuntime::from_external_with_config(tokio_rt.handle().clone(), &config).unwrap();
717 let ctx = XetContext::new(config, rt);
718 assert_eq!(ctx.runtime.mode(), RuntimeMode::External);
719
720 let result = tokio_rt.block_on(async { ctx.runtime.bridge_async("test", async { 99 }).await.unwrap() });
721 assert_eq!(result, 99);
722 }
723
724 #[test]
725 fn test_bridge_sync_owned_mode() {
726 let ctx = XetContext::default().unwrap();
727 assert_eq!(ctx.runtime.mode(), RuntimeMode::Owned);
728 let result = ctx.runtime.bridge_sync(async { 123 }).unwrap();
729 assert_eq!(result, 123);
730 }
731
732 #[test]
733 fn test_default_reuses_owned_xet_runtime_from_tls() {
734 let parent = XetContext::default().unwrap();
735 let parent_runtime = parent.runtime.clone();
736 let parent_config = parent.config.clone();
737
738 let child = parent
739 .runtime
740 .bridge_sync(async move { XetContext::default().unwrap() })
741 .unwrap();
742
743 assert!(Arc::ptr_eq(&child.runtime, &parent_runtime));
744 assert!(!Arc::ptr_eq(&child.config, &parent_config));
745 }
746
747 #[test]
748 fn test_bridge_sync_from_spawn_blocking_owned_mode() {
749 let ctx = XetContext::default().unwrap();
750 let rt = ctx.runtime.clone();
751 let rt2 = ctx.runtime.clone();
752 let jh = rt.spawn_blocking(move || rt2.bridge_sync(async { 456 }).unwrap());
753 let result = ctx.runtime.bridge_sync(async { jh.await.unwrap() }).unwrap();
754 assert_eq!(result, 456);
755 }
756
757 #[test]
758 fn test_bridge_sync_external_mode_returns_error() {
759 let tokio_rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
760 let config = XetConfig::new();
761 let rt = XetRuntime::from_external_with_config(tokio_rt.handle().clone(), &config).unwrap();
762 let ctx = XetContext::new(config, rt);
763 assert_eq!(ctx.runtime.mode(), RuntimeMode::External);
764
765 let result = ctx.runtime.bridge_sync(async { 789 });
766 assert!(matches!(result, Err(RuntimeError::InvalidRuntime(_))));
767 }
768
769 #[test]
770 fn test_handle_meets_requirements_multi_thread_all() {
771 let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
772 assert!(XetRuntime::handle_meets_requirements(rt.handle()));
773 }
774
775 #[test]
776 fn test_handle_meets_requirements_current_thread_rejected() {
777 let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
778 assert!(!XetRuntime::handle_meets_requirements(rt.handle()));
779 }
780
781 #[test]
782 fn test_handle_meets_requirements_no_drivers_rejected() {
783 let rt = tokio::runtime::Builder::new_multi_thread().build().unwrap();
784 assert!(!XetRuntime::handle_meets_requirements(rt.handle()));
785 }
786
787 #[test]
788 fn test_from_validated_external_accepts_valid_handle() {
789 let tokio_rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
790 let config = XetConfig::new();
791 let rt = XetRuntime::from_validated_external(tokio_rt.handle().clone(), &config).unwrap();
792 assert_eq!(rt.mode(), RuntimeMode::External);
793 }
794
795 #[test]
796 fn test_from_validated_external_rejects_current_thread_runtime() {
797 let tokio_rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
798 let config = XetConfig::new();
799 let result = XetRuntime::from_validated_external(tokio_rt.handle().clone(), &config);
800 assert!(matches!(result, Err(RuntimeError::InvalidRuntime(_))));
801 }
802
803 #[test]
804 fn test_from_validated_external_rejects_runtime_without_drivers() {
805 let tokio_rt = tokio::runtime::Builder::new_multi_thread().build().unwrap();
806 let config = XetConfig::new();
807 let result = XetRuntime::from_validated_external(tokio_rt.handle().clone(), &config);
808 assert!(matches!(result, Err(RuntimeError::InvalidRuntime(_))));
809 }
810
811 #[test]
812 fn test_bridge_async_owned_mode_catches_panic() {
813 let ctx = XetContext::default().unwrap();
814 let rt = ctx.runtime.clone();
815 let result = ctx.runtime.bridge_sync(async move {
816 rt.bridge_async("panic_test", async {
817 panic!("intentional test panic");
818 })
819 .await
820 });
821 let err = result.unwrap().unwrap_err();
822 assert!(matches!(err, RuntimeError::TaskPanic(_)));
823 }
824
825 #[test]
826 fn test_context_config_preserved_through_external() {
827 let tokio_rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
828 let mut config = XetConfig::new();
829 config.data.default_cas_endpoint = "https://test-endpoint.example.com".into();
830 let rt = XetRuntime::from_external(tokio_rt.handle().clone());
831 let ctx = XetContext::new(config, rt);
832 assert_eq!(ctx.config.data.default_cas_endpoint, "https://test-endpoint.example.com");
833 }
834
835 #[test]
836 fn test_check_sigint_shutdown_not_triggered() {
837 let ctx = XetContext::default().unwrap();
838 assert!(ctx.check_sigint_shutdown().is_ok());
839 }
840
841 #[test]
842 fn test_from_external_with_config_rejects_second_attach() {
843 let tokio_rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
844 let config = XetConfig::new();
845
846 let first = XetRuntime::from_external_with_config(tokio_rt.handle().clone(), &config).unwrap();
847 let second = XetRuntime::from_external_with_config(tokio_rt.handle().clone(), &config);
848
849 assert!(matches!(second, Err(RuntimeError::ExternalAlreadyAttached(_))));
850 drop(first);
851 }
852
853 #[test]
854 fn test_perform_sigint_shutdown_tolerates_poisoned_runtime_lock() {
855 let ctx = XetContext::default().unwrap();
856 let runtime = ctx.runtime.clone();
857 let runtime_cell = runtime.runtime_cell_if_owned().unwrap().clone();
858
859 let _ = std::thread::spawn(move || {
860 let _guard = runtime_cell.write().unwrap();
861 panic!("intentional poison for test");
862 })
863 .join();
864
865 let shutdown_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
866 runtime.perform_sigint_shutdown();
867 }));
868 assert!(shutdown_result.is_ok());
869 }
870
871 #[test]
872 fn test_sigint_shutdown_causes_keyboard_interrupt_on_bridges() {
873 let ctx = XetContext::default().unwrap();
874 ctx.runtime.perform_sigint_shutdown();
875
876 let sync_result = ctx.runtime.bridge_sync(async { 1 });
877 assert!(matches!(sync_result, Err(RuntimeError::KeyboardInterrupt)));
878
879 let tokio_rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
880 let tp = ctx.runtime.clone();
881 let async_result = tokio_rt.block_on(async move { tp.bridge_async("sigint_test", async { 1 }).await });
882 assert!(matches!(async_result, Err(RuntimeError::KeyboardInterrupt)));
883 }
884
885 #[test]
886 fn test_concurrent_bridge_sync_stress() {
887 use std::sync::Barrier;
888
889 let ctx = XetContext::default().unwrap();
890 let n = 200;
891 let barrier = Arc::new(Barrier::new(n));
892 let sum = Arc::new(AtomicUsize::new(0));
893
894 let handles: Vec<_> = (0..n)
895 .map(|i| {
896 let tp = ctx.runtime.clone();
897 let barrier = barrier.clone();
898 let sum = sum.clone();
899 std::thread::spawn(move || {
900 barrier.wait();
901 let result = tp.bridge_sync(async move { i }).unwrap();
902 sum.fetch_add(result, Ordering::Relaxed);
903 })
904 })
905 .collect();
906
907 for h in handles {
908 h.join().unwrap();
909 }
910
911 assert_eq!(sum.load(Ordering::Relaxed), (0..n).sum::<usize>());
912 }
913}