Skip to main content

webx_core/
dispatch_runtime.rs

1//! Per-request dispatch runtime — instance-scoped provider and handler cache.
2//!
3//! `Host` stores a [`DispatchRuntime`] and scopes it to each HTTP request via
4//! a task-local. Macro-generated dispatch and `Mediator` read from the active
5//! runtime when present, falling back to process-wide globals for backward
6//! compatibility (in-process `Mediator::send` without HTTP context).
7
8use std::future::Future;
9use std::sync::Arc;
10
11use crate::route::scan::{try_global_provider, HandlerCache};
12
13tokio::task_local! {
14    static ACTIVE_RUNTIME: Option<Arc<DispatchRuntime>>;
15}
16
17/// Provider + handler registry for one host instance.
18#[derive(Clone)]
19pub struct DispatchRuntime {
20    provider: Arc<rust_dix::ServiceProvider>,
21    handler_cache: Arc<HandlerCache>,
22}
23
24impl DispatchRuntime {
25    pub fn new(provider: Arc<rust_dix::ServiceProvider>, handler_cache: Arc<HandlerCache>) -> Self {
26        Self {
27            provider,
28            handler_cache,
29        }
30    }
31
32    pub fn provider(&self) -> &Arc<rust_dix::ServiceProvider> {
33        &self.provider
34    }
35
36    pub fn handler_cache(&self) -> &Arc<HandlerCache> {
37        &self.handler_cache
38    }
39
40    /// Run an async block with this runtime active for the current task.
41    pub async fn run<F, R>(&self, f: F) -> R
42    where
43        F: Future<Output = R>,
44    {
45        ACTIVE_RUNTIME.scope(Some(Arc::new(self.clone())), f).await
46    }
47}
48
49/// Provider for HTTP dispatch and `#[handler(inject)]` factories.
50///
51/// Prefers the active [`DispatchRuntime`] (HTTP requests and hosted services started
52/// by `Host`). Falls back to the deprecated process-wide shim when
53/// [`set_global_provider`](crate::route::scan::set_global_provider) was called manually.
54pub fn dispatch_provider() -> Arc<rust_dix::ServiceProvider> {
55    if let Ok(Some(provider)) =
56        ACTIVE_RUNTIME.try_with(|rt| rt.as_ref().map(|r| Arc::clone(&r.provider)))
57    {
58        return provider;
59    }
60
61    if let Some(provider) = try_global_provider() {
62        tracing::warn!(
63            "dispatch_provider() using deprecated global ServiceProvider shim; \
64             prefer Host::dispatch_runtime().run() or host.provider()"
65        );
66        return provider;
67    }
68
69    panic!(
70        "No active DispatchRuntime and no deprecated global ServiceProvider. \
71         HTTP and hosted services run inside Host::dispatch_runtime().run(); \
72         for tests use host.dispatch_runtime().run with an async block, or host.provider()."
73    );
74}
75
76/// Handler registry for the active dispatch context.
77///
78/// Prefers the active [`DispatchRuntime`]; falls back to a lazy-built process-wide
79/// registry (inventory is process-wide; contents are identical per host).
80pub fn dispatch_handler_cache() -> Arc<HandlerCache> {
81    if let Ok(Some(cache)) =
82        ACTIVE_RUNTIME.try_with(|rt| rt.as_ref().map(|r| Arc::clone(&r.handler_cache)))
83    {
84        return cache;
85    }
86
87    #[allow(deprecated)]
88    {
89        HandlerCache::get_or_init()
90    }
91}