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