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}