some_executor/
global_executor.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Global executor management for program-wide task execution.
4//!
5//! This module provides functionality for setting and accessing a global executor
6//! that persists for the entire lifetime of the program. This is useful when you
7//! need to spawn tasks from contexts where you cannot easily pass an executor
8//! as a parameter, such as signal handlers or global initialization code.
9//!
10//! # Overview
11//!
12//! The global executor pattern allows you to:
13//! - Set a single executor for the entire program using [`set_global_executor`]
14//! - Access this executor from anywhere using [`global_executor`]
15//! - Spawn tasks without needing to thread an executor through your call stack
16//!
17//! # Important Considerations
18//!
19//! - The global executor can only be set once. Attempting to set it multiple times
20//!   will panic.
21//! - You must initialize the global executor before attempting to use it. Accessing
22//!   an uninitialized global executor will return `None`.
23//! - For most use cases, [`crate::current_executor::current_executor`] is preferred
24//!   as it provides more flexibility and context-aware executor selection.
25//!
26//! # Example Usage
27//!
28//! ```no_run
29//! # // not runnable because we use `todo!()`
30//! use some_executor::global_executor::{set_global_executor, global_executor};
31//! use some_executor::DynExecutor;
32//!
33//! // Initialize the global executor early in your program
34//! let executor: Box<DynExecutor> = todo!(); // Your executor implementation
35//! set_global_executor(executor);
36//!
37//! // Later, from anywhere in your program
38//! global_executor(|e| {
39//!     if let Some(executor) = e {
40//!         // Use the executor
41//!         let mut executor = executor.clone_box();
42//!         // spawn tasks...
43//!     }
44//! });
45//! ```
46
47use crate::DynExecutor;
48use std::sync::OnceLock;
49
50static GLOBAL_RUNTIME: OnceLock<Box<DynExecutor>> = OnceLock::new();
51
52/// Accesses the global executor through a callback function.
53///
54/// This function provides safe access to the global executor (if one has been set)
55/// through a callback pattern. The callback receives an `Option<&DynExecutor>` which
56/// will be `Some` if a global executor has been initialized, or `None` otherwise.
57///
58/// # Arguments
59///
60/// * `c` - A closure that receives `Option<&DynExecutor>` and returns a value of type `R`
61///
62/// # Returns
63///
64/// Whatever value the callback function returns.
65///
66/// # Usage Patterns
67///
68/// ## Cloning the executor for task spawning
69///
70/// ```no_run
71/// # // not runnable because there is no global executor set in this config
72/// use some_executor::global_executor::global_executor;
73/// use some_executor::task::{Task, ConfigurationBuilder};
74///
75/// let mut executor = global_executor(|e| {
76///     e.expect("Global executor not initialized").clone_box()
77/// });
78///
79/// // Now you can spawn tasks
80/// let task = Task::without_notifications(
81///     "my-task".to_string(),
82///     ConfigurationBuilder::new().build(),
83///     async { 42 },
84/// );
85/// # // executor.spawn(task);
86/// ```
87///
88/// ## Checking if executor is available
89///
90/// ```
91/// use some_executor::global_executor::global_executor;
92///
93/// let is_available = global_executor(|e| e.is_some());
94/// if !is_available {
95///     println!("Global executor not initialized");
96/// }
97/// ```
98///
99/// ## Spawning with fallback
100///
101/// ```
102/// use some_executor::global_executor::global_executor;
103/// use some_executor::current_executor::current_executor;
104///
105/// // Try global executor, fall back to current executor
106/// let mut executor = global_executor(|e| {
107///     e.map(|exec| exec.clone_box())
108/// }).unwrap_or_else(|| current_executor());
109/// ```
110///
111/// # Important Notes
112///
113/// - The global executor must be initialized with [`set_global_executor`] before use
114/// - For most use cases, prefer [`crate::current_executor::current_executor`] which
115///   provides more flexible executor discovery
116/// - The callback pattern ensures thread-safe access to the global executor
117pub fn global_executor<R>(c: impl FnOnce(Option<&DynExecutor>) -> R) -> R {
118    let e = GLOBAL_RUNTIME.get();
119    c(e.map(|e| &**e))
120}
121
122/// Sets the global executor for the entire program.
123///
124/// This function initializes the global executor that will be available throughout
125/// the program's lifetime. Once set, the executor can be accessed from anywhere
126/// using [`global_executor`].
127///
128/// # Arguments
129///
130/// * `runtime` - A boxed [`DynExecutor`] that will serve as the global executor
131///
132/// # Panics
133///
134/// This function will panic if called more than once. The global executor can only
135/// be initialized once during the program's lifetime.
136///
137/// # Example
138///
139/// ```
140/// use some_executor::global_executor::set_global_executor;
141/// use some_executor::DynExecutor;
142///
143/// // Early in your program initialization
144/// fn init_executor() {
145///     let executor: Box<DynExecutor> = todo!(); // Your executor implementation
146///     set_global_executor(executor);
147/// }
148/// ```
149///
150/// # Best Practices
151///
152/// - Call this function early in your program's initialization, ideally in `main()`
153///   or during startup
154/// - Ensure the executor is fully configured before setting it as the global executor
155/// - Consider whether you actually need a global executor - in many cases,
156///   [`crate::current_executor`] provides a more flexible solution
157///
158/// # Thread Safety
159///
160/// This function is thread-safe and uses internal synchronization. However, it should
161/// typically be called from a single initialization point to avoid race conditions
162/// where multiple threads attempt to set the global executor.
163pub fn set_global_executor(runtime: Box<DynExecutor>) {
164    GLOBAL_RUNTIME
165        .set(runtime)
166        .expect("Global runtime already set");
167}
168
169#[cfg(test)]
170mod tests {
171    use crate::global_executor::global_executor;
172    use crate::task::{ConfigurationBuilder, Task};
173    use std::any::Any;
174
175    #[cfg_attr(not(target_arch = "wasm32"), test)]
176    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
177    fn global_pattern() {
178        #[allow(unused)]
179        fn dont_execute_just_compile() {
180            let mut runtime = global_executor(|e| e.unwrap().clone_box());
181            let configuration = ConfigurationBuilder::new().build();
182            //get a Box<dyn Any>
183
184            let task = Task::new_objsafe(
185                "test".into(),
186                Box::new(async {
187                    Box::new(()) as Box<dyn Any + Send + 'static>
188                    // todo!()
189                }),
190                configuration,
191                None,
192            );
193            runtime.spawn_objsafe(task);
194        }
195    }
196}