webx_core/
dispatch_runtime.rs1use 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 static ACTIVE_SCOPE_PROVIDER: Option<Arc<rust_dix::ServiceProvider>>;
21}
22
23#[derive(Clone)]
25pub struct DispatchRuntime {
26 provider: Arc<rust_dix::ServiceProvider>,
27 handler_cache: Arc<HandlerCache>,
28}
29
30impl DispatchRuntime {
31 pub fn new(provider: Arc<rust_dix::ServiceProvider>, handler_cache: Arc<HandlerCache>) -> Self {
32 Self {
33 provider,
34 handler_cache,
35 }
36 }
37
38 pub fn provider(&self) -> &Arc<rust_dix::ServiceProvider> {
39 &self.provider
40 }
41
42 pub fn handler_cache(&self) -> &Arc<HandlerCache> {
43 &self.handler_cache
44 }
45
46 pub async fn run<F, R>(&self, f: F) -> R
48 where
49 F: Future<Output = R>,
50 {
51 ACTIVE_RUNTIME.scope(Some(Arc::new(self.clone())), f).await
52 }
53
54 pub async fn run_in_scope<F, R>(self: &Arc<Self>, f: F) -> R
65 where
66 F: Future<Output = R>,
67 {
68 let scope = self.provider.scope();
69 let scoped_provider = Arc::clone(scope.scope_provider());
70 let result = ACTIVE_RUNTIME
71 .scope(
72 Some(Arc::clone(self)),
73 ACTIVE_SCOPE_PROVIDER.scope(Some(scoped_provider), f),
74 )
75 .await;
76 scope.dispose();
77 result
78 }
79}
80
81pub fn dispatch_provider() -> Arc<rust_dix::ServiceProvider> {
87 if let Ok(Some(provider)) = ACTIVE_SCOPE_PROVIDER.try_with(|p| p.clone()) {
90 return provider;
91 }
92
93 if let Ok(Some(provider)) =
94 ACTIVE_RUNTIME.try_with(|rt| rt.as_ref().map(|r| Arc::clone(&r.provider)))
95 {
96 return provider;
97 }
98
99 if let Some(provider) = try_global_provider() {
100 tracing::warn!(
101 "dispatch_provider() using deprecated global ServiceProvider shim; \
102 prefer Host::dispatch_runtime().run() or host.provider()"
103 );
104 return provider;
105 }
106
107 panic!(
108 "No active DispatchRuntime and no deprecated global ServiceProvider. \
109 HTTP and hosted services run inside Host::dispatch_runtime().run(); \
110 for tests use host.dispatch_runtime().run with an async block, or host.provider()."
111 );
112}
113
114pub fn dispatch_handler_cache() -> Arc<HandlerCache> {
119 if let Ok(Some(cache)) =
120 ACTIVE_RUNTIME.try_with(|rt| rt.as_ref().map(|r| Arc::clone(&r.handler_cache)))
121 {
122 return cache;
123 }
124
125 #[allow(deprecated)]
126 {
127 HandlerCache::get_or_init()
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use rust_dix::ServiceCollection;
135 use std::sync::atomic::{AtomicUsize, Ordering};
136
137 #[derive(Debug)]
138 struct ScopedThing(usize);
139
140 fn runtime_with_scoped_counter() -> (Arc<DispatchRuntime>, Arc<AtomicUsize>) {
141 let created = Arc::new(AtomicUsize::new(0));
142 let counter = Arc::clone(&created);
143 let svc = ServiceCollection::new().scoped::<ScopedThing>(move |_| {
144 let n = counter.fetch_add(1, Ordering::SeqCst);
145 Arc::new(ScopedThing(n))
146 });
147 let provider = svc.build().expect("provider");
148 let runtime = Arc::new(DispatchRuntime::new(
149 provider,
150 Arc::new(HandlerCache::build()),
151 ));
152 (runtime, created)
153 }
154
155 #[tokio::test]
158 async fn run_in_scope_gives_each_run_its_own_scoped_cache() {
159 let (runtime, created) = runtime_with_scoped_counter();
160
161 let same_within_one_scope = runtime
162 .run_in_scope(async {
163 let first = dispatch_provider().get::<ScopedThing>().expect("scoped");
164 let second = dispatch_provider().get::<ScopedThing>().expect("scoped");
165 Arc::ptr_eq(&first, &second)
166 })
167 .await;
168 assert!(
169 same_within_one_scope,
170 "scoped service must be cached per scope"
171 );
172 assert_eq!(created.load(Ordering::SeqCst), 1);
173
174 runtime
175 .run_in_scope(async {
176 let again = dispatch_provider().get::<ScopedThing>().expect("scoped");
177 assert_eq!(again.0, 1, "a new scope must not reuse the root cache");
178 })
179 .await;
180 assert_eq!(created.load(Ordering::SeqCst), 2);
181 }
182
183 #[tokio::test]
186 async fn scope_provider_does_not_leak_outside_run_in_scope() {
187 let (runtime, _created) = runtime_with_scoped_counter();
188 runtime
189 .run(async {
190 assert!(
191 dispatch_provider().get::<ScopedThing>().is_ok(),
192 "runtime provider still resolves the service"
193 );
194 })
195 .await;
196 }
197}