Skip to main content

xet_runtime/core/
context.rs

1use std::sync::Arc;
2
3#[cfg(not(target_family = "wasm"))]
4use tokio::runtime::Handle as TokioRuntimeHandle;
5#[cfg(not(target_family = "wasm"))]
6use tracing::info;
7
8use super::XetCommon;
9use super::runtime::XetRuntime;
10use crate::config::XetConfig;
11use crate::error::RuntimeError;
12
13/// Bundles the thread pool, configuration, and shared state into a single clonable handle.
14///
15/// Every major struct in the codebase should accept `&XetContext` in its constructor
16/// and store a clone. This replaces the thread-local globals, allowing multiple
17/// independent runtimes within the same process.
18#[derive(Clone)]
19pub struct XetContext {
20    pub runtime: Arc<XetRuntime>,
21    pub config: Arc<XetConfig>,
22    pub common: Arc<XetCommon>,
23}
24
25impl XetContext {
26    /// Creates a context from a pre-built thread pool and configuration.
27    pub fn new(config: XetConfig, runtime: Arc<XetRuntime>) -> Self {
28        let config = Arc::new(config);
29        let common = Arc::new(XetCommon::new(&config));
30        Self {
31            runtime,
32            config,
33            common,
34        }
35    }
36
37    /// Creates a context with default configuration and an auto-detected thread pool.
38    ///
39    /// If called from an owned runtime worker thread, reuses that owned [`XetRuntime`].
40    /// Otherwise, if called from within an existing tokio runtime, wraps that runtime.
41    /// If neither is available, spins up a new owned tokio thread pool.
42    #[allow(clippy::should_implement_trait)]
43    pub fn default() -> Result<Self, RuntimeError> {
44        Self::with_config(XetConfig::new())
45    }
46
47    /// Creates a context with the given configuration and an auto-detected thread pool.
48    ///
49    /// Follows the same runtime selection as [`default`](Self::default):
50    /// reuse an owned runtime if available, wrap an existing tokio handle, or create a new one.
51    pub fn with_config(config: XetConfig) -> Result<Self, RuntimeError> {
52        #[cfg(not(target_family = "wasm"))]
53        let runtime = if let Some(runtime) = XetRuntime::current_if_exists() {
54            runtime
55        } else if let Ok(handle) = TokioRuntimeHandle::try_current()
56            && Self::handle_meets_requirements(&handle)
57        {
58            info!(
59                "Detected compatible existing Tokio runtime; using external handle instead of creating a new thread pool"
60            );
61            XetRuntime::from_external(handle)
62        } else {
63            XetRuntime::new(&config)?
64        };
65
66        #[cfg(target_family = "wasm")]
67        let runtime = XetRuntime::new(&config)?;
68
69        Ok(Self::new(config, runtime))
70    }
71
72    /// Wraps a caller-provided tokio handle with the given configuration.
73    #[cfg(not(target_family = "wasm"))]
74    pub fn from_external(rt_handle: TokioRuntimeHandle, config: XetConfig) -> Self {
75        Self::new(config, XetRuntime::from_external(rt_handle))
76    }
77
78    /// Checks whether a tokio handle meets the requirements for use with xet.
79    #[cfg(not(target_family = "wasm"))]
80    pub fn handle_meets_requirements(handle: &TokioRuntimeHandle) -> bool {
81        XetRuntime::handle_meets_requirements(handle)
82    }
83
84    /// Returns an error if the runtime is in the middle of a SIGINT shutdown.
85    #[inline]
86    pub fn check_sigint_shutdown(&self) -> Result<(), RuntimeError> {
87        if self.runtime.in_sigint_shutdown() {
88            Err(RuntimeError::KeyboardInterrupt)
89        } else {
90            Ok(())
91        }
92    }
93}
94
95impl std::fmt::Debug for XetContext {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        f.debug_struct("XetContext")
98            .field("runtime", &self.runtime)
99            .field("config", &"...")
100            .field("common", &"...")
101            .finish()
102    }
103}