1use std::future::Future;
16use std::sync::atomic::AtomicU64;
17use std::sync::atomic::AtomicU8;
18use std::sync::atomic::AtomicUsize;
19use std::sync::atomic::Ordering;
20use std::sync::Arc;
21use std::time::Duration;
22use std::time::Instant;
23
24use dashmap::mapref::entry::Entry;
25use futures::future::join_all;
26use futures::future::BoxFuture;
27use futures::future::FutureExt;
28use parking_lot::Mutex;
29use serde::Serialize;
30use tokio::sync::Notify;
31use tokio::task::AbortHandle;
32use tokio_util::sync::CancellationToken;
33use tokio_util::task::TaskTracker;
34
35use crate::blocking::BlockingTaskSnapshot;
36use crate::blocking::BlockingTaskTable;
37use crate::blocking::IsolatedBlockingTables;
38use crate::critical::CriticalFailureState;
39use crate::critical::CriticalRegistration;
40use crate::error::RuntimeError;
41use crate::error::RuntimeResult;
42use crate::handle::RuntimeHandle;
43use crate::operation::OperationContext;
44use crate::service_context::RootGroupPermit;
45use crate::shutdown_deadline::ShutdownDeadline;
46use crate::shutdown_report::ShutdownAnnotation;
47use crate::shutdown_report::ShutdownReport;
48use crate::shutdown_report::TaskSnapshot;
49
50mod completion;
51mod diagnostics;
52mod registry;
53mod shutdown;
54mod submission;
55
56use registry::ActiveTaskRegistry;
57use shutdown::ShutdownCoordinator;
58
59const STATE_OPEN: u8 = 0;
60const STATE_CLOSING: u8 = 1;
61const STATE_CLOSED: u8 = 2;
62const STATE_SHUTDOWN_COMPLETED: u8 = 3;
63const STATE_POISONED: u8 = 4;
64
65const MAX_INLINE_TASK_FUTURE_SIZE: usize = crate::stack::MAX_INLINE_SIZE;
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
75#[serde(transparent)]
76pub struct TaskId {
77 sequence: u64,
78 #[serde(skip)]
79 group: TaskGroupId,
80}
81
82impl TaskId {
83 pub(crate) fn new(group: TaskGroupId, sequence: u64) -> Self {
84 Self { sequence, group }
85 }
86
87 pub fn as_u64(self) -> u64 {
89 self.sequence
90 }
91
92 pub fn group_id(self) -> TaskGroupId {
94 self.group
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
103pub struct TaskGroupId(u64);
104
105impl TaskGroupId {
106 pub fn as_u64(self) -> u64 {
108 self.0
109 }
110
111 fn next() -> Self {
112 static NEXT_TASK_GROUP_ID: AtomicU64 = AtomicU64::new(1);
113 Self(NEXT_TASK_GROUP_ID.fetch_add(1, Ordering::Relaxed))
114 }
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Hash)]
122pub struct TaskName(TaskNameRepr);
123
124#[derive(Debug, Clone, PartialEq, Eq, Hash)]
125enum TaskNameRepr {
126 Static(&'static str),
127 Shared(Arc<str>),
128}
129
130impl TaskName {
131 pub fn as_str(&self) -> &str {
133 match &self.0 {
134 TaskNameRepr::Static(name) => name,
135 TaskNameRepr::Shared(name) => name,
136 }
137 }
138}
139
140impl std::fmt::Display for TaskName {
141 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 formatter.write_str(self.as_str())
143 }
144}
145
146impl From<&'static str> for TaskName {
147 fn from(name: &'static str) -> Self {
148 Self(TaskNameRepr::Static(name))
149 }
150}
151
152impl From<String> for TaskName {
153 fn from(name: String) -> Self {
154 Self(TaskNameRepr::Shared(Arc::from(name)))
155 }
156}
157
158impl From<&String> for TaskName {
159 fn from(name: &String) -> Self {
160 Self(TaskNameRepr::Shared(Arc::from(name.as_str())))
161 }
162}
163
164impl From<Arc<str>> for TaskName {
165 fn from(name: Arc<str>) -> Self {
166 Self(TaskNameRepr::Shared(name))
167 }
168}
169
170impl From<&Arc<str>> for TaskName {
171 fn from(name: &Arc<str>) -> Self {
172 Self(TaskNameRepr::Shared(Arc::clone(name)))
173 }
174}
175
176impl From<Box<str>> for TaskName {
177 fn from(name: Box<str>) -> Self {
178 Self(TaskNameRepr::Shared(Arc::from(name)))
179 }
180}
181
182impl From<std::borrow::Cow<'static, str>> for TaskName {
183 fn from(name: std::borrow::Cow<'static, str>) -> Self {
184 match name {
185 std::borrow::Cow::Borrowed(name) => Self::from(name),
186 std::borrow::Cow::Owned(name) => Self::from(name),
187 }
188 }
189}
190
191#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
193pub struct TaskGroupEventCounts {
194 pub poisoned_groups: u64,
196 pub closed_component_requests: u64,
199}
200
201#[derive(Debug, Default)]
204struct TaskGroupTreeState {
205 poisoned_groups: AtomicU64,
206 closed_component_requests: AtomicU64,
207 isolated_blocking: IsolatedBlockingTables,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
212pub enum TaskKind {
213 Service,
215 Worker,
217 ScheduledDriver,
219 ScheduledRun,
221 BlockingReaper,
223 Shutdown,
225 Other,
227}
228
229impl TaskKind {
230 pub(crate) const ALL: [Self; 7] = [
231 Self::Service,
232 Self::Worker,
233 Self::ScheduledDriver,
234 Self::ScheduledRun,
235 Self::BlockingReaper,
236 Self::Shutdown,
237 Self::Other,
238 ];
239 pub(crate) const COUNT: usize = Self::ALL.len();
240
241 pub(crate) const fn index(self) -> usize {
242 match self {
243 Self::Service => 0,
244 Self::Worker => 1,
245 Self::ScheduledDriver => 2,
246 Self::ScheduledRun => 3,
247 Self::BlockingReaper => 4,
248 Self::Shutdown => 5,
249 Self::Other => 6,
250 }
251 }
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
256pub enum TaskState {
257 Queued,
259 Running,
261 Completed,
263 Cancelled,
265 Aborted,
267 Panicked,
269 Leaked,
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
275pub enum TaskResult {
276 Completed,
278 Cancelled,
280 Aborted,
282 Panicked,
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
291pub enum TaskGroupLifecycleState {
292 Open,
294 Closing,
296 Closed,
298 ShutdownCompleted,
300 Poisoned,
302}
303
304impl TaskGroupLifecycleState {
305 pub(crate) fn admission_error(self, operation: crate::RuntimeOperation) -> RuntimeError {
309 match self {
310 Self::Poisoned => RuntimeError::poisoned(operation),
311 Self::Open | Self::Closing | Self::Closed | Self::ShutdownCompleted => RuntimeError::closed(operation),
312 }
313 }
314}
315
316#[derive(Debug, Clone)]
328pub struct TaskGroup {
329 inner: Arc<TaskGroupInner>,
330}
331
332#[derive(Debug, Clone, Copy)]
333pub(crate) struct TaskKindDiagnostics {
334 pub(crate) kind: TaskKind,
335 pub(crate) active: usize,
336 pub(crate) long_running: usize,
337 pub(crate) max_elapsed: Duration,
338}
339
340#[derive(Debug, Clone)]
341pub(crate) struct TaskGroupDiagnostics {
342 pub(crate) local_task_count: usize,
343 pub(crate) group_count: usize,
344 pub(crate) task_count: usize,
345 pub(crate) task_kinds: Vec<TaskKindDiagnostics>,
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq)]
350pub(crate) enum TaskDetailScope {
351 Local,
353 Subtree,
355}
356
357#[derive(Debug, Clone)]
363pub(crate) struct TaskDetail {
364 pub(crate) kind: TaskKind,
365 pub(crate) scope: TaskDetailScope,
366 pub(crate) elapsed: Duration,
367}
368
369#[derive(Debug, Clone, Default)]
371pub(crate) struct TaskDetailScan {
372 pub(crate) scanned: usize,
374 pub(crate) group_entries_scanned: usize,
376 pub(crate) details: Vec<TaskDetail>,
378 pub(crate) truncated: bool,
380}
381
382#[derive(Debug)]
383struct TaskGroupInner {
384 id: TaskGroupId,
385 parent_id: Option<TaskGroupId>,
386 name: Arc<str>,
387 runtime: RuntimeHandle,
388 cancellation_token: CancellationToken,
389 tracker: TaskTracker,
390 registry: Arc<ActiveTaskRegistry>,
391 parent: Option<Arc<TaskGroupInner>>,
394 tree: Arc<TaskGroupTreeState>,
395 closed_component_requests: AtomicUsize,
397 next_task_id: AtomicU64,
398 completed: AtomicUsize,
399 cancelled: AtomicUsize,
400 aborted: AtomicUsize,
401 panicked: AtomicUsize,
402 lifecycle: AtomicU8,
403 spawn_gate: Mutex<()>,
404 settlement_gate: Mutex<()>,
405 shutdown: ShutdownCoordinator,
406}
407
408#[derive(Debug, Clone)]
409struct TaskMeta {
410 id: TaskId,
411 name: TaskName,
412 group_id: TaskGroupId,
413 group_name: Arc<str>,
414 kind: TaskKind,
415 state: TaskState,
416 started_at: Instant,
417 abort_handle: Option<AbortHandle>,
418 abort_requested: bool,
419 completion: Arc<TaskCompletion>,
420 operation: Option<u64>,
422}
423
424#[derive(Debug)]
425struct TaskCompletion {
426 done: AtomicU8,
427 notify: Notify,
428}
429
430impl TaskCompletion {
431 fn new() -> Self {
432 Self {
433 done: AtomicU8::new(0),
434 notify: Notify::new(),
435 }
436 }
437
438 fn mark_done(&self) {
439 if self.done.swap(1, Ordering::AcqRel) == 0 {
440 self.notify.notify_waiters();
441 }
442 }
443
444 fn is_done(&self) -> bool {
445 self.done.load(Ordering::Acquire) != 0
446 }
447
448 async fn wait(&self) {
449 loop {
450 let notified = self.notify.notified();
451 if self.is_done() {
452 return;
453 }
454 notified.await;
455 }
456 }
457}
458
459impl TaskGroup {
460 pub(crate) fn root(name: Arc<str>, runtime: RuntimeHandle, _permit: RootGroupPermit) -> Self {
465 Self {
466 inner: Arc::new(TaskGroupInner::new(
467 TaskGroupId::next(),
468 None,
469 name,
470 runtime,
471 CancellationToken::new(),
472 None,
473 Arc::default(),
474 )),
475 }
476 }
477
478 pub fn id(&self) -> TaskGroupId {
480 self.inner.id
481 }
482
483 pub(crate) fn runtime(&self) -> &RuntimeHandle {
484 &self.inner.runtime
485 }
486
487 pub fn parent_id(&self) -> Option<TaskGroupId> {
489 self.inner.parent_id
490 }
491
492 pub fn name(&self) -> &str {
494 &self.inner.name
495 }
496
497 pub fn cancellation_token(&self) -> CancellationToken {
499 self.inner.cancellation_token.clone()
500 }
501
502 pub fn shutdown_deadline(&self) -> Option<ShutdownDeadline> {
504 self.inner.shutdown.deadline()
505 }
506
507 pub fn lifecycle_state(&self) -> TaskGroupLifecycleState {
509 self.inner.lifecycle_state()
510 }
511
512 pub fn task_count(&self) -> usize {
516 self.inner.registry.tasks.len()
517 }
518
519 pub fn component_count(&self) -> usize {
521 self.inner.registry.component_count()
522 }
523
524 pub fn owns_task(&self, task_id: TaskId) -> bool {
527 task_id.group_id() == self.inner.id
528 }
529
530 pub fn contains_task(&self, task_id: TaskId) -> bool {
534 self.inner.registry.tasks.contains_key(&task_id)
535 }
536
537 #[cfg(test)]
540 pub(crate) fn lock_settlement_for_test(&self) -> parking_lot::MutexGuard<'_, ()> {
541 self.inner.settlement_gate.lock()
542 }
543
544 pub(crate) fn operation_task_ids(&self, operation: u64) -> Vec<TaskId> {
546 self.inner
547 .registry
548 .tasks
549 .iter()
550 .filter(|entry| entry.operation == Some(operation))
551 .map(|entry| entry.id)
552 .collect()
553 }
554
555 pub fn event_counts(&self) -> TaskGroupEventCounts {
557 let tree = &self.inner.tree;
558 TaskGroupEventCounts {
559 poisoned_groups: tree.poisoned_groups.load(Ordering::Relaxed),
560 closed_component_requests: tree.closed_component_requests.load(Ordering::Relaxed),
561 }
562 }
563
564 pub(crate) fn register_isolated_blocking(&self, table: &Arc<BlockingTaskTable>) {
567 self.inner.tree.isolated_blocking.register(table);
568 }
569
570 pub(crate) fn isolated_blocking_report(&self) -> (usize, Vec<BlockingTaskSnapshot>) {
573 self.inner.tree.isolated_blocking.report()
574 }
575
576 pub(crate) fn component(&self, name: impl Into<Arc<str>>) -> Self {
581 let name = name.into();
582 self.try_child(name.clone()).unwrap_or_else(|_error| {
583 self.inner
584 .tree
585 .closed_component_requests
586 .fetch_add(1, Ordering::Relaxed);
587 if self.inner.closed_component_requests.fetch_add(1, Ordering::Relaxed) == 0 {
588 tracing::warn!(
589 group = %self.inner.path(),
590 component = %name,
591 state = ?self.lifecycle_state(),
592 "component requested after its owner stopped admitting children; returning a closed group"
593 );
594 }
595 self.closed_component(name)
596 })
597 }
598
599 pub fn try_child(&self, name: impl Into<Arc<str>>) -> RuntimeResult<Self> {
611 let name = name.into();
612 let _spawn_guard = self.inner.spawn_gate.lock();
613 let state = self.inner.lifecycle_state();
614 if state != TaskGroupLifecycleState::Open {
615 return Err(state.admission_error(crate::RuntimeOperation::CreateTaskGroupChild));
616 }
617
618 let child = self.open_component(name);
619 self.inner
620 .registry
621 .register_component(child.id(), Arc::downgrade(&child.inner));
622 Ok(child)
623 }
624
625 fn open_component(&self, name: Arc<str>) -> Self {
626 Self {
627 inner: Arc::new(TaskGroupInner::new(
628 TaskGroupId::next(),
629 Some(self.inner.id),
630 name,
631 self.inner.runtime.clone(),
632 self.inner.cancellation_token.child_token(),
633 Some(self.inner.clone()),
634 Arc::clone(&self.inner.tree),
635 )),
636 }
637 }
638
639 fn closed_component(&self, name: Arc<str>) -> Self {
640 let child = self.open_component(name);
641 child.inner.tracker.close();
642 child.inner.cancellation_token.cancel();
643 child.inner.lifecycle.store(STATE_SHUTDOWN_COMPLETED, Ordering::Release);
644 child
645 }
646
647 pub fn spawn<F>(&self, name: impl Into<TaskName>, kind: TaskKind, future: F) -> RuntimeResult<TaskId>
649 where
650 F: Future<Output = ()> + Send + 'static,
651 {
652 self.spawn_inner(name.into(), kind, future)
653 }
654
655 pub fn spawn_critical<F>(
666 &self,
667 name: impl Into<TaskName>,
668 kind: TaskKind,
669 failures: CriticalFailureState,
670 future: F,
671 ) -> RuntimeResult<TaskId>
672 where
673 F: Future<Output = ()> + Send + 'static,
674 {
675 let registration = CriticalRegistration {
676 failures,
677 task_kind: kind,
678 expects_until_cancelled: false,
679 };
680 let (task_id, join_handle) =
681 self.spawn_inner_with_handle(name.into(), kind, false, Some(registration), None, future)?;
682 drop(join_handle);
683 Ok(task_id)
684 }
685
686 pub fn spawn_critical_service<F>(
696 &self,
697 name: impl Into<TaskName>,
698 failures: CriticalFailureState,
699 future: F,
700 ) -> RuntimeResult<TaskId>
701 where
702 F: Future<Output = ()> + Send + 'static,
703 {
704 let registration = CriticalRegistration {
705 failures,
706 task_kind: TaskKind::Service,
707 expects_until_cancelled: true,
708 };
709 let (task_id, join_handle) =
710 self.spawn_inner_with_handle(name.into(), TaskKind::Service, false, Some(registration), None, future)?;
711 drop(join_handle);
712 Ok(task_id)
713 }
714
715 pub fn spawn_service<F>(&self, name: impl Into<TaskName>, future: F) -> RuntimeResult<TaskId>
722 where
723 F: Future<Output = ()> + Send + 'static,
724 {
725 self.spawn(name, TaskKind::Service, future)
726 }
727
728 pub fn spawn_cancellable_service<F>(&self, name: impl Into<TaskName>, future: F) -> RuntimeResult<TaskId>
738 where
739 F: Future<Output = ()> + Send + 'static,
740 {
741 if std::mem::size_of::<F>() > MAX_INLINE_TASK_FUTURE_SIZE {
742 self.spawn_cancellable_service_inner(name.into(), Box::pin(future))
743 } else {
744 self.spawn_cancellable_service_inner(name.into(), future)
745 }
746 }
747
748 fn spawn_cancellable_service_inner<F>(&self, name: TaskName, future: F) -> RuntimeResult<TaskId>
749 where
750 F: Future<Output = ()> + Send + 'static,
751 {
752 let owner_cancellation = self.cancellation_token();
753 self.spawn_service(name, async move {
754 tokio::select! {
755 biased;
756 _ = owner_cancellation.cancelled() => {}
757 _ = future => {}
758 }
759 })
760 }
761
762 pub fn spawn_operation<F>(
767 &self,
768 context: &OperationContext,
769 name: impl Into<TaskName>,
770 future: F,
771 ) -> RuntimeResult<TaskId>
772 where
773 F: Future<Output = ()> + Send + 'static,
774 {
775 self.spawn_operation_with_cancellation(context, name.into(), future, Some(self.cancellation_token()))
776 }
777
778 pub fn spawn_draining_operation<F>(
787 &self,
788 context: &OperationContext,
789 name: impl Into<TaskName>,
790 future: F,
791 ) -> RuntimeResult<TaskId>
792 where
793 F: Future<Output = ()> + Send + 'static,
794 {
795 self.spawn_operation_with_cancellation(context, name.into(), future, None)
796 }
797
798 fn spawn_operation_with_cancellation<F>(
799 &self,
800 context: &OperationContext,
801 name: TaskName,
802 future: F,
803 owner_cancellation: Option<CancellationToken>,
804 ) -> RuntimeResult<TaskId>
805 where
806 F: Future<Output = ()> + Send + 'static,
807 {
808 if std::mem::size_of::<F>() > MAX_INLINE_TASK_FUTURE_SIZE {
809 self.spawn_operation_inner(context, name, Box::pin(future), owner_cancellation)
810 } else {
811 self.spawn_operation_inner(context, name, future, owner_cancellation)
812 }
813 }
814
815 fn spawn_operation_inner<F>(
816 &self,
817 context: &OperationContext,
818 name: TaskName,
819 future: F,
820 owner_cancellation: Option<CancellationToken>,
821 ) -> RuntimeResult<TaskId>
822 where
823 F: Future<Output = ()> + Send + 'static,
824 {
825 let registration = {
826 let _operation_spawn_guard = context.spawn_guard();
827 context.prepare_spawn(self.id())?
828 };
829 let guard = registration.guard();
830 let execution = crate::operation::OperationExecution::new(future, guard, context.clone(), owner_cancellation);
831 let task_id = self.spawn_operation_task(name, context.task_kind(), context.id(), execution.run())?;
832 registration.finish_registration();
833 Ok(task_id)
834 }
835
836 pub fn spawn_with_handle<F>(
838 &self,
839 name: impl Into<TaskName>,
840 kind: TaskKind,
841 future: F,
842 ) -> RuntimeResult<(TaskId, tokio::task::JoinHandle<()>)>
843 where
844 F: Future<Output = ()> + Send + 'static,
845 {
846 self.spawn_inner_with_handle(name.into(), kind, true, None, None, future)
847 }
848
849 pub fn spawn_service_with_handle<F>(
851 &self,
852 name: impl Into<TaskName>,
853 future: F,
854 ) -> RuntimeResult<(TaskId, tokio::task::JoinHandle<()>)>
855 where
856 F: Future<Output = ()> + Send + 'static,
857 {
858 self.spawn_with_handle(name, TaskKind::Service, future)
859 }
860
861 pub fn cancel(&self) {
867 self.inner.cancellation_token.cancel();
868 }
869
870 pub fn abort_task(&self, task_id: TaskId) -> bool {
876 self.abort_task_inner(task_id).is_some()
877 }
878
879 pub async fn abort_task_and_wait(&self, task_id: TaskId, timeout: Duration) -> bool {
885 self.abort_task_until(task_id, ShutdownDeadline::after(timeout)).await
886 }
887
888 pub(crate) async fn abort_task_until(&self, task_id: TaskId, deadline: ShutdownDeadline) -> bool {
889 let Some(completion) = self.abort_task_inner(task_id) else {
890 return false;
891 };
892
893 if completion.is_done() {
894 return true;
895 }
896
897 if deadline.is_expired() {
898 return false;
899 }
900
901 tokio::time::timeout_at(deadline.instant().into(), completion.wait())
902 .await
903 .is_ok()
904 }
905
906 pub async fn wait_task(&self, task_id: TaskId, timeout: Duration) -> bool {
916 self.wait_task_until(task_id, ShutdownDeadline::after(timeout)).await
917 }
918
919 pub(crate) async fn wait_task_until(&self, task_id: TaskId, deadline: ShutdownDeadline) -> bool {
920 if !self.owns_task(task_id) {
921 return false;
922 }
923 let Some(completion) = self
924 .inner
925 .registry
926 .tasks
927 .get(&task_id)
928 .map(|meta| meta.completion.clone())
929 else {
930 return true;
931 };
932
933 if completion.is_done() {
934 return true;
935 }
936
937 if deadline.is_expired() {
938 return false;
939 }
940
941 tokio::time::timeout_at(deadline.instant().into(), completion.wait())
942 .await
943 .is_ok()
944 }
945
946 pub fn shutdown(&self, timeout: Duration) -> BoxFuture<'_, ShutdownReport> {
951 self.shutdown_until(ShutdownDeadline::after(timeout))
952 }
953
954 pub fn shutdown_until(&self, deadline: ShutdownDeadline) -> BoxFuture<'_, ShutdownReport> {
961 self.tighten_shutdown_deadline(deadline);
962 async move {
963 let report = self
964 .inner
965 .shutdown
966 .report
967 .get_or_init(|| async { self.shutdown_inner().await })
968 .await;
969 self.inner.shutdown.report_ready.notify_waiters();
970 report.clone()
971 }
972 .boxed()
973 }
974
975 pub fn shutdown_now(&self) -> ShutdownReport {
981 if let Some(report) = self.inner.shutdown.report.get() {
982 return report.clone();
983 }
984
985 let report = self.shutdown_now_inner();
986 let _ = self.inner.shutdown.report.set(report.clone());
987 self.inner.shutdown.report_ready.notify_waiters();
988 report
989 }
990
991 fn tighten_shutdown_deadline(&self, deadline: ShutdownDeadline) {
992 let mut pending = vec![(self.clone(), deadline)];
993 while let Some((group, deadline)) = pending.pop() {
994 let installed = group.inner.shutdown.tighten(deadline);
995 pending.extend(
996 group
997 .inner
998 .registry
999 .components_snapshot()
1000 .into_iter()
1001 .map(|child| (child, installed)),
1002 );
1003 }
1004 }
1005
1006 async fn shutdown_inner(&self) -> ShutdownReport {
1007 let started_at = Instant::now();
1008 let deadline = self
1009 .inner
1010 .shutdown
1011 .deadline()
1012 .unwrap_or_else(|| ShutdownDeadline::after(Duration::ZERO));
1013 let descendants = self.close_admission_tree();
1017 self.tighten_shutdown_deadline(deadline);
1020 self.inner.cancellation_token.cancel();
1021 self.mark_closed(&descendants);
1022 let children = join_all(descendants.iter().map(|group| async move {
1025 group.inner.shutdown.report.get_or_init(|| group.shutdown_one()).await;
1026 group.inner.shutdown.report_ready.notify_waiters();
1027 }));
1028 let (_, mut report) = tokio::join!(children, self.shutdown_one());
1029 report.elapsed = started_at.elapsed();
1030 report
1031 }
1032
1033 async fn shutdown_one(&self) -> ShutdownReport {
1034 let started_at = Instant::now();
1035 let children = self.inner.registry.components_snapshot();
1036
1037 let child_reports = async {
1038 join_all(
1039 children
1040 .into_iter()
1041 .map(|child| async move { child.inner.shutdown.wait_report().await.clone() }),
1042 )
1043 .await
1044 };
1045 let tracked_shutdown = async {
1048 if self.inner.shutdown.run_until(self.inner.tracker.wait()).await.is_err() {
1049 let aborted_before_timeout = self.inner.aborted.load(Ordering::Acquire);
1050 self.abort_tracked_tasks();
1051 let _ = self.inner.shutdown.run_until(self.inner.tracker.wait()).await;
1052 Some(aborted_before_timeout)
1053 } else {
1054 None
1055 }
1056 };
1057
1058 let (child_reports, aborted_before_timeout) = tokio::join!(child_reports, tracked_shutdown);
1059
1060 let mut report = ShutdownReport::new(self.inner.name.to_string(), started_at.elapsed());
1061 report.children = child_reports;
1062 self.record_task_outcomes(&mut report);
1063
1064 if let Some(aborted_before_timeout) = aborted_before_timeout {
1065 let aborted_by_timeout = report.aborted.saturating_sub(aborted_before_timeout);
1067 if aborted_by_timeout > 0 {
1068 report.annotations.push(ShutdownAnnotation::new(format!(
1069 "confirmed cancellation of {aborted_by_timeout} aborted tasks"
1070 )));
1071 }
1072 report.timed_out = aborted_by_timeout + report.leaked;
1073 }
1074
1075 self.inner.lifecycle.store(STATE_SHUTDOWN_COMPLETED, Ordering::Release);
1076 report
1077 }
1078
1079 fn close_admission_tree(&self) -> Vec<TaskGroup> {
1080 let mut retained = Vec::new();
1081 let mut pending = vec![self.clone()];
1082 while let Some(group) = pending.pop() {
1083 {
1084 let _spawn_guard = group.inner.spawn_gate.lock();
1085 let _ = group
1088 .inner
1089 .lifecycle
1090 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |state| {
1091 matches!(state, STATE_OPEN | STATE_POISONED).then_some(STATE_CLOSING)
1092 });
1093 group.inner.tracker.close();
1094 pending.extend(group.inner.registry.components_snapshot());
1095 }
1096 if group.id() != self.id() {
1097 retained.push(group);
1098 }
1099 }
1100 retained.reverse();
1102 retained
1103 }
1104
1105 fn mark_closed(&self, descendants: &[TaskGroup]) {
1111 for group in descendants.iter().chain(std::iter::once(self)) {
1112 let _ = group.inner.lifecycle.compare_exchange(
1113 STATE_CLOSING,
1114 STATE_CLOSED,
1115 Ordering::AcqRel,
1116 Ordering::Acquire,
1117 );
1118 }
1119 }
1120
1121 fn shutdown_now_inner(&self) -> ShutdownReport {
1122 let started_at = Instant::now();
1123 let descendants = self.close_admission_tree();
1124 self.inner.cancellation_token.cancel();
1125 self.mark_closed(&descendants);
1126 let mut reports = std::collections::HashMap::new();
1127 for group in &descendants {
1128 let children = group
1129 .inner
1130 .registry
1131 .components_snapshot()
1132 .into_iter()
1133 .filter_map(|child| reports.remove(&child.id()))
1134 .collect();
1135 let report = if let Some(report) = group.inner.shutdown.report.get() {
1136 report.clone()
1137 } else {
1138 let report = group.shutdown_now_one(children);
1139 let _ = group.inner.shutdown.report.set(report.clone());
1140 group.inner.shutdown.report_ready.notify_waiters();
1141 report
1142 };
1143 reports.insert(group.id(), report);
1144 }
1145 let children = self
1146 .inner
1147 .registry
1148 .components_snapshot()
1149 .into_iter()
1150 .filter_map(|child| reports.remove(&child.id()))
1151 .collect();
1152 let mut report = self.shutdown_now_one(children);
1153 report.elapsed = started_at.elapsed();
1154 report
1155 }
1156
1157 fn shutdown_now_one(&self, child_reports: Vec<ShutdownReport>) -> ShutdownReport {
1158 let started_at = Instant::now();
1159
1160 let aborted_before = self.inner.aborted.load(Ordering::Acquire);
1161 self.abort_tracked_tasks();
1162
1163 let mut report = ShutdownReport::new(self.inner.name.to_string(), started_at.elapsed());
1164 report.children = child_reports;
1165 self.record_task_outcomes(&mut report);
1166
1167 let aborted_now = report.aborted.saturating_sub(aborted_before);
1168 if aborted_now > 0 {
1169 report.annotations.push(ShutdownAnnotation::new(format!(
1170 "confirmed cancellation of {aborted_now} aborted tasks"
1171 )));
1172 }
1173
1174 self.inner.lifecycle.store(STATE_SHUTDOWN_COMPLETED, Ordering::Release);
1175 report
1176 }
1177
1178 fn abort_tracked_tasks(&self) {
1179 for mut entry in self.inner.registry.tasks.iter_mut() {
1180 entry.abort_requested = true;
1181 if let Some(abort_handle) = &entry.abort_handle {
1182 abort_handle.abort();
1183 }
1184 }
1185 }
1186
1187 fn record_task_outcomes(&self, report: &mut ShutdownReport) {
1188 let _settlement = self.inner.settlement_gate.lock();
1191 report.completed = self.inner.completed.load(Ordering::Relaxed);
1192 report.cancelled = self.inner.cancelled.load(Ordering::Relaxed);
1193 report.panicked = self.inner.panicked.load(Ordering::Relaxed);
1194 report.aborted = self.inner.aborted.load(Ordering::Relaxed);
1195 let closed_components = self.inner.closed_component_requests.load(Ordering::Relaxed);
1196 if closed_components > 0 {
1197 report.annotations.push(ShutdownAnnotation::new(format!(
1198 "{closed_components} component requests after admission closed received closed groups"
1199 )));
1200 }
1201 let mut requested = 0;
1202 for entry in self.inner.registry.tasks.iter() {
1203 requested += usize::from(entry.abort_requested);
1204 report.leaked += 1;
1205 report.push_remaining_task(entry.snapshot(TaskState::Leaked));
1206 }
1207 if requested > 0 {
1208 report.annotations.push(ShutdownAnnotation::new(format!(
1209 "abort requested for {requested} tasks whose destruction is not yet confirmed"
1210 )));
1211 }
1212 }
1213}
1214
1215impl TaskGroupInner {
1216 fn new(
1217 id: TaskGroupId,
1218 parent_id: Option<TaskGroupId>,
1219 name: Arc<str>,
1220 runtime: RuntimeHandle,
1221 cancellation_token: CancellationToken,
1222 parent: Option<Arc<TaskGroupInner>>,
1223 tree: Arc<TaskGroupTreeState>,
1224 ) -> Self {
1225 Self {
1226 id,
1227 parent_id,
1228 name,
1229 runtime,
1230 cancellation_token,
1231 tracker: TaskTracker::new(),
1232 registry: Arc::new(ActiveTaskRegistry::new()),
1233 parent,
1234 tree,
1235 closed_component_requests: AtomicUsize::new(0),
1236 next_task_id: AtomicU64::new(1),
1237 completed: AtomicUsize::new(0),
1238 cancelled: AtomicUsize::new(0),
1239 aborted: AtomicUsize::new(0),
1240 panicked: AtomicUsize::new(0),
1241 lifecycle: AtomicU8::new(STATE_OPEN),
1242 spawn_gate: Mutex::new(()),
1243 settlement_gate: Mutex::new(()),
1244 shutdown: ShutdownCoordinator::new(),
1245 }
1246 }
1247
1248 fn lifecycle_state(&self) -> TaskGroupLifecycleState {
1249 match self.lifecycle.load(Ordering::Acquire) {
1250 STATE_OPEN => TaskGroupLifecycleState::Open,
1251 STATE_CLOSING if self.cancellation_token.is_cancelled() => TaskGroupLifecycleState::Closed,
1256 STATE_CLOSING => TaskGroupLifecycleState::Closing,
1257 STATE_CLOSED => TaskGroupLifecycleState::Closed,
1258 STATE_SHUTDOWN_COMPLETED => TaskGroupLifecycleState::ShutdownCompleted,
1259 _ => TaskGroupLifecycleState::Poisoned,
1260 }
1261 }
1262
1263 fn mark_poisoned_if_open(&self) {
1264 if self
1265 .lifecycle
1266 .compare_exchange(STATE_OPEN, STATE_POISONED, Ordering::AcqRel, Ordering::Acquire)
1267 .is_ok()
1268 {
1269 self.tree.poisoned_groups.fetch_add(1, Ordering::Relaxed);
1270 tracing::error!(
1271 group = %self.path(),
1272 "task group poisoned by a panicking task; it no longer admits work"
1273 );
1274 }
1275 }
1276
1277 fn path(&self) -> String {
1279 let mut names = vec![self.name.as_ref()];
1280 let mut parent = self.parent.as_deref();
1281 while let Some(group) = parent {
1282 names.push(group.name.as_ref());
1283 parent = group.parent.as_deref();
1284 }
1285 names.reverse();
1286 names.join("/")
1287 }
1288
1289 fn finish_task(&self, task_id: TaskId, result: TaskResult) {
1290 let _settlement = self.settlement_gate.lock();
1291 let Entry::Occupied(entry) = self.registry.tasks.entry(task_id) else {
1292 return;
1293 };
1294
1295 match result {
1296 TaskResult::Completed => {
1297 self.completed.fetch_add(1, Ordering::Relaxed);
1298 }
1299 TaskResult::Cancelled => {
1300 self.cancelled.fetch_add(1, Ordering::Relaxed);
1301 }
1302 TaskResult::Panicked => {
1303 self.panicked.fetch_add(1, Ordering::Relaxed);
1304 self.mark_poisoned_if_open();
1305 }
1306 TaskResult::Aborted => {
1307 self.aborted.fetch_add(1, Ordering::Relaxed);
1308 }
1309 }
1310 self.registry.remove_task_detail(task_id);
1313 entry.remove();
1314 }
1315}
1316
1317impl Drop for TaskGroupInner {
1318 fn drop(&mut self) {
1319 let mut parent = self.parent.take();
1320 let mut child_id = self.id;
1321 while let Some(ancestor) = parent {
1322 ancestor.registry.unregister_component(child_id);
1323 let Some(mut ancestor) = Arc::into_inner(ancestor) else {
1324 break;
1325 };
1326 child_id = ancestor.id;
1327 parent = ancestor.parent.take();
1328 }
1331 }
1332}
1333
1334impl TaskMeta {
1335 fn snapshot(&self, override_state: TaskState) -> TaskSnapshot {
1336 TaskSnapshot {
1337 id: self.id,
1338 name: self.name.to_string(),
1339 group_id: self.group_id,
1340 group_name: self.group_name.to_string(),
1341 kind: self.kind,
1342 state: override_state,
1343 elapsed: self.started_at.elapsed(),
1344 }
1345 }
1346}
1347
1348#[cfg(test)]
1349mod tests {
1350 use super::*;
1351 use crate::RuntimeContext;
1352
1353 #[tokio::test]
1354 async fn a_closing_group_reports_closed_as_soon_as_its_token_is_cancelled() {
1355 let context = RuntimeContext::from_current("closing-then-cancelled");
1356 let group = context.service_context("owner").task_group().clone();
1357 let child = group.try_child("child").unwrap();
1358
1359 let descendants = group.close_admission_tree();
1362 assert_eq!(group.lifecycle_state(), TaskGroupLifecycleState::Closing);
1363 assert_eq!(child.lifecycle_state(), TaskGroupLifecycleState::Closing);
1364 group.inner.cancellation_token.cancel();
1365 assert_eq!(group.lifecycle_state(), TaskGroupLifecycleState::Closed);
1366 assert_eq!(child.lifecycle_state(), TaskGroupLifecycleState::Closed);
1367
1368 group.mark_closed(&descendants);
1369 assert_eq!(group.lifecycle_state(), TaskGroupLifecycleState::Closed);
1370 assert!(group.shutdown(Duration::from_secs(1)).await.is_healthy());
1371 assert_eq!(group.lifecycle_state(), TaskGroupLifecycleState::ShutdownCompleted);
1372 }
1373}