1use std::future::Future;
16use std::num::NonZeroUsize;
17use std::sync::atomic::AtomicU64;
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::DashMap;
25use parking_lot::Mutex;
26use tokio::sync::OwnedSemaphorePermit;
27use tokio::sync::Semaphore;
28use tokio_util::sync::CancellationToken;
29use tokio_util::task::task_tracker::TaskTrackerToken;
30use tokio_util::task::TaskTracker;
31
32use super::admission::GlobalBlockingBudget;
33use super::admission::GlobalBlockingPermit;
34use super::diagnostics::BlockingTaskMeta;
35use super::diagnostics::BlockingTaskTable;
36use super::BlockingExecutorSnapshot;
37use super::BlockingKind;
38use super::BlockingLane;
39use super::BlockingPoolPolicy;
40use super::BlockingTaskId;
41use super::BlockingTaskState;
42use crate::error::RuntimeContractViolation;
43use crate::error::RuntimeError;
44use crate::error::RuntimeResult;
45use crate::handle::RuntimeHandle;
46use crate::shutdown_deadline::ShutdownDeadline;
47use crate::task_group::TaskGroup;
48use crate::task_group::TaskGroupLifecycleState;
49
50#[derive(Debug, Clone)]
61pub struct BlockingExecutor {
62 runtime: RuntimeHandle,
63 policy: Arc<BlockingPoolPolicy>,
64 lane: BlockingLane,
65 budget: GlobalBlockingBudget,
66 queue_permits: Arc<Semaphore>,
67 tasks: Arc<BlockingTaskTable>,
68 next_task_id: Arc<AtomicU64>,
69 rejected: Arc<AtomicU64>,
70 admission: BlockingAdmission,
71 isolated: Option<Arc<IsolatedLifecycle>>,
72}
73
74#[derive(Debug, Default)]
75struct IsolatedLifecycle {
76 gate: Mutex<()>,
77 closed: CancellationToken,
78 tracker: TaskTracker,
79}
80
81#[derive(Debug, Clone)]
86pub struct BlockingExecutorShutdownReport {
87 pub completed: bool,
89 pub pending_operations: usize,
91 pub snapshot: BlockingExecutorSnapshot,
93}
94
95#[derive(Debug, Clone)]
96enum BlockingAdmission {
97 Unscoped,
98 Scope(TaskGroup),
99 Drain(Arc<DrainLeaseState>),
100}
101
102#[derive(Debug)]
103struct DrainLeaseState {
104 scope: TaskGroup,
105 deadline: ShutdownDeadline,
106 remaining: AtomicUsize,
107}
108
109impl DrainLeaseState {
110 fn effective_deadline(&self) -> ShutdownDeadline {
111 self.scope
112 .shutdown_deadline()
113 .map_or(self.deadline, |scope_deadline| self.deadline.earliest(scope_deadline))
114 }
115}
116
117#[derive(Debug)]
126pub struct BlockingDrainLease {
127 executor: BlockingExecutor,
128}
129
130impl BlockingDrainLease {
131 pub fn spawn_io<F, R>(
137 &self,
138 name: impl Into<Arc<str>>,
139 operation: F,
140 ) -> impl Future<Output = RuntimeResult<R>> + Send + '_
141 where
142 F: FnOnce() -> R + Send + 'static,
143 R: Send + 'static,
144 {
145 self.executor.spawn_io(name, operation)
146 }
147
148 pub fn spawn_io_until<F, R>(
150 &self,
151 name: impl Into<Arc<str>>,
152 deadline: ShutdownDeadline,
153 operation: F,
154 ) -> impl Future<Output = RuntimeResult<R>> + Send + '_
155 where
156 F: FnOnce() -> R + Send + 'static,
157 R: Send + 'static,
158 {
159 self.executor.spawn_io_until(name, deadline, operation)
160 }
161
162 #[must_use]
164 pub fn remaining_operations(&self) -> usize {
165 let BlockingAdmission::Drain(state) = &self.executor.admission else {
166 return 0;
167 };
168 state.remaining.load(Ordering::Acquire)
169 }
170}
171
172#[derive(Debug)]
173struct DrainReservation {
174 state: Option<Arc<DrainLeaseState>>,
175}
176
177impl DrainReservation {
178 const fn none() -> Self {
179 Self { state: None }
180 }
181
182 fn committed(mut self) {
183 self.state.take();
184 }
185}
186
187impl Drop for DrainReservation {
188 fn drop(&mut self) {
189 if let Some(state) = self.state.take() {
190 state.remaining.fetch_add(1, Ordering::Release);
191 }
192 }
193}
194
195#[derive(Debug, Clone, Copy)]
196enum AdmissionFailure {
197 ScopeClosed,
198 LeaseExpired,
199 LeaseExhausted,
200 QueueCapacityExhausted,
201 QueueDeadlineExpired,
202}
203
204impl AdmissionFailure {
205 fn into_error(self) -> RuntimeError {
206 match self {
207 Self::ScopeClosed => RuntimeError::closed(crate::RuntimeOperation::BlockingQueueAdmission),
208 Self::LeaseExpired | Self::QueueDeadlineExpired => {
209 RuntimeError::timed_out(crate::RuntimeOperation::BlockingQueueAdmission)
210 }
211 Self::LeaseExhausted | Self::QueueCapacityExhausted => {
212 RuntimeError::capacity(crate::RuntimeOperation::BlockingQueueAdmission)
213 }
214 }
215 }
216}
217
218struct QueuedBlockingTaskGuard {
219 tasks: Arc<DashMap<BlockingTaskId, BlockingTaskMeta>>,
220 task_id: BlockingTaskId,
221 armed: bool,
222}
223
224impl QueuedBlockingTaskGuard {
225 fn new(tasks: Arc<DashMap<BlockingTaskId, BlockingTaskMeta>>, task_id: BlockingTaskId) -> Self {
226 Self {
227 tasks,
228 task_id,
229 armed: true,
230 }
231 }
232
233 fn disarm(mut self) {
234 self.armed = false;
235 }
236}
237
238impl Drop for QueuedBlockingTaskGuard {
239 fn drop(&mut self) {
240 if self.armed {
241 self.tasks.remove(&self.task_id);
242 }
243 }
244}
245
246pub(crate) struct BlockingTask<R>
251where
252 R: Send + 'static,
253{
254 join_handle: Option<tokio::task::JoinHandle<RuntimeResult<R>>>,
255 tasks: Arc<DashMap<BlockingTaskId, BlockingTaskMeta>>,
256 task_id: BlockingTaskId,
257 wait_deadline: Instant,
258}
259
260impl<R> BlockingTask<R>
261where
262 R: Send + 'static,
263{
264 fn new(
265 join_handle: tokio::task::JoinHandle<RuntimeResult<R>>,
266 tasks: Arc<DashMap<BlockingTaskId, BlockingTaskMeta>>,
267 task_id: BlockingTaskId,
268 wait_deadline: Instant,
269 ) -> Self {
270 Self {
271 join_handle: Some(join_handle),
272 tasks,
273 task_id,
274 wait_deadline,
275 }
276 }
277
278 pub(crate) async fn wait(&mut self) -> RuntimeResult<R> {
280 let Some(join_handle) = self.join_handle.as_mut() else {
281 return Err(RuntimeError::context_unavailable(
282 crate::RuntimeOperation::RunBlockingTask,
283 ));
284 };
285 let result = join_handle.await;
286 self.join_handle.take();
287 result.map_err(|error| RuntimeError::join(crate::RuntimeOperation::RunBlockingTask, error))?
288 }
289
290 async fn wait_until(&mut self, deadline: Instant) -> RuntimeResult<R> {
291 match tokio::time::timeout_at(tokio::time::Instant::from_std(deadline), self.wait()).await {
292 Ok(result) => result,
293 Err(_elapsed) => {
294 self.mark_timed_out();
295 Err(RuntimeError::timed_out(crate::RuntimeOperation::BlockingTask))
296 }
297 }
298 }
299
300 fn mark_timed_out(&mut self) {
301 if let Some(mut meta) = self.tasks.get_mut(&self.task_id) {
302 meta.state = BlockingTaskState::TimedOutStillRunning;
303 }
304 }
305}
306
307impl<R> Drop for BlockingTask<R>
308where
309 R: Send + 'static,
310{
311 fn drop(&mut self) {
312 if self.join_handle.is_some() {
313 self.mark_timed_out();
314 }
315 }
316}
317
318struct BlockingSubmission<F> {
320 operation: F,
321 registration: Option<TaskTrackerToken>,
322}
323
324struct BlockingWork<F> {
327 operation: F,
328 permit: GlobalBlockingPermit,
329 completion: BlockingCompletionGuard,
330 execution_deadline: Option<ShutdownDeadline>,
331}
332
333impl<F> BlockingWork<F> {
334 fn run<R>(self) -> RuntimeResult<R>
335 where
336 F: FnOnce() -> R,
337 {
338 let completion = self.completion;
340 let permit = self.permit;
341 let result = if self.execution_deadline.is_some_and(ShutdownDeadline::is_expired) {
342 Err(RuntimeError::timed_out(crate::RuntimeOperation::BlockingTaskDeadline))
343 } else {
344 Ok((self.operation)())
345 };
346 drop(permit);
347 drop(completion);
348 result
349 }
350}
351
352struct BlockingCompletionGuard {
353 tasks: Arc<DashMap<BlockingTaskId, BlockingTaskMeta>>,
354 task_id: BlockingTaskId,
355 _isolated_registration: Option<TaskTrackerToken>,
356}
357
358impl Drop for BlockingCompletionGuard {
359 fn drop(&mut self) {
360 self.tasks.remove(&self.task_id);
361 }
362}
363
364impl BlockingExecutor {
365 pub fn new(policy: BlockingPoolPolicy, owner_group: TaskGroup) -> Result<Self, RuntimeContractViolation> {
371 Self::new_isolated(policy, owner_group)
372 }
373
374 pub fn new_isolated(policy: BlockingPoolPolicy, owner_group: TaskGroup) -> Result<Self, RuntimeContractViolation> {
389 policy.validate()?;
390 let capacity = policy.max_concurrency;
391 let mut executor = Self::new_with_budget(
392 policy,
393 BlockingLane::StorageIo,
394 GlobalBlockingBudget::isolated(capacity),
395 owner_group.runtime().clone(),
396 );
397 executor.isolated = Some(Arc::new(IsolatedLifecycle::default()));
398 owner_group.register_isolated_blocking(&executor.tasks);
399 Ok(executor.scoped_to(owner_group))
400 }
401
402 pub(crate) fn new_managed(
403 policy: BlockingPoolPolicy,
404 lane: BlockingLane,
405 budget: GlobalBlockingBudget,
406 runtime: RuntimeHandle,
407 ) -> Result<Self, RuntimeContractViolation> {
408 policy.validate()?;
409 Ok(Self::new_with_budget(policy, lane, budget, runtime))
410 }
411
412 fn new_with_budget(
413 policy: BlockingPoolPolicy,
414 lane: BlockingLane,
415 budget: GlobalBlockingBudget,
416 runtime: RuntimeHandle,
417 ) -> Self {
418 Self {
419 runtime,
420 queue_permits: Arc::new(Semaphore::new(policy.max_queue_depth)),
421 policy: Arc::new(policy),
422 lane,
423 budget,
424 tasks: Arc::new(DashMap::with_shard_amount(8)),
426 next_task_id: Arc::new(AtomicU64::new(1)),
427 rejected: Arc::new(AtomicU64::new(0)),
428 admission: BlockingAdmission::Unscoped,
429 isolated: None,
430 }
431 }
432
433 pub fn stop_admission(&self) -> RuntimeResult<()> {
443 let lifecycle = self
444 .isolated
445 .as_ref()
446 .ok_or_else(|| RuntimeError::unsupported(crate::RuntimeOperation::ShutdownBlockingExecutor))?;
447 let _gate = lifecycle.gate.lock();
448 lifecycle.closed.cancel();
449 lifecycle.tracker.close();
450 Ok(())
451 }
452
453 pub async fn shutdown_until(&self, deadline: ShutdownDeadline) -> RuntimeResult<BlockingExecutorShutdownReport> {
463 self.stop_admission()?;
464 let lifecycle = self
465 .isolated
466 .as_ref()
467 .expect("stop_admission validates an isolated executor");
468 let _ = tokio::time::timeout_at(deadline.instant().into(), lifecycle.tracker.wait()).await;
469 let pending_operations = lifecycle.tracker.len();
470 Ok(BlockingExecutorShutdownReport {
471 completed: pending_operations == 0,
472 pending_operations,
473 snapshot: self.snapshot(),
474 })
475 }
476
477 async fn isolated_closed(&self) {
478 match &self.isolated {
479 Some(lifecycle) => lifecycle.closed.cancelled().await,
480 None => std::future::pending().await,
481 }
482 }
483
484 pub(crate) fn scoped_to(&self, scope: TaskGroup) -> Self {
485 let mut scoped = self.clone();
486 scoped.admission = BlockingAdmission::Scope(scope);
487 scoped
488 }
489
490 pub fn try_drain_lease(
503 &self,
504 deadline: ShutdownDeadline,
505 max_operations: NonZeroUsize,
506 ) -> RuntimeResult<BlockingDrainLease> {
507 let BlockingAdmission::Scope(scope) = &self.admission else {
508 return Err(AdmissionFailure::ScopeClosed.into_error());
509 };
510 if !scope_is_open(scope) {
511 return Err(AdmissionFailure::ScopeClosed.into_error());
512 }
513 let deadline = scope
514 .shutdown_deadline()
515 .map_or(deadline, |scope_deadline| deadline.earliest(scope_deadline));
516 if deadline.is_expired() {
517 return Err(AdmissionFailure::LeaseExpired.into_error());
518 }
519 let mut leased = self.clone();
520 leased.admission = BlockingAdmission::Drain(Arc::new(DrainLeaseState {
521 scope: scope.clone(),
522 deadline,
523 remaining: AtomicUsize::new(max_operations.get()),
524 }));
525 Ok(BlockingDrainLease { executor: leased })
526 }
527
528 pub fn policy(&self) -> &BlockingPoolPolicy {
530 &self.policy
531 }
532
533 pub fn spawn_io<F, R>(
535 &self,
536 name: impl Into<Arc<str>>,
537 operation: F,
538 ) -> impl Future<Output = RuntimeResult<R>> + Send + '_
539 where
540 F: FnOnce() -> R + Send + 'static,
541 R: Send + 'static,
542 {
543 self.spawn(name, BlockingKind::ShortIo, operation)
544 }
545
546 #[cfg(test)]
548 pub(crate) fn submit_io<F, R>(
549 &self,
550 name: impl Into<Arc<str>>,
551 operation: F,
552 ) -> impl Future<Output = RuntimeResult<BlockingTask<R>>> + Send + '_
553 where
554 F: FnOnce() -> R + Send + 'static,
555 R: Send + 'static,
556 {
557 self.submit_inner(name.into(), BlockingKind::ShortIo, None, Box::new(operation))
558 }
559
560 pub(crate) fn submit_io_until<F, R>(
567 &self,
568 name: impl Into<Arc<str>>,
569 deadline: Option<ShutdownDeadline>,
570 operation: F,
571 ) -> impl Future<Output = RuntimeResult<BlockingTask<R>>> + Send + '_
572 where
573 F: FnOnce() -> R + Send + 'static,
574 R: Send + 'static,
575 {
576 self.submit_inner(name.into(), BlockingKind::ShortIo, deadline, Box::new(operation))
577 }
578
579 pub fn spawn_io_until<F, R>(
581 &self,
582 name: impl Into<Arc<str>>,
583 deadline: ShutdownDeadline,
584 operation: F,
585 ) -> impl Future<Output = RuntimeResult<R>> + Send + '_
586 where
587 F: FnOnce() -> R + Send + 'static,
588 R: Send + 'static,
589 {
590 self.spawn_until(name, BlockingKind::ShortIo, deadline, operation)
591 }
592
593 pub fn spawn<F, R>(
599 &self,
600 name: impl Into<Arc<str>>,
601 kind: BlockingKind,
602 operation: F,
603 ) -> impl Future<Output = RuntimeResult<R>> + Send + '_
604 where
605 F: FnOnce() -> R + Send + 'static,
606 R: Send + 'static,
607 {
608 self.spawn_inner(name.into(), kind, None, Box::new(operation))
612 }
613
614 pub fn spawn_until<F, R>(
619 &self,
620 name: impl Into<Arc<str>>,
621 kind: BlockingKind,
622 deadline: ShutdownDeadline,
623 operation: F,
624 ) -> impl Future<Output = RuntimeResult<R>> + Send + '_
625 where
626 F: FnOnce() -> R + Send + 'static,
627 R: Send + 'static,
628 {
629 self.spawn_inner(name.into(), kind, Some(deadline), Box::new(operation))
630 }
631
632 async fn spawn_inner<F, R>(
633 &self,
634 name: Arc<str>,
635 kind: BlockingKind,
636 deadline: Option<ShutdownDeadline>,
637 operation: F,
638 ) -> RuntimeResult<R>
639 where
640 F: FnOnce() -> R + Send + 'static,
641 R: Send + 'static,
642 {
643 if std::mem::size_of::<R>() > crate::stack::MAX_INLINE_SIZE {
644 let task = self
647 .submit_inner(name, kind, deadline, move || Box::new(operation()))
648 .await?;
649 self.wait_for_caller(task).await.map(|value| *value)
650 } else {
651 let task = self.submit_inner(name, kind, deadline, operation).await?;
652 self.wait_for_caller(task).await
653 }
654 }
655
656 async fn wait_for_caller<R: Send + 'static>(&self, mut task: BlockingTask<R>) -> RuntimeResult<R> {
657 let task_id = task.task_id;
658 let started_at = Instant::now();
659 let deadline = task.wait_deadline;
660 let result = task.wait_until(deadline).await;
661 if result.is_ok() {
662 let elapsed = started_at.elapsed();
663 if elapsed > self.policy.warn_after {
664 tracing::warn!(
665 task_id = task_id.as_u64(),
666 elapsed_ms = elapsed.as_millis(),
667 "blocking task exceeded warn_after"
668 );
669 }
670 }
671 result
672 }
673
674 async fn submit_inner<F, R>(
675 &self,
676 name: Arc<str>,
677 kind: BlockingKind,
678 deadline: Option<ShutdownDeadline>,
679 operation: F,
680 ) -> RuntimeResult<BlockingTask<R>>
681 where
682 F: FnOnce() -> R + Send + 'static,
683 R: Send + 'static,
684 {
685 if kind == BlockingKind::LongRunning {
686 self.rejected.fetch_add(1, Ordering::Relaxed);
687 return Err(RuntimeError::unsupported(crate::RuntimeOperation::BlockingExecutorKind));
688 }
689 let caller_deadline = deadline;
690 let admission_deadline = self.admission.effective_deadline(caller_deadline);
691 if admission_deadline.is_some_and(ShutdownDeadline::is_expired) {
692 self.rejected.fetch_add(1, Ordering::Relaxed);
693 return Err(RuntimeError::timed_out(crate::RuntimeOperation::BlockingQueueAdmission));
694 }
695 let drain_reservation = self.admission.reserve().map_err(|failure| {
696 self.rejected.fetch_add(1, Ordering::Relaxed);
697 failure.into_error()
698 })?;
699 let submitted_at = Instant::now();
700 let operation_deadline = admission_deadline.map_or_else(
701 || {
702 submitted_at
703 .checked_add(self.policy.queue_timeout.saturating_add(self.policy.task_timeout))
704 .unwrap_or(submitted_at)
705 },
706 ShutdownDeadline::instant,
707 );
708 let queue_deadline = phase_deadline(submitted_at, self.policy.queue_timeout, operation_deadline);
709
710 let isolated_registration = if let Some(lifecycle) = &self.isolated {
711 let _gate = lifecycle.gate.lock();
712 if lifecycle.closed.is_cancelled() {
713 self.rejected.fetch_add(1, Ordering::Relaxed);
714 return Err(AdmissionFailure::ScopeClosed.into_error());
715 }
716 Some(lifecycle.tracker.token())
717 } else {
718 None
719 };
720
721 let submission = BlockingSubmission {
722 operation,
723 registration: isolated_registration,
724 };
725
726 let queue_permit = tokio::select! {
727 biased;
728 _ = self.isolated_closed() => Err(AdmissionFailure::ScopeClosed),
729 result = self.acquire_queue_permit(queue_deadline) => result,
730 }
731 .map_err(|failure| {
732 self.rejected.fetch_add(1, Ordering::Relaxed);
733 failure.into_error()
734 })?;
735 let task_id = BlockingTaskId(self.next_task_id.fetch_add(1, Ordering::Relaxed));
736 self.tasks.insert(
737 task_id,
738 BlockingTaskMeta {
739 id: task_id,
740 name: name.clone(),
741 kind,
742 state: BlockingTaskState::Queued,
743 queued_at: Instant::now(),
744 started_at: None,
745 },
746 );
747 let queued_task_guard = QueuedBlockingTaskGuard::new(self.tasks.clone(), task_id);
748
749 let permit = tokio::select! {
750 biased;
751 _ = self.isolated_closed() => Err(AdmissionFailure::ScopeClosed),
752 result = self.admit(queue_deadline) => result,
753 }
754 .map_err(|failure| {
755 self.rejected.fetch_add(1, Ordering::Relaxed);
756 failure.into_error()
757 })?;
758 drop(queue_permit);
759
760 let _isolated_gate = self.isolated.as_ref().map(|lifecycle| lifecycle.gate.lock());
763 if self
764 .isolated
765 .as_ref()
766 .is_some_and(|lifecycle| lifecycle.closed.is_cancelled())
767 {
768 self.rejected.fetch_add(1, Ordering::Relaxed);
769 return Err(AdmissionFailure::ScopeClosed.into_error());
770 }
771
772 let execution_deadline = self.admission.effective_deadline(caller_deadline);
773 let current_operation_deadline = execution_deadline.map_or(operation_deadline, ShutdownDeadline::instant);
774 let task_deadline = phase_deadline(Instant::now(), self.policy.task_timeout, current_operation_deadline);
775 if task_deadline <= Instant::now()
776 || execution_deadline.is_some_and(ShutdownDeadline::is_expired)
777 || !self.admission.still_valid()
778 {
779 self.rejected.fetch_add(1, Ordering::Relaxed);
780 return Err(
781 if task_deadline <= Instant::now() || execution_deadline.is_some_and(ShutdownDeadline::is_expired) {
782 RuntimeError::timed_out(crate::RuntimeOperation::BlockingTaskDeadline)
783 } else {
784 AdmissionFailure::ScopeClosed.into_error()
785 },
786 );
787 }
788
789 let started_at = Instant::now();
790 if let Some(mut meta) = self.tasks.get_mut(&task_id) {
791 meta.state = BlockingTaskState::Running;
792 meta.started_at = Some(started_at);
793 }
794 queued_task_guard.disarm();
795
796 let BlockingSubmission {
797 operation,
798 registration,
799 } = submission;
800 let work = BlockingWork {
801 operation,
802 permit,
803 completion: BlockingCompletionGuard {
804 tasks: self.tasks.clone(),
805 task_id,
806 _isolated_registration: registration,
807 },
808 execution_deadline,
809 };
810 let join_handle = self.runtime.tokio_handle().spawn_blocking(move || work.run());
811 drain_reservation.committed();
812 Ok(BlockingTask::new(
813 join_handle,
814 self.tasks.clone(),
815 task_id,
816 task_deadline,
817 ))
818 }
819
820 async fn acquire_queue_permit(&self, deadline: Instant) -> Result<OwnedSemaphorePermit, AdmissionFailure> {
821 if !matches!(&self.admission, BlockingAdmission::Drain(_)) {
822 return self
823 .queue_permits
824 .clone()
825 .try_acquire_owned()
826 .map_err(|_error| AdmissionFailure::QueueCapacityExhausted);
827 }
828
829 let deadline = self.admission.effective_instant(deadline);
830 match tokio::time::timeout_at(
831 tokio::time::Instant::from_std(deadline),
832 self.queue_permits.clone().acquire_owned(),
833 )
834 .await
835 {
836 Ok(Ok(permit)) => Ok(permit),
837 Ok(Err(_closed)) => Err(AdmissionFailure::QueueDeadlineExpired),
838 Err(_elapsed) => Err(AdmissionFailure::QueueDeadlineExpired),
839 }
840 }
841
842 async fn admit(&self, deadline: Instant) -> Result<GlobalBlockingPermit, AdmissionFailure> {
843 match &self.admission {
844 BlockingAdmission::Scope(scope) => {
845 if !scope_is_open(scope) {
846 return Err(AdmissionFailure::ScopeClosed);
847 }
848 let cancellation = scope.cancellation_token();
849 let permit = tokio::select! {
850 biased;
851 _ = cancellation.cancelled() => return Err(AdmissionFailure::ScopeClosed),
852 permit = self.budget.acquire(self.lane, deadline) => {
853 permit.map_err(|()| AdmissionFailure::QueueDeadlineExpired)?
854 }
855 };
856 if !scope_is_open(scope) {
857 drop(permit);
858 return Err(AdmissionFailure::ScopeClosed);
859 }
860 Ok(permit)
861 }
862 BlockingAdmission::Drain(state) => {
863 if state.effective_deadline().is_expired() {
864 return Err(AdmissionFailure::LeaseExpired);
865 }
866 let deadline = self.admission.effective_instant(deadline);
867 self.budget
868 .acquire(self.lane, deadline)
869 .await
870 .map_err(|()| AdmissionFailure::QueueDeadlineExpired)
871 }
872 BlockingAdmission::Unscoped => self
873 .budget
874 .acquire(self.lane, deadline)
875 .await
876 .map_err(|()| AdmissionFailure::QueueDeadlineExpired),
877 }
878 }
879
880 pub(crate) fn aggregate(&self) -> super::BlockingExecutorAggregate {
882 let mut aggregate =
883 super::BlockingExecutorAggregate::new(self.lane, self.policy.max_concurrency, self.policy.max_queue_depth);
884 let now = Instant::now();
885 for entry in self.tasks.iter() {
886 let task = entry.value();
887 aggregate.record_kind(
888 task.kind,
889 now.saturating_duration_since(task.started_at.unwrap_or(task.queued_at)),
890 );
891 match task.state {
892 BlockingTaskState::Queued => aggregate.queued += 1,
893 BlockingTaskState::Running => aggregate.running += 1,
894 BlockingTaskState::TimedOutStillRunning => aggregate.timed_out_still_running += 1,
895 BlockingTaskState::Completed | BlockingTaskState::JoinFailed => {}
896 }
897 }
898 aggregate.blocking_still_running = aggregate.running + aggregate.timed_out_still_running;
899 aggregate
900 }
901
902 pub fn snapshot(&self) -> BlockingExecutorSnapshot {
904 let tasks = self
905 .tasks
906 .iter()
907 .map(|entry| entry.value().snapshot())
908 .collect::<Vec<_>>();
909 let queued = tasks
910 .iter()
911 .filter(|task| task.state == BlockingTaskState::Queued)
912 .count();
913 let running = tasks
914 .iter()
915 .filter(|task| task.state == BlockingTaskState::Running)
916 .count();
917 let timed_out_still_running = tasks
918 .iter()
919 .filter(|task| task.state == BlockingTaskState::TimedOutStillRunning)
920 .count();
921 let oldest_queue_wait = tasks
922 .iter()
923 .filter(|task| task.state == BlockingTaskState::Queued)
924 .map(|task| task.elapsed)
925 .max()
926 .unwrap_or(Duration::ZERO);
927 let admission = self.budget.snapshot(self.lane);
928
929 BlockingExecutorSnapshot {
930 name: self.policy.name.clone(),
931 lane: self.lane,
932 max_concurrency: self.policy.max_concurrency,
933 max_queue_depth: self.policy.max_queue_depth,
934 global_capacity: admission.global_capacity,
935 global_running: admission.global_running,
936 global_available: admission.global_available,
937 lane_reserved: admission.lane_reserved,
938 lane_running: admission.lane_running,
939 lane_borrowed: admission.lane_borrowed,
940 queued,
941 running,
942 timed_out_still_running,
943 blocking_still_running: running + timed_out_still_running,
944 rejected: self.rejected.load(Ordering::Relaxed),
945 oldest_queue_wait,
946 tasks,
947 }
948 }
949
950 pub fn blocking_still_running(&self) -> usize {
952 self.tasks
953 .iter()
954 .filter(|entry| {
955 matches!(
956 entry.value().state,
957 BlockingTaskState::Running | BlockingTaskState::TimedOutStillRunning
958 )
959 })
960 .count()
961 }
962}
963
964impl BlockingAdmission {
965 fn effective_deadline(&self, deadline: Option<ShutdownDeadline>) -> Option<ShutdownDeadline> {
966 match self {
967 Self::Unscoped => deadline,
968 Self::Scope(scope) => bound_deadline(deadline, scope.shutdown_deadline()),
969 Self::Drain(state) => bound_deadline(deadline, Some(state.effective_deadline())),
970 }
971 }
972
973 fn reserve(&self) -> Result<DrainReservation, AdmissionFailure> {
974 match self {
975 Self::Unscoped => Ok(DrainReservation::none()),
976 Self::Scope(scope) => {
977 if scope_is_open(scope) {
978 Ok(DrainReservation::none())
979 } else {
980 Err(AdmissionFailure::ScopeClosed)
981 }
982 }
983 Self::Drain(state) => {
984 if state.effective_deadline().is_expired() {
985 return Err(AdmissionFailure::LeaseExpired);
986 }
987 let reserved = state
988 .remaining
989 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |remaining| {
990 remaining.checked_sub(1)
991 });
992 if reserved.is_err() {
993 return Err(AdmissionFailure::LeaseExhausted);
994 }
995 Ok(DrainReservation {
996 state: Some(Arc::clone(state)),
997 })
998 }
999 }
1000 }
1001
1002 fn still_valid(&self) -> bool {
1003 match self {
1004 Self::Unscoped => true,
1005 Self::Scope(scope) => {
1006 scope_is_open(scope) && !scope.shutdown_deadline().is_some_and(ShutdownDeadline::is_expired)
1007 }
1008 Self::Drain(state) => !state.effective_deadline().is_expired(),
1009 }
1010 }
1011
1012 fn effective_instant(&self, deadline: Instant) -> Instant {
1013 self.effective_deadline(Some(ShutdownDeadline::at(deadline)))
1014 .map_or(deadline, ShutdownDeadline::instant)
1015 }
1016}
1017
1018fn bound_deadline(
1019 deadline: Option<ShutdownDeadline>,
1020 scope_deadline: Option<ShutdownDeadline>,
1021) -> Option<ShutdownDeadline> {
1022 match (deadline, scope_deadline) {
1023 (Some(deadline), Some(scope_deadline)) => Some(deadline.earliest(scope_deadline)),
1024 (Some(deadline), None) => Some(deadline),
1025 (None, Some(scope_deadline)) => Some(scope_deadline),
1026 (None, None) => None,
1027 }
1028}
1029
1030fn scope_is_open(scope: &TaskGroup) -> bool {
1031 scope.lifecycle_state() == TaskGroupLifecycleState::Open && !scope.cancellation_token().is_cancelled()
1032}
1033
1034fn phase_deadline(started_at: Instant, policy_timeout: Duration, operation_deadline: Instant) -> Instant {
1035 started_at
1036 .checked_add(policy_timeout)
1037 .unwrap_or(operation_deadline)
1038 .min(operation_deadline)
1039}
1040
1041#[cfg(test)]
1042mod tests;