Skip to main content

kerniq_runtime/
runtime.rs

1//! Tokio runtime ownership.
2//!
3//! Kerniq never creates a runtime behind the user's back. There are exactly
4//! two modes:
5//!
6//! * **User-managed** (`app.run(addr).await`): the application owns the
7//!   runtime, typically through `#[tokio::main]`. Kerniq only spawns tasks
8//!   on it.
9//! * **Framework-managed** (`app.run_blocking(addr)`): Kerniq builds a
10//!   multi-threaded runtime, runs the server to completion and shuts the
11//!   runtime down. This is refused with [`ServerError::NestedRuntime`] when
12//!   called from inside an existing runtime, because Tokio cannot nest
13//!   runtimes (it would panic).
14
15use std::future::Future;
16use std::time::Duration;
17
18use kerniq_core::ServerError;
19
20/// Whether the current thread is inside a Tokio runtime context.
21pub fn in_runtime() -> bool {
22    tokio::runtime::Handle::try_current().is_ok()
23}
24
25/// Builds a multi-threaded runtime, drives `future` to completion and shuts
26/// the runtime down, waiting at most `shutdown_timeout` for stray tasks.
27///
28/// `workers` sets the number of worker threads (default: one per core).
29///
30/// # Errors
31///
32/// [`ServerError::NestedRuntime`] if called from inside a runtime, or
33/// [`ServerError::Runtime`] if the runtime cannot be built.
34pub fn block_on<F: Future>(
35    workers: Option<usize>,
36    shutdown_timeout: Duration,
37    future: F,
38) -> Result<F::Output, ServerError> {
39    if in_runtime() {
40        return Err(ServerError::NestedRuntime);
41    }
42    let mut builder = tokio::runtime::Builder::new_multi_thread();
43    builder.enable_all().thread_name("kerniq-worker");
44    if let Some(workers) = workers {
45        builder.worker_threads(workers);
46    }
47    let runtime = builder.build().map_err(ServerError::Runtime)?;
48    let output = runtime.block_on(future);
49    runtime.shutdown_timeout(shutdown_timeout);
50    Ok(output)
51}