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}