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
82#[derive(Debug)]
83enum RuntimeBackend {
84 External { handle_id: Option<tokio::runtime::Id> },
85 OwnedThreadPool { runtime: OwnedRuntimeCell },
86}
87
88#[derive(Debug)]
113pub struct XetRuntime {
114 backend: RuntimeBackend,
116
117 handle_ref: OnceLock<TokioRuntimeHandle>,
121
122 external_executor_count: AtomicUsize,
124
125 sigint_shutdown: AtomicBool,
127
128 creation_pid: u32,
130
131 system_monitor: Option<SystemMonitor>,
133}
134
135fn system_monitor_for_config(config: &XetConfig) -> Option<SystemMonitor> {
136 config
137 .system_monitor
138 .enabled
139 .then(|| {
140 SystemMonitor::follow_process(config.system_monitor.sample_interval, config.system_monitor.log_path.clone())
141 .ok()
142 })
143 .flatten()
144}
145
146impl XetRuntime {
147 pub fn new(config: &XetConfig) -> Result<Arc<Self>, RuntimeError> {
149 #[cfg(feature = "fd-track")]
150 let _fd_scope = track_fd_scope("XetRuntime::new");
151
152 let runtime = Arc::new(std::sync::RwLock::new(None));
153
154 let rt = Arc::new(Self {
155 backend: RuntimeBackend::OwnedThreadPool {
156 runtime: runtime.clone(),
157 },
158 handle_ref: OnceLock::new(),
159 external_executor_count: 0.into(),
160 sigint_shutdown: false.into(),
161 creation_pid: std::process::id(),
162 system_monitor: system_monitor_for_config(config),
163 });
164
165 let rt_weak = Arc::downgrade(&rt);
166 let pid = std::process::id();
167 let set_threadlocal_reference = move || {
168 THREAD_THREADPOOL_REF.set(Some((pid, rt_weak.clone())));
169 };
170
171 let thread_id = AtomicUsize::new(0);
172 let get_thread_name = move || {
173 let id = thread_id.fetch_add(1, Ordering::Relaxed);
174 format!("{THREADPOOL_THREAD_ID_PREFIX}-{id}")
175 };
176
177 let tokio_rt = TokioRuntimeBuilder::new_multi_thread()
178 .worker_threads(get_num_tokio_worker_threads())
179 .on_thread_start(set_threadlocal_reference)
180 .thread_keep_alive(std::time::Duration::from_millis(100))
181 .thread_name_fn(get_thread_name)
182 .thread_stack_size(THREADPOOL_STACK_SIZE)
183 .enable_all()
184 .build()
185 .map_err(RuntimeError::RuntimeInit)?;
186
187 let handle = tokio_rt.handle().clone();
188 let tokio_rt = Arc::new(tokio_rt);
189 *runtime.write().unwrap() = Some(tokio_rt);
190 rt.handle_ref.set(handle).unwrap();
191
192 #[cfg(feature = "fd-track")]
193 report_fd_count("XetRuntime::new complete");
194
195 Ok(rt)
196 }
197
198 pub fn from_external_with_config(
201 rt_handle: TokioRuntimeHandle,
202 config: &XetConfig,
203 ) -> Result<Arc<Self>, RuntimeError> {
204 #[cfg(feature = "fd-track")]
205 let _fd_scope = track_fd_scope("XetRuntime::from_external_with_config");
206
207 let id = rt_handle.id();
208
209 let mut reg = EXTERNAL_THREADPOOL_REGISTRY.write()?;
210 if let Some(existing) = reg.get(&id)
211 && existing.upgrade().is_some()
212 {
213 return Err(RuntimeError::ExternalAlreadyAttached(id));
214 }
215
216 let rt = Arc::new(Self {
217 backend: RuntimeBackend::External { handle_id: Some(id) },
218 handle_ref: rt_handle.into(),
219 external_executor_count: 0.into(),
220 sigint_shutdown: false.into(),
221 creation_pid: std::process::id(),
222 system_monitor: system_monitor_for_config(config),
223 });
224
225 reg.insert(id, Arc::downgrade(&rt));
226
227 #[cfg(feature = "fd-track")]
228 report_fd_count("XetRuntime::from_external_with_config complete");
229
230 Ok(rt)
231 }
232
233 pub fn from_external(rt_handle: TokioRuntimeHandle) -> Arc<Self> {
235 Arc::new(Self {
236 backend: RuntimeBackend::External { handle_id: None },
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: None,
242 })
243 }
244
245 #[inline]
250 pub fn current_if_exists() -> Option<Arc<Self>> {
251 let pid = std::process::id();
252 THREAD_THREADPOOL_REF.with_borrow(|entry| {
253 entry
254 .as_ref()
255 .filter(|(entry_pid, _)| *entry_pid == pid)
256 .and_then(|(_, weak_pool)| weak_pool.upgrade())
257 })
258 }
259
260 #[inline]
261 pub fn handle(&self) -> TokioRuntimeHandle {
262 self.handle_ref.get().expect("Not initialized with handle set.").clone()
263 }
264
265 #[inline]
266 pub fn num_worker_threads(&self) -> usize {
267 self.handle().metrics().num_workers()
268 }
269
270 #[inline]
272 pub fn external_executor_count(&self) -> usize {
273 self.external_executor_count.load(Ordering::SeqCst)
274 }
275
276 pub fn perform_sigint_shutdown(&self) {
282 #[cfg(feature = "fd-track")]
283 let _fd_scope = track_fd_scope("XetRuntime::perform_sigint_shutdown");
284
285 self.sigint_shutdown.store(true, Ordering::SeqCst);
287
288 if cfg!(debug_assertions) {
289 eprintln!("SIGINT detected, shutting down.");
290 }
291
292 let Some(runtime_cell) = self.runtime_cell_if_owned() else {
294 if let Some(monitor) = &self.system_monitor {
295 let _ = monitor.stop();
296 }
297 return;
298 };
299
300 let maybe_runtime = match runtime_cell.write() {
303 Ok(mut guard) => guard.take(),
304 Err(poisoned) => {
305 eprintln!("WARNING: perform_sigint_shutdown encountered a poisoned runtime lock; continuing shutdown.");
306 poisoned.into_inner().take()
307 },
308 };
309
310 let Some(runtime) = maybe_runtime else {
311 eprintln!("WARNING: perform_sigint_shutdown called on runtime that has already been shut down.");
312 if let Some(monitor) = &self.system_monitor {
313 let _ = monitor.stop();
314 }
315 return;
316 };
317
318 drop(runtime);
321
322 if let Some(monitor) = &self.system_monitor {
324 let _ = monitor.stop();
325 }
326 }
327
328 pub fn discard_runtime(&self) {
330 let Some(runtime_cell) = self.runtime_cell_if_owned() else {
333 return;
334 };
335
336 let Ok(mut rt_lock) = runtime_cell.write() else {
342 return;
343 };
344
345 let Some(runtime) = rt_lock.take() else {
346 return;
347 };
348
349 std::mem::forget(runtime);
353 }
354
355 pub fn in_sigint_shutdown(&self) -> bool {
358 self.sigint_shutdown.load(Ordering::SeqCst)
359 }
360
361 fn check_sigint(&self) -> Result<(), RuntimeError> {
362 if self.in_sigint_shutdown() {
363 Err(RuntimeError::KeyboardInterrupt)
364 } else {
365 Ok(())
366 }
367 }
368
369 pub fn external_run_async_task<F>(&self, future: F) -> Result<F::Output, RuntimeError>
372 where
373 F: Future + Send + 'static,
374 F::Output: Send + 'static,
375 {
376 self.external_executor_count.fetch_add(1, Ordering::SeqCst);
377 let _executor_count_guard = CallbackGuard::new(|| {
378 self.external_executor_count.fetch_sub(1, Ordering::SeqCst);
379 });
380
381 self.handle().block_on(async move {
382 self.handle().spawn(future).await.map_err(RuntimeError::from)
385 })
386 }
387
388 pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
390 where
391 F: Future + Send + 'static,
392 F::Output: Send + 'static,
393 {
394 debug!("xet-runtime: spawn called, {}", self);
396 self.handle().spawn(future)
397 }
398
399 pub async fn bridge_async<T, F>(&self, task_name: &'static str, fut: F) -> Result<T, RuntimeError>
408 where
409 F: Future<Output = T> + Send + 'static,
410 T: Send + 'static,
411 {
412 self.check_sigint()?;
413 if std::process::id() != self.creation_pid {
414 return Err(RuntimeError::InvalidRuntime(format!(
415 "XetRuntime was created in process {} but is being used in process {}",
416 self.creation_pid,
417 std::process::id(),
418 )));
419 }
420 match &self.backend {
421 RuntimeBackend::External { .. } => Ok(fut.await),
422 RuntimeBackend::OwnedThreadPool { .. } => self.bridge_to_owned(task_name, fut).await,
423 }
424 }
425
426 pub fn bridge_sync<F>(&self, future: F) -> Result<F::Output, RuntimeError>
437 where
438 F: Future + Send + 'static,
439 F::Output: Send + 'static,
440 {
441 self.check_sigint()?;
442 if std::process::id() != self.creation_pid {
443 return Err(RuntimeError::InvalidRuntime(format!(
444 "XetRuntime was created in process {} but is being used in process {}",
445 self.creation_pid,
446 std::process::id(),
447 )));
448 }
449 if matches!(self.backend, RuntimeBackend::External { .. }) {
450 return Err(RuntimeError::InvalidRuntime(
451 "bridge_sync() cannot be called on an External-mode runtime; \
452 use the async API instead"
453 .into(),
454 ));
455 }
456
457 self.external_executor_count.fetch_add(1, Ordering::SeqCst);
458 let _executor_count_guard = CallbackGuard::new(|| {
459 self.external_executor_count.fetch_sub(1, Ordering::SeqCst);
460 });
461
462 let spawn_handle = self.handle();
463 self.handle()
464 .block_on(async move { spawn_handle.spawn(future).await.map_err(RuntimeError::from) })
465 }
466
467 async fn bridge_to_owned<T, F>(&self, task_name: &'static str, fut: F) -> Result<T, RuntimeError>
479 where
480 F: Future<Output = T> + Send + 'static,
481 T: Send + 'static,
482 {
483 let (tx, rx) = oneshot::channel();
484 self.spawn(async move {
485 let result = AssertUnwindSafe(fut).catch_unwind().await;
486 let _ = tx.send(result);
487 });
488 match rx.await {
489 Ok(Ok(value)) => Ok(value),
490 Ok(Err(panic_payload)) => {
491 let msg = if let Some(s) = panic_payload.downcast_ref::<&str>() {
492 format!("{task_name}: {s}")
493 } else if let Some(s) = panic_payload.downcast_ref::<String>() {
494 format!("{task_name}: {s}")
495 } else {
496 format!("{task_name}: <unknown panic>")
497 };
498 Err(RuntimeError::TaskPanic(msg))
499 },
500 Err(_) => Err(RuntimeError::TaskCanceled(task_name.to_string())),
501 }
502 }
503
504 #[inline]
505 fn runtime_cell_if_owned(&self) -> Option<&OwnedRuntimeCell> {
506 match &self.backend {
507 RuntimeBackend::OwnedThreadPool { runtime } => Some(runtime),
508 RuntimeBackend::External { .. } => None,
509 }
510 }
511
512 pub fn spawn_blocking<F, R>(self: &Arc<Self>, f: F) -> JoinHandle<R>
515 where
516 F: FnOnce() -> R + Send + 'static,
517 R: Send + 'static,
518 {
519 let pool_weak = Arc::downgrade(self);
520 self.handle().spawn_blocking(move || {
521 let pid = std::process::id();
522 THREAD_THREADPOOL_REF.set(Some((pid, pool_weak)));
523 f()
524 })
525 }
526
527 #[inline]
529 pub fn mode(&self) -> RuntimeMode {
530 match &self.backend {
531 RuntimeBackend::External { .. } => RuntimeMode::External,
532 RuntimeBackend::OwnedThreadPool { .. } => RuntimeMode::Owned,
533 }
534 }
535
536 pub fn from_validated_external(
538 rt_handle: TokioRuntimeHandle,
539 config: &XetConfig,
540 ) -> Result<Arc<Self>, RuntimeError> {
541 if !Self::handle_meets_requirements(&rt_handle) {
542 return Err(RuntimeError::InvalidRuntime(
543 "supplied tokio handle does not meet requirements \
544 (missing drivers or wrong flavor)"
545 .into(),
546 ));
547 }
548 Self::from_external_with_config(rt_handle, config)
549 }
550
551 pub fn handle_meets_requirements(handle: &TokioRuntimeHandle) -> bool {
568 if matches!(handle.runtime_flavor(), tokio::runtime::RuntimeFlavor::CurrentThread) {
569 return false;
570 }
571
572 let _guard = handle.enter();
573 let waker = Waker::noop();
574 let mut cx = Context::from_waker(waker);
575
576 let has_time = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
577 let mut sleep = pin!(tokio::time::sleep(std::time::Duration::ZERO));
578 let _ = sleep.as_mut().poll(&mut cx);
579 }))
580 .is_ok();
581
582 let has_io = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
583 let mut bind = pin!(tokio::net::TcpListener::bind("127.0.0.1:0"));
584 let _ = bind.as_mut().poll(&mut cx);
585 }))
586 .is_ok();
587
588 has_time && has_io
589 }
590}
591
592impl Drop for XetRuntime {
593 fn drop(&mut self) {
594 #[cfg(feature = "fd-track")]
595 let _fd_scope = track_fd_scope("XetRuntime::drop");
596
597 self.handle_ref.take();
598
599 if self.creation_pid != std::process::id() {
604 self.discard_runtime();
605 return;
606 }
607
608 if let RuntimeBackend::External { handle_id: Some(id) } = &self.backend {
609 if let Ok(mut reg) = EXTERNAL_THREADPOOL_REGISTRY.write() {
610 reg.remove(id);
611 }
612 return;
613 }
614
615 let in_async_context = TokioRuntimeHandle::try_current().is_ok();
620 if let RuntimeBackend::OwnedThreadPool { runtime } = &self.backend
621 && let Ok(mut guard) = runtime.write()
622 && let Some(rt_arc) = guard.take()
623 && let Ok(rt) = Arc::try_unwrap(rt_arc)
624 {
625 if in_async_context {
626 rt.shutdown_background();
627 } else {
628 rt.shutdown_timeout(std::time::Duration::from_secs(5));
629 }
630 }
631 }
632}
633
634impl Display for XetRuntime {
635 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
636 let metrics = match &self.backend {
637 RuntimeBackend::External { .. } => self.handle().metrics(),
638 RuntimeBackend::OwnedThreadPool { runtime } => {
639 let Ok(runtime_rlg) = runtime.try_read() else {
642 return write!(f, "Locked Tokio Runtime.");
643 };
644
645 let Some(ref runtime) = *runtime_rlg else {
646 return write!(f, "Terminated Tokio Runtime Handle; cancel_all_and_shutdown called.");
647 };
648 runtime.metrics()
649 },
650 };
651
652 write!(
653 f,
654 "pool: num_workers: {:?}, num_alive_tasks: {:?}, global_queue_depth: {:?}",
655 metrics.num_workers(),
656 metrics.num_alive_tasks(),
657 metrics.global_queue_depth()
658 )
659 }
660}
661
662#[cfg(test)]
663mod tests {
664 use super::*;
665 use crate::core::XetContext;
666
667 #[test]
668 fn test_bridge_async_owned_mode_runs_on_pool() {
669 let ctx = XetContext::default().unwrap();
670 assert_eq!(ctx.runtime.mode(), RuntimeMode::Owned);
671 let rt = ctx.runtime.clone();
672 let result = ctx
673 .runtime
674 .bridge_sync(async move { rt.bridge_async("test", async { 42 }).await.unwrap() });
675 assert_eq!(result.unwrap(), 42);
676 }
677
678 #[test]
679 fn test_bridge_async_external_mode_runs_directly() {
680 let tokio_rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
681 let config = XetConfig::new();
682 let rt = XetRuntime::from_external_with_config(tokio_rt.handle().clone(), &config).unwrap();
683 let ctx = XetContext::new(config, rt);
684 assert_eq!(ctx.runtime.mode(), RuntimeMode::External);
685
686 let result = tokio_rt.block_on(async { ctx.runtime.bridge_async("test", async { 99 }).await.unwrap() });
687 assert_eq!(result, 99);
688 }
689
690 #[test]
691 fn test_bridge_sync_owned_mode() {
692 let ctx = XetContext::default().unwrap();
693 assert_eq!(ctx.runtime.mode(), RuntimeMode::Owned);
694 let result = ctx.runtime.bridge_sync(async { 123 }).unwrap();
695 assert_eq!(result, 123);
696 }
697
698 #[test]
699 fn test_default_reuses_owned_xet_runtime_from_tls() {
700 let parent = XetContext::default().unwrap();
701 let parent_runtime = parent.runtime.clone();
702 let parent_config = parent.config.clone();
703
704 let child = parent
705 .runtime
706 .bridge_sync(async move { XetContext::default().unwrap() })
707 .unwrap();
708
709 assert!(Arc::ptr_eq(&child.runtime, &parent_runtime));
710 assert!(!Arc::ptr_eq(&child.config, &parent_config));
711 }
712
713 #[test]
714 fn test_bridge_sync_from_spawn_blocking_owned_mode() {
715 let ctx = XetContext::default().unwrap();
716 let rt = ctx.runtime.clone();
717 let rt2 = ctx.runtime.clone();
718 let jh = rt.spawn_blocking(move || rt2.bridge_sync(async { 456 }).unwrap());
719 let result = ctx.runtime.bridge_sync(async { jh.await.unwrap() }).unwrap();
720 assert_eq!(result, 456);
721 }
722
723 #[test]
724 fn test_bridge_sync_external_mode_returns_error() {
725 let tokio_rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
726 let config = XetConfig::new();
727 let rt = XetRuntime::from_external_with_config(tokio_rt.handle().clone(), &config).unwrap();
728 let ctx = XetContext::new(config, rt);
729 assert_eq!(ctx.runtime.mode(), RuntimeMode::External);
730
731 let result = ctx.runtime.bridge_sync(async { 789 });
732 assert!(matches!(result, Err(RuntimeError::InvalidRuntime(_))));
733 }
734
735 #[test]
736 fn test_handle_meets_requirements_multi_thread_all() {
737 let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
738 assert!(XetRuntime::handle_meets_requirements(rt.handle()));
739 }
740
741 #[test]
742 fn test_handle_meets_requirements_current_thread_rejected() {
743 let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
744 assert!(!XetRuntime::handle_meets_requirements(rt.handle()));
745 }
746
747 #[test]
748 fn test_handle_meets_requirements_no_drivers_rejected() {
749 let rt = tokio::runtime::Builder::new_multi_thread().build().unwrap();
750 assert!(!XetRuntime::handle_meets_requirements(rt.handle()));
751 }
752
753 #[test]
754 fn test_from_validated_external_accepts_valid_handle() {
755 let tokio_rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
756 let config = XetConfig::new();
757 let rt = XetRuntime::from_validated_external(tokio_rt.handle().clone(), &config).unwrap();
758 assert_eq!(rt.mode(), RuntimeMode::External);
759 }
760
761 #[test]
762 fn test_from_validated_external_rejects_current_thread_runtime() {
763 let tokio_rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
764 let config = XetConfig::new();
765 let result = XetRuntime::from_validated_external(tokio_rt.handle().clone(), &config);
766 assert!(matches!(result, Err(RuntimeError::InvalidRuntime(_))));
767 }
768
769 #[test]
770 fn test_from_validated_external_rejects_runtime_without_drivers() {
771 let tokio_rt = tokio::runtime::Builder::new_multi_thread().build().unwrap();
772 let config = XetConfig::new();
773 let result = XetRuntime::from_validated_external(tokio_rt.handle().clone(), &config);
774 assert!(matches!(result, Err(RuntimeError::InvalidRuntime(_))));
775 }
776
777 #[test]
778 fn test_bridge_async_owned_mode_catches_panic() {
779 let ctx = XetContext::default().unwrap();
780 let rt = ctx.runtime.clone();
781 let result = ctx.runtime.bridge_sync(async move {
782 rt.bridge_async("panic_test", async {
783 panic!("intentional test panic");
784 })
785 .await
786 });
787 let err = result.unwrap().unwrap_err();
788 assert!(matches!(err, RuntimeError::TaskPanic(_)));
789 }
790
791 #[test]
792 fn test_context_config_preserved_through_external() {
793 let tokio_rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
794 let mut config = XetConfig::new();
795 config.data.default_cas_endpoint = "https://test-endpoint.example.com".into();
796 let rt = XetRuntime::from_external(tokio_rt.handle().clone());
797 let ctx = XetContext::new(config, rt);
798 assert_eq!(ctx.config.data.default_cas_endpoint, "https://test-endpoint.example.com");
799 }
800
801 #[test]
802 fn test_check_sigint_shutdown_not_triggered() {
803 let ctx = XetContext::default().unwrap();
804 assert!(ctx.check_sigint_shutdown().is_ok());
805 }
806
807 #[test]
808 fn test_from_external_with_config_rejects_second_attach() {
809 let tokio_rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
810 let config = XetConfig::new();
811
812 let first = XetRuntime::from_external_with_config(tokio_rt.handle().clone(), &config).unwrap();
813 let second = XetRuntime::from_external_with_config(tokio_rt.handle().clone(), &config);
814
815 assert!(matches!(second, Err(RuntimeError::ExternalAlreadyAttached(_))));
816 drop(first);
817 }
818
819 #[test]
820 fn test_perform_sigint_shutdown_tolerates_poisoned_runtime_lock() {
821 let ctx = XetContext::default().unwrap();
822 let runtime = ctx.runtime.clone();
823 let runtime_cell = runtime.runtime_cell_if_owned().unwrap().clone();
824
825 let _ = std::thread::spawn(move || {
826 let _guard = runtime_cell.write().unwrap();
827 panic!("intentional poison for test");
828 })
829 .join();
830
831 let shutdown_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
832 runtime.perform_sigint_shutdown();
833 }));
834 assert!(shutdown_result.is_ok());
835 }
836
837 #[test]
838 fn test_sigint_shutdown_causes_keyboard_interrupt_on_bridges() {
839 let ctx = XetContext::default().unwrap();
840 ctx.runtime.perform_sigint_shutdown();
841
842 let sync_result = ctx.runtime.bridge_sync(async { 1 });
843 assert!(matches!(sync_result, Err(RuntimeError::KeyboardInterrupt)));
844
845 let tokio_rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
846 let tp = ctx.runtime.clone();
847 let async_result = tokio_rt.block_on(async move { tp.bridge_async("sigint_test", async { 1 }).await });
848 assert!(matches!(async_result, Err(RuntimeError::KeyboardInterrupt)));
849 }
850
851 #[test]
852 fn test_concurrent_bridge_sync_stress() {
853 use std::sync::Barrier;
854
855 let ctx = XetContext::default().unwrap();
856 let n = 200;
857 let barrier = Arc::new(Barrier::new(n));
858 let sum = Arc::new(AtomicUsize::new(0));
859
860 let handles: Vec<_> = (0..n)
861 .map(|i| {
862 let tp = ctx.runtime.clone();
863 let barrier = barrier.clone();
864 let sum = sum.clone();
865 std::thread::spawn(move || {
866 barrier.wait();
867 let result = tp.bridge_sync(async move { i }).unwrap();
868 sum.fetch_add(result, Ordering::Relaxed);
869 })
870 })
871 .collect();
872
873 for h in handles {
874 h.join().unwrap();
875 }
876
877 assert_eq!(sum.load(Ordering::Relaxed), (0..n).sum::<usize>());
878 }
879}