some_executor 0.7.2

A trait for libraries that abstract over any executor
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0

/*!
An executor-agnostic program entry point.

[`ExecutorMain`] is the trait spelling of "construct the chosen executor, install it,
and run this future." It exists so an attribute macro can expand to something that
compiles against a backend the macro has never heard of:

```text
#[some_executor::main(some_executor_tokio::TokioExecutor)]
async fn main() { .. }
```

expands to roughly

```text
fn main() {
    <some_executor_tokio::TokioExecutor as some_executor::ExecutorMain>::main(async { .. })
}
```

The macro cannot know an inherent method on an arbitrary backend type, so the call has
to go through a trait this crate defines. That is the whole reason [`ExecutorMain`] is
separate from [`block_on`](fn@crate::block_on): `block_on` is for code that already has an
executor, and this is for code that is choosing one.

# Why it returns `()`

An entry point cannot hand a value back on every platform. On the wasm32 main thread
`fn main` returns while the program keeps running on the event loop, so there is no
moment at which a result could be produced — the honest signature there is "start this
and return." Making the trait return `()` everywhere is what lets a wasm backend
implement it at all, and therefore what makes `#[some_executor::main]` portable.

An `async fn main` that wants to return a `Result` handles it inside the future:

```
# use std::future::Future;
# fn wrapper<E: some_executor::ExecutorMain>() {
E::main(async {
    if let Err(e) = fallible().await {
        eprintln!("error: {e}");
        std::process::exit(1);
    }
});
# }
# async fn fallible() -> Result<(), String> { Ok(()) }
```

That is what the attribute macro generates for a fallible `main`.

# Implementing it

A backend that can block does the obvious three steps -- construct, install globally,
drive -- and [`run_main`] packages them, so the whole implementation is one line:

```
use some_executor::{SomeExecutorExt, entry_point::run_main};
use std::future::Future;

// The body a blocking backend writes for `ExecutorMain::main`:
fn main_impl<E, F>(future: F)
where
    E: SomeExecutorExt + Default + 'static,
    F: Future<Output = ()> + 'static,
{
    run_main(E::default(), future)
}
```

A wasm32 main-thread backend cannot block, so it installs itself and hands the future to
the event loop instead — which is exactly why the return type is `()`:

```text
fn main<F: Future<Output = ()> + 'static>(future: F) {
    set_global_executor(Box::new(MyWasmExecutor::new()));
    wasm_lite_std::spawn_local(future);
}
```
*/

use crate::SomeExecutorExt;
use crate::global_executor::set_global_executor;
use std::fmt::Debug;
use std::future::Future;

/// Constructs an executor, installs it, and runs `future` as the program's main task.
///
/// Implemented by executor backends and called by `#[some_executor::main]`. See the
/// [module documentation](self) for why this is an associated function returning `()`
/// rather than a method returning the future's output.
///
/// # Contract
///
/// - Called at most once per process, from `fn main`, before any other executor is
///   installed.
/// - The implementation should install itself via
///   [`set_global_executor`] so that code
///   anywhere in the program can spawn.
/// - It should run `future` to completion where it can, and — where it cannot, such as
///   the wasm32 main thread — hand it to the platform's event loop and return.
pub trait ExecutorMain {
    /// Runs `future` as the program's main task.
    ///
    /// `'static` is required because a backend that cannot block has to hand the future
    /// to an event loop that outlives this call.
    fn main<F: Future<Output = ()> + 'static>(future: F);
}

/// The ordinary [`ExecutorMain::main`] body for a backend that can block: install
/// `executor` globally, then drive `future` on it.
///
/// # Panics
///
/// Panics if a global executor is already set, which
/// [`set_global_executor`] does not allow.
/// That is the right behavior for an entry point — two of them in one process is a bug —
/// but it does mean this function cannot be called twice, including from two tests in
/// the same process.
///
/// Also panics on the wasm32 main thread, which cannot block; see
/// [`block_on`](fn@crate::block_on).
pub fn run_main<E, F>(mut executor: E, future: F)
where
    E: SomeExecutorExt + 'static,
    F: Future<Output = ()> + 'static,
{
    set_global_executor(executor.clone_box());
    executor.block_on(future);
}

/// The backend `#[some_executor::main]` uses when no argument is given.
///
/// This is the crate's built-in fallback executor, which is always available
/// and needs no dependency. It is meant for quick demos, examples and
/// doctests; it warns on first use precisely because it is not what a real
/// program should be running on.
///
/// ```no_run
/// # // no_run because: the attribute generates this doctest's own `fn main`, so
/// # // running it would install a process-wide executor inside the test harness.
/// #[some_executor::main]
/// async fn main() {
///     println!("no backend named, so this ran on LastResort");
/// }
/// ```
///
/// # Platform behaviour
///
/// It blocks, so on the wasm32 main thread it panics like anything else that
/// blocks there. A wasm32 program names a backend that can hand its future to
/// the event loop instead; that is the case [`ExecutorMain`] returning `()`
/// exists for.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
pub struct LastResort;

impl ExecutorMain for LastResort {
    fn main<F: Future<Output = ()> + 'static>(future: F) {
        // Not `run_main`: the fallback executor is object-safe `SomeExecutor`
        // only, and `run_main` needs `SomeExecutorExt` for its `block_on`. The
        // two steps are the same either way -- install it so anything in the
        // program can spawn, then drive the main future on the free
        // `block_on`.
        set_global_executor(Box::new(crate::last_resort::LastResortExecutor::new()));
        crate::block_on(future);
    }
}

/// What `#[some_executor::main]` does with whatever an `async fn main`
/// returned.
///
/// The macro always emits `MainResult::report(..)` and lets the compiler pick
/// the impl, so it never has to inspect the declared return type — and a `main`
/// returning something unsupported gets "`MainResult` is not implemented for
/// `X`" rather than a mystery inside an expansion nobody wrote.
///
/// Putting the policy here rather than in generated code is deliberate: it is
/// documented, it is tested, and changing it does not mean changing what every
/// existing crate's `fn main` expands to.
pub trait MainResult {
    /// Called with the value the future produced, on the executor's thread.
    fn report(self);
}

/// An infallible `main` has nothing to report.
impl MainResult for () {
    fn report(self) {}
}

/// A fallible `main` logs its error and brings the process down.
///
/// Panicking rather than `std::process::exit(1)`: an exit code is meaningless
/// on the wasm32 main thread, where `fn main` returns while the program keeps
/// running, and a panic is the one failure signal both targets have. The error
/// is logged first because a panic payload is a `&str` — the `Debug` rendering
/// would otherwise be the only copy, and on wasm32 a panic under `panic_abort`
/// does not unwind to anywhere that could print it.
impl<T, E: Debug> MainResult for Result<T, E> {
    fn report(self) {
        if let Err(error) = self {
            let rendered = format!("{error:?}");
            logwise::event!(
                class: operational,
                severity: error,
                name: "some_executor.main.failed",
                detail error = local(rendered.as_str())
            );
            panic!("main returned an error: {rendered}");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::ExecutorMain;
    use std::future::Future;
    use std::sync::Arc;
    use std::sync::atomic::{AtomicUsize, Ordering};

    /// A backend that can block and has nothing else to install.
    ///
    /// `run_main` is deliberately not used here: it sets the process-wide global
    /// executor, which only one test per process could ever do.
    struct DirectMain;

    impl ExecutorMain for DirectMain {
        fn main<F: Future<Output = ()> + 'static>(future: F) {
            crate::block_on(future);
        }
    }

    /// Stands in for the attribute macro: names a backend it knows nothing about
    /// beyond the trait.
    fn expand_main<E: ExecutorMain>(counter: Arc<AtomicUsize>) {
        E::main(async move {
            counter.fetch_add(1, Ordering::Relaxed);
        });
    }

    #[cfg_attr(not(target_arch = "wasm32"), test)]
    #[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test(worker))]
    fn generic_over_the_backend() {
        let counter = Arc::new(AtomicUsize::new(0));
        expand_main::<DirectMain>(counter.clone());
        assert_eq!(counter.load(Ordering::Relaxed), 1);
    }

    /// An infallible `main` reports nothing, which is the case every ordinary
    /// program takes.
    #[cfg_attr(not(target_arch = "wasm32"), test)]
    #[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test)]
    fn a_unit_result_is_silent() {
        super::MainResult::report(());
    }

    /// An `Ok` is as silent as a unit, so a fallible `main` that succeeds is
    /// indistinguishable from one that could not fail.
    #[cfg_attr(not(target_arch = "wasm32"), test)]
    #[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test)]
    fn an_ok_result_is_silent() {
        super::MainResult::report(Ok::<_, String>(7));
    }

    /// An `Err` brings the process down, and says what the error was.
    ///
    /// Native-only: the wasm32 profile builds std with `panic_abort`, so there
    /// is nothing for `catch_unwind` to catch and the case cannot be observed
    /// from inside the program. The behaviour is the same there -- it is the
    /// observation that is not available.
    #[cfg(not(target_arch = "wasm32"))]
    #[test]
    fn an_err_panics_with_the_error_in_the_message() {
        let panicked = std::panic::catch_unwind(|| {
            super::MainResult::report(Err::<(), _>("disk on fire"));
        });
        let payload = panicked.expect_err("a failing main must not return quietly");
        let message = payload
            .downcast_ref::<String>()
            .expect("the panic payload is the formatted message");
        assert!(
            message.contains("disk on fire"),
            "the error has to survive into the panic message, or the only copy \
             is a log line that may not have gone anywhere: {message}"
        );
    }
}