luminal/runtime/
runtime.rs

1//! Main runtime implementation
2//!
3//! This module provides the Runtime implementation, which is the main entry point
4//! for using the Luminal async runtime. It also provides global convenience functions
5//! for spawning tasks and blocking on futures.
6
7use std::future::Future;
8use std::sync::Arc;
9
10use super::executor::Executor;
11use super::handle::Handle;
12use super::join_handle::JoinHandle;
13
14/// Main runtime for executing async tasks
15///
16/// The Runtime is the central coordination point for the Luminal async runtime.
17/// It provides methods for spawning tasks, blocking on futures, and managing
18/// the runtime itself. Unlike tokio, this runtime is safe to pass across
19/// DLL boundaries as it doesn't rely on thread-local storage.
20pub struct Runtime {
21    /// The underlying executor that schedules and runs tasks
22    executor: Arc<Executor>,
23}
24
25impl Runtime {
26    /// Creates a new Luminal runtime
27    ///
28    /// This initializes a new multi-threaded runtime with a work-stealing scheduler
29    /// using the number of available CPU cores. The runtime will create worker
30    /// threads and begin processing the task queue immediately.
31    ///
32    /// # Returns
33    /// 
34    /// A new Runtime instance wrapped in a Result
35    ///
36    /// # Errors
37    ///
38    /// Returns an error if the runtime could not be initialized
39    ///
40    /// # Example
41    ///
42    /// ```
43    /// use luminal::Runtime;
44    ///
45    /// let rt = Runtime::new().unwrap();
46    /// rt.block_on(async {
47    ///     println!("Running on Luminal runtime!");
48    /// });
49    /// ```
50    pub fn new() -> Result<Self, crate::error::RuntimeError> {
51        Ok(Runtime {
52            executor: Arc::new(Executor::new()),
53        })
54    }
55
56    /// Spawns a future onto the runtime
57    ///
58    /// This method takes a future and begins executing it on the runtime,
59    /// returning a JoinHandle that can be used to await its completion and
60    /// retrieve its result.
61    ///
62    /// # Type Parameters
63    ///
64    /// * `F` - The future type
65    ///
66    /// # Parameters
67    ///
68    /// * `future` - The future to execute
69    ///
70    /// # Returns
71    ///
72    /// A JoinHandle that can be used to await the future's completion
73    ///
74    /// # Example
75    ///
76    /// ```
77    /// use luminal::Runtime;
78    ///
79    /// let rt = Runtime::new().unwrap();
80    /// let handle = rt.spawn(async {
81    ///     // Some async work
82    ///     42
83    /// });
84    ///
85    /// let result = rt.block_on(handle); // Waits for the result
86    /// assert_eq!(result, 42);
87    /// ```
88    pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
89    where
90        F: Future + Send + 'static,
91        F::Output: Send + 'static,
92    {
93        self.executor.spawn(future)
94    }
95
96    /// Blocks the current thread until the provided future completes
97    ///
98    /// This method takes a future and blocks the current thread until it completes,
99    /// helping process other tasks while waiting to avoid deadlocks.
100    ///
101    /// # Type Parameters
102    ///
103    /// * `F` - The future type
104    ///
105    /// # Parameters
106    ///
107    /// * `future` - The future to execute and wait for
108    ///
109    /// # Returns
110    ///
111    /// The output of the future
112    ///
113    /// # Example
114    ///
115    /// ```
116    /// use luminal::Runtime;
117    ///
118    /// let rt = Runtime::new().unwrap();
119    /// let result = rt.block_on(async {
120    ///     // Some async work
121    ///     42
122    /// });
123    /// assert_eq!(result, 42);
124    /// ```
125    pub fn block_on<F>(&self, future: F) -> F::Output
126    where
127        F: Future + Send + 'static,
128        F::Output: Send + 'static,
129    {
130        self.executor.block_on(future)
131    }
132
133    /// Returns a Handle to this runtime
134    ///
135    /// The Handle provides a lightweight way to interact with the runtime
136    /// without cloning the entire Runtime.
137    ///
138    /// # Returns
139    ///
140    /// A new Handle to this runtime
141    ///
142    /// # Example
143    ///
144    /// ```
145    /// use luminal::Runtime;
146    ///
147    /// let rt = Runtime::new().unwrap();
148    /// let handle = rt.handle();
149    ///
150    /// // Use the handle to spawn tasks
151    /// let task = handle.spawn(async { 42 });
152    /// ```
153    pub fn handle(&self) -> Handle {
154        Handle::new(self.executor.clone())
155    }
156    
157    /// Returns statistics about the runtime
158    ///
159    /// # Returns
160    ///
161    /// A tuple containing the current queue length and the number of tasks processed
162    ///
163    /// # Example
164    ///
165    /// ```
166    /// use luminal::Runtime;
167    ///
168    /// let rt = Runtime::new().unwrap();
169    /// let (queue_len, tasks_processed) = rt.stats();
170    /// println!("Queue length: {}, Tasks processed: {}", queue_len, tasks_processed);
171    /// ```
172    pub fn stats(&self) -> (usize, usize) {
173        self.executor.stats()
174    }
175}
176
177impl Clone for Runtime {
178    /// Creates a new runtime referring to the same executor
179    ///
180    /// This allows for lightweight cloning of the runtime, as the
181    /// underlying executor is reference-counted.
182    ///
183    /// # Returns
184    ///
185    /// A new Runtime instance referring to the same executor
186    fn clone(&self) -> Self {
187        Runtime {
188            executor: self.executor.clone(),
189        }
190    }
191}
192
193// Thread-local runtime for global convenience functions
194thread_local! {
195    /// Thread-local runtime for global convenience functions
196    ///
197    /// While Luminal generally avoids thread-local storage for its core functionality
198    /// to ensure DLL boundary safety, these convenience functions use a thread-local
199    /// runtime for ease of use when DLL boundary safety isn't a concern.
200    static THREAD_RUNTIME: std::cell::RefCell<Option<Runtime>> = std::cell::RefCell::new(None);
201}
202
203/// Lazily initializes the thread-local runtime if needed and executes the given function with it
204fn with_thread_local_runtime<F, R>(f: F) -> R 
205where
206    F: FnOnce(&Runtime) -> R
207{
208    THREAD_RUNTIME.with(|cell| {
209        if cell.borrow().is_none() {
210            // Initialize the runtime if it doesn't exist yet
211            let rt = Runtime::new().expect("Failed to initialize thread-local runtime");
212            *cell.borrow_mut() = Some(rt);
213        }
214        
215        // Execute the function with a reference to the runtime
216        f(cell.borrow().as_ref().unwrap())
217    })
218}
219
220/// Spawns a future onto the current thread's runtime
221///
222/// This is a convenience function that uses a thread-local runtime.
223/// For DLL boundary safety, create and use an explicit Runtime instead.
224///
225/// # Type Parameters
226///
227/// * `F` - The future type
228///
229/// # Parameters
230///
231/// * `future` - The future to execute
232///
233/// # Returns
234///
235/// A JoinHandle that can be used to await the future's completion
236///
237/// # Example
238///
239/// ```
240/// use luminal::Runtime;
241/// 
242/// // Create an explicit runtime instead of using thread locals for doctests
243/// let rt = Runtime::new().unwrap();
244/// let handle = rt.spawn(async {
245///     // Some async work
246///     42
247/// });
248/// ```
249pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
250where
251    F: Future + Send + 'static,
252    F::Output: Send + 'static,
253{
254    with_thread_local_runtime(|rt| rt.spawn(future))
255}
256
257/// Blocks the current thread until the provided future completes
258///
259/// This is a convenience function that uses a thread-local runtime.
260/// For DLL boundary safety, create and use an explicit Runtime instead.
261///
262/// # Type Parameters
263///
264/// * `F` - The future type
265///
266/// # Parameters
267///
268/// * `future` - The future to execute and wait for
269///
270/// # Returns
271///
272/// The output of the future
273///
274/// # Example
275///
276/// ```
277/// use luminal::Runtime;
278/// 
279/// // Create an explicit runtime instead of using thread locals for doctests
280/// let rt = Runtime::new().unwrap();
281/// let result = rt.block_on(async {
282///     // Some async work
283///     42
284/// });
285/// assert_eq!(result, 42);
286/// ```
287pub fn block_on<F>(future: F) -> F::Output
288where
289    F: Future + Send + 'static,
290    F::Output: Send + 'static,
291{
292    with_thread_local_runtime(|rt| rt.block_on(future))
293}