1use std::future::Future;
16use std::sync::Arc;
17
18use crate::blocking::BlockingExecutor;
19use crate::blocking::BlockingLane;
20use crate::blocking::BlockingLanePolicies;
21use crate::blocking::GlobalBlockingBudget;
22use crate::critical::CriticalFailureState;
23use crate::diagnostics::RuntimeDiagnostics;
24use crate::diagnostics::RuntimeDiagnosticsSnapshot;
25use crate::error::RuntimeContractViolation;
26use crate::error::RuntimeResult;
27use crate::handle::RuntimeHandle;
28use crate::resource_budget::ResourceBudget;
29use crate::resources::RuntimeResources;
30use crate::scheduled::ScheduledTaskGroup;
31use crate::shutdown_report::ShutdownReport;
32use crate::task_group::TaskGroup;
33use crate::task_group::TaskId;
34use crate::task_group::TaskKind;
35use crate::task_spawner::TaskSpawner;
36use crate::RuntimeContractPolicy;
37
38#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40pub struct ScopeId(Arc<str>);
41
42impl ScopeId {
43 pub fn try_new(name: impl Into<Arc<str>>) -> Result<Self, RuntimeContractViolation> {
50 let name = name.into();
51 if name.trim().is_empty() {
52 return Err(RuntimeContractViolation::InvalidConfiguration {
53 policy: RuntimeContractPolicy::ServiceContextScopeNotBlank,
54 });
55 }
56 Ok(Self(name))
57 }
58
59 pub fn from_static(name: &'static str) -> Self {
66 Self::try_new(Arc::<str>::from(name)).expect("static service context scope name must be valid")
67 }
68
69 pub fn as_str(&self) -> &str {
71 &self.0
72 }
73
74 fn into_inner(self) -> Arc<str> {
75 self.0
76 }
77}
78
79impl From<&'static str> for ScopeId {
80 fn from(value: &'static str) -> Self {
81 Self::from_static(value)
82 }
83}
84
85impl TryFrom<String> for ScopeId {
86 type Error = RuntimeContractViolation;
87
88 fn try_from(value: String) -> Result<Self, Self::Error> {
89 Self::try_new(Arc::<str>::from(value))
90 }
91}
92
93impl TryFrom<Arc<str>> for ScopeId {
94 type Error = RuntimeContractViolation;
95
96 fn try_from(value: Arc<str>) -> Result<Self, Self::Error> {
97 Self::try_new(value)
98 }
99}
100
101#[cfg(test)]
102mod scope_id_tests {
103 use super::*;
104
105 #[test]
106 fn dynamic_scope_ids_return_contract_violations() {
107 assert_eq!(
108 ScopeId::try_new(Arc::<str>::from(" ")),
109 Err(RuntimeContractViolation::InvalidConfiguration {
110 policy: RuntimeContractPolicy::ServiceContextScopeNotBlank,
111 })
112 );
113 assert!(ScopeId::try_from(String::new()).is_err());
114 }
115}
116
117#[derive(Debug, Clone)]
118struct BlockingLanes {
119 storage_io: BlockingExecutor,
120 metadata_io: BlockingExecutor,
121 cpu_crypto: BlockingExecutor,
122}
123
124impl BlockingLanes {
125 fn new(policies: BlockingLanePolicies, global_capacity: usize, runtime: RuntimeHandle, scope: &TaskGroup) -> Self {
126 let budget = GlobalBlockingBudget::managed(global_capacity, &policies);
127 Self {
128 storage_io: BlockingExecutor::new_managed(
129 policies.storage_io,
130 BlockingLane::StorageIo,
131 budget.clone(),
132 runtime.clone(),
133 )
134 .expect("RuntimeConfig validates storage blocking policy before root context construction")
135 .scoped_to(scope.clone()),
136 metadata_io: BlockingExecutor::new_managed(
137 policies.metadata_io,
138 BlockingLane::MetadataIo,
139 budget.clone(),
140 runtime.clone(),
141 )
142 .expect("RuntimeConfig validates metadata blocking policy before root context construction")
143 .scoped_to(scope.clone()),
144 cpu_crypto: BlockingExecutor::new_managed(policies.cpu_crypto, BlockingLane::CpuCrypto, budget, runtime)
145 .expect("RuntimeConfig validates CPU blocking policy before root context construction")
146 .scoped_to(scope.clone()),
147 }
148 }
149
150 fn scoped_to(&self, scope: &TaskGroup) -> Self {
151 Self {
152 storage_io: self.storage_io.scoped_to(scope.clone()),
153 metadata_io: self.metadata_io.scoped_to(scope.clone()),
154 cpu_crypto: self.cpu_crypto.scoped_to(scope.clone()),
155 }
156 }
157
158 fn get(&self, lane: BlockingLane) -> &BlockingExecutor {
159 match lane {
160 BlockingLane::StorageIo => &self.storage_io,
161 BlockingLane::MetadataIo => &self.metadata_io,
162 BlockingLane::CpuCrypto => &self.cpu_crypto,
163 }
164 }
165
166 fn snapshots(&self) -> Vec<crate::blocking::BlockingExecutorSnapshot> {
167 vec![
168 self.storage_io.snapshot(),
169 self.metadata_io.snapshot(),
170 self.cpu_crypto.snapshot(),
171 ]
172 }
173
174 fn aggregates(&self) -> Vec<crate::blocking::BlockingExecutorAggregate> {
175 vec![
176 self.storage_io.aggregate(),
177 self.metadata_io.aggregate(),
178 self.cpu_crypto.aggregate(),
179 ]
180 }
181}
182
183#[derive(Debug)]
188pub struct RootServiceContext {
189 name: Arc<str>,
190 task_group: TaskGroup,
191 blocking_lanes: BlockingLanes,
192 diagnostics: RuntimeDiagnostics,
193 resources: RuntimeResources,
194 _sealed: RootContextSeal,
195}
196
197#[derive(Debug)]
198struct RootContextSeal;
199
200pub(crate) struct RootGroupPermit(());
205
206impl RootServiceContext {
207 pub(crate) fn new(
209 name: Arc<str>,
210 runtime: RuntimeHandle,
211 blocking_policies: BlockingLanePolicies,
212 global_blocking_capacity: usize,
213 diagnostics: RuntimeDiagnostics,
214 resources: RuntimeResources,
215 ) -> Self {
216 let task_group = TaskGroup::root(Arc::clone(&name), runtime.clone(), RootGroupPermit(()));
217 let blocking_lanes = BlockingLanes::new(blocking_policies, global_blocking_capacity, runtime, &task_group);
218 Self {
219 name,
220 task_group,
221 blocking_lanes,
222 diagnostics,
223 resources,
224 _sealed: RootContextSeal,
225 }
226 }
227
228 pub fn name(&self) -> &str {
230 &self.name
231 }
232
233 pub fn component(&self, scope: impl Into<ScopeId>) -> ChildServiceContext {
239 ChildServiceContext::new(
240 scope.into(),
241 &self.task_group,
242 self.blocking_lanes.clone(),
243 self.diagnostics.clone(),
244 self.resources.clone(),
245 )
246 }
247
248 pub fn process_budget(&self) -> ResourceBudget {
250 self.resources.process_budget()
251 }
252
253 pub fn resources(&self) -> &RuntimeResources {
255 &self.resources
256 }
257
258 pub fn diagnostics_snapshot(&self) -> RuntimeDiagnosticsSnapshot {
260 self.diagnostics
261 .snapshot(&self.task_group, self.blocking_lanes.snapshots())
262 }
263
264 pub fn diagnostics_view_v1(
266 &self,
267 component: crate::diagnostics::RuntimeComponent,
268 ) -> crate::diagnostics::RuntimeDiagnosticsViewV1 {
269 self.diagnostics.view_v1_with_aggregates(
270 component,
271 &self.task_group,
272 self.blocking_lanes.aggregates(),
273 Default::default(),
274 )
275 }
276
277 pub(crate) fn task_group(&self) -> &TaskGroup {
278 &self.task_group
279 }
280
281 pub(crate) fn blocking(&self, lane: BlockingLane) -> &BlockingExecutor {
282 self.blocking_lanes.get(lane)
283 }
284
285 pub(crate) fn diagnostics(&self) -> &RuntimeDiagnostics {
286 &self.diagnostics
287 }
288
289 pub(crate) fn merge_blocking_into(&self, report: &mut ShutdownReport) {
292 for snapshot in self.blocking_lanes.snapshots() {
293 report.merge_blocking(snapshot);
294 }
295 let (still_running, tasks) = self.task_group.isolated_blocking_report();
296 report.merge_blocking_tasks(still_running, tasks);
297 }
298}
299
300#[derive(Debug, Clone)]
306pub struct ChildServiceContext {
307 name: Arc<str>,
308 task_group: TaskGroup,
309 blocking_lanes: BlockingLanes,
310 diagnostics: RuntimeDiagnostics,
311 resources: RuntimeResources,
312 _sealed: Arc<ChildContextSeal>,
313}
314
315#[derive(Debug)]
316struct ChildContextSeal;
317
318impl ChildServiceContext {
319 fn new(
320 scope: ScopeId,
321 parent_group: &TaskGroup,
322 blocking_lanes: BlockingLanes,
323 diagnostics: RuntimeDiagnostics,
324 resources: RuntimeResources,
325 ) -> Self {
326 let name = scope.into_inner();
327 let task_group = parent_group.component(name.clone());
328 Self {
329 name: name.clone(),
330 task_group: task_group.clone(),
331 blocking_lanes: blocking_lanes.scoped_to(&task_group),
332 diagnostics,
333 resources,
334 _sealed: Arc::new(ChildContextSeal),
335 }
336 }
337
338 fn try_new(
339 scope: ScopeId,
340 parent_group: &TaskGroup,
341 blocking_lanes: BlockingLanes,
342 diagnostics: RuntimeDiagnostics,
343 resources: RuntimeResources,
344 ) -> RuntimeResult<Self> {
345 let name = scope.into_inner();
346 let task_group = parent_group.try_child(name.clone())?;
347 Ok(Self {
348 name: name.clone(),
349 task_group: task_group.clone(),
350 blocking_lanes: blocking_lanes.scoped_to(&task_group),
351 diagnostics,
352 resources,
353 _sealed: Arc::new(ChildContextSeal),
354 })
355 }
356
357 pub fn name(&self) -> &str {
359 &self.name
360 }
361
362 pub fn task_spawner(&self) -> TaskSpawner {
364 TaskSpawner::new(self.task_group.clone())
365 }
366
367 pub fn task_group(&self) -> &TaskGroup {
369 &self.task_group
370 }
371
372 pub fn blocking(&self, lane: BlockingLane) -> &BlockingExecutor {
374 self.blocking_lanes.get(lane)
375 }
376
377 pub fn storage_io(&self) -> &BlockingExecutor {
379 self.blocking(BlockingLane::StorageIo)
380 }
381
382 pub fn metadata_io(&self) -> &BlockingExecutor {
384 self.blocking(BlockingLane::MetadataIo)
385 }
386
387 pub fn cpu_crypto(&self) -> &BlockingExecutor {
389 self.blocking(BlockingLane::CpuCrypto)
390 }
391
392 pub fn diagnostics(&self) -> &RuntimeDiagnostics {
394 &self.diagnostics
395 }
396
397 pub fn process_budget(&self) -> ResourceBudget {
399 self.resources.process_budget()
400 }
401
402 pub fn resources(&self) -> &RuntimeResources {
404 &self.resources
405 }
406
407 pub fn diagnostics_snapshot(&self) -> RuntimeDiagnosticsSnapshot {
409 self.diagnostics
410 .snapshot(&self.task_group, self.blocking_lanes.snapshots())
411 }
412
413 pub fn diagnostics_view_v1(
415 &self,
416 component: crate::diagnostics::RuntimeComponent,
417 ) -> crate::diagnostics::RuntimeDiagnosticsViewV1 {
418 self.diagnostics.view_v1_with_aggregates(
419 component,
420 &self.task_group,
421 self.blocking_lanes.aggregates(),
422 Default::default(),
423 )
424 }
425
426 pub fn diagnostics_view_v2(
432 &self,
433 component: crate::diagnostics::RuntimeComponent,
434 inputs: crate::diagnostics::RuntimeDiagnosticsInputs,
435 ) -> crate::diagnostics::RuntimeDiagnosticsViewV2 {
436 self.diagnostics_view_v2_with_options(
437 component,
438 inputs,
439 crate::diagnostics::RuntimeDiagnosticsViewOptionsV2::default(),
440 )
441 }
442
443 pub fn diagnostics_view_v2_with_options(
447 &self,
448 component: crate::diagnostics::RuntimeComponent,
449 inputs: crate::diagnostics::RuntimeDiagnosticsInputs,
450 options: crate::diagnostics::RuntimeDiagnosticsViewOptionsV2,
451 ) -> crate::diagnostics::RuntimeDiagnosticsViewV2 {
452 self.diagnostics.view_v2_with_aggregates(
453 component,
454 &self.task_group,
455 self.blocking_lanes.aggregates(),
456 inputs,
457 options,
458 )
459 }
460
461 pub fn component(&self, scope: impl Into<ScopeId>) -> Self {
467 Self::new(
468 scope.into(),
469 &self.task_group,
470 self.blocking_lanes.clone(),
471 self.diagnostics.clone(),
472 self.resources.clone(),
473 )
474 }
475
476 pub fn try_component(&self, scope: impl Into<ScopeId>) -> RuntimeResult<Self> {
487 Self::try_new(
488 scope.into(),
489 &self.task_group,
490 self.blocking_lanes.clone(),
491 self.diagnostics.clone(),
492 self.resources.clone(),
493 )
494 }
495
496 pub fn scheduled_tasks(&self, scope: impl Into<ScopeId>) -> ScheduledTaskGroup {
498 let component = self.component(scope);
499 ScheduledTaskGroup::new(component.task_group)
500 }
501
502 pub fn spawn<F>(&self, name: impl Into<crate::TaskName>, kind: TaskKind, future: F) -> RuntimeResult<TaskId>
504 where
505 F: Future<Output = ()> + Send + 'static,
506 {
507 self.task_group.spawn(name, kind, future)
508 }
509
510 pub fn spawn_service<F>(&self, name: impl Into<crate::TaskName>, future: F) -> RuntimeResult<TaskId>
515 where
516 F: Future<Output = ()> + Send + 'static,
517 {
518 self.task_group.spawn_service(name, future)
519 }
520
521 pub fn spawn_cancellable_service<F>(&self, name: impl Into<crate::TaskName>, future: F) -> RuntimeResult<TaskId>
527 where
528 F: Future<Output = ()> + Send + 'static,
529 {
530 self.task_group.spawn_cancellable_service(name, future)
531 }
532
533 pub fn spawn_critical<F>(
539 &self,
540 name: impl Into<crate::TaskName>,
541 kind: TaskKind,
542 failures: CriticalFailureState,
543 future: F,
544 ) -> RuntimeResult<TaskId>
545 where
546 F: Future<Output = ()> + Send + 'static,
547 {
548 self.task_group.spawn_critical(name, kind, failures, future)
549 }
550
551 pub fn spawn_critical_service<F>(
557 &self,
558 name: impl Into<crate::TaskName>,
559 failures: CriticalFailureState,
560 future: F,
561 ) -> RuntimeResult<TaskId>
562 where
563 F: Future<Output = ()> + Send + 'static,
564 {
565 self.task_group.spawn_critical_service(name, failures, future)
566 }
567}