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}