spawns_core/
compat.rs

1use crate::Task;
2use linkme::distributed_slice;
3use std::sync::OnceLock;
4
5/// Item of [COMPATS] to encapsulate functions to spawn task for async runtime.
6#[non_exhaustive]
7pub enum Compat {
8    /// Named global function to spawn task.
9    NamedGlobal { name: &'static str, spawn: fn(Task) },
10    /// Global function to spawn task.
11    #[doc(hidden)]
12    #[deprecated(since = "1.0.3", note = "use NamedGlobal instead")]
13    Global(fn(Task)),
14    #[allow(clippy::type_complexity)]
15    /// Local function to detect async runtimes.
16    Local(fn() -> Option<fn(Task)>),
17}
18
19/// [DistributedSlice][linkme::DistributedSlice] to collect [Compat]s from
20/// [distributed_slice][linkme::distributed_slice].
21///
22/// Here are examples for `tokio` and `smol`, see [linkme] for details.
23///
24/// ```rust,no_run
25/// use spawns_core as spawns;
26///
27/// use linkme::distributed_slice;
28/// use spawns::{Compat, Task, COMPATS};
29///
30/// #[distributed_slice(COMPATS)]
31/// static TOKIO: Compat = Compat::Local(tokio_local);
32///
33/// fn tokio_spawn(task: Task) {
34///     let Task { future, .. } = task;
35///     let handle = tokio::runtime::Handle::current();
36///     handle.spawn(Box::into_pin(future));
37/// }
38///
39/// fn tokio_local() -> Option<fn(Task)> {
40///     tokio::runtime::Handle::try_current()
41///         .ok()
42///         .map(|_| tokio_spawn as fn(Task))
43/// }
44///
45/// #[distributed_slice(COMPATS)]
46/// static SMOL: Compat = Compat::NamedGlobal {
47///     name: "smol",
48///     spawn: smol_global,
49/// };
50///
51/// fn smol_global(task: Task) {
52///     let Task { future, .. } = task;
53///     smol::spawn(Box::into_pin(future)).detach()
54/// }
55/// ```
56#[distributed_slice]
57pub static COMPATS: [Compat] = [..];
58
59#[derive(Clone, Copy)]
60pub(crate) enum Failure {
61    NotFound,
62    #[allow(dead_code)]
63    MultipleGlobals,
64}
65
66fn pick_global(choose: Option<&str>) -> Result<fn(Task), Failure> {
67    let mut globals = 0;
68    let mut last_named = None;
69    let mut last_unnamed = None;
70    match COMPATS.iter().find_map(|compat| match compat {
71        Compat::Local(_) => None,
72        #[allow(deprecated)]
73        Compat::Global(global) => {
74            globals += 1;
75            last_unnamed = Some(global);
76            None
77        }
78        Compat::NamedGlobal { spawn, name } => {
79            if choose == Some(name) {
80                Some(spawn)
81            } else {
82                globals += 1;
83                last_named = Some(spawn);
84                None
85            }
86        }
87    }) {
88        Some(spawn) => Ok(*spawn),
89        None => {
90            #[cfg(feature = "panic-multiple-global-spawners")]
91            if globals > 1 {
92                return Err(Failure::MultipleGlobals);
93            }
94            last_named
95                .or(last_unnamed)
96                .ok_or(Failure::NotFound)
97                .copied()
98        }
99    }
100}
101
102fn find_global() -> Result<fn(Task), Failure> {
103    static FOUND: OnceLock<Result<fn(Task), Failure>> = OnceLock::new();
104    if let Some(found) = FOUND.get() {
105        return *found;
106    }
107    let choose = std::env::var("SPAWNS_GLOBAL_SPAWNER").ok();
108    let result = pick_global(choose.as_deref());
109    *FOUND.get_or_init(|| result)
110}
111
112fn find_local() -> Option<fn(Task)> {
113    COMPATS.iter().find_map(|compat| match compat {
114        Compat::Local(local) => local(),
115        #[allow(deprecated)]
116        Compat::Global(_) => None,
117        Compat::NamedGlobal { .. } => None,
118    })
119}
120
121pub(crate) fn find_spawn() -> Option<fn(Task)> {
122    match COMPATS.len() {
123        0 => return None,
124        1 => match COMPATS[0] {
125            Compat::NamedGlobal { spawn, .. } => return Some(spawn),
126            #[allow(deprecated)]
127            Compat::Global(spawn) => return Some(spawn),
128            Compat::Local(local) => return local(),
129        },
130        _ => {}
131    }
132    match find_local()
133        .ok_or(Failure::NotFound)
134        .or_else(|_| find_global())
135    {
136        Ok(spawn) => Some(spawn),
137        Err(Failure::NotFound) => None,
138        Err(Failure::MultipleGlobals) => panic!("multiple global spawners"),
139    }
140}