1use std::cell::RefCell;
2use std::future::Future;
3use std::panic::{catch_unwind, AssertUnwindSafe};
4use std::pin::Pin;
5use std::sync::atomic::{AtomicU8, Ordering};
6use std::sync::Arc;
7use std::task::{Context, Poll};
8
9use web_time::Instant;
14
15use crate::cycle::GcEdge;
16
17use super::{
18 CompletionDecoder, CompletionDelivery, CompletionKind, CompletionSender, ExternalCompletion,
19 ExternalFailure, ExternalFailureCode, IdCounter, IdExhausted, InterruptibleResource,
20 OperationId, QuarantineBound, ResourceClass, RuntimeId, SendPayload, Trace, WaitGeneration,
21 WaitId,
22};
23
24type JobResult = Result<SendPayload, ExternalFailure>;
25type AsyncJobFuture = Pin<Box<dyn Future<Output = JobResult> + Send + 'static>>;
26type AsyncJob = Box<dyn FnOnce() -> AsyncJobFuture + Send + 'static>;
27type BlockingJob = Box<dyn FnOnce() -> JobResult + Send + 'static>;
28
29enum PreparedJob {
30 Async(AsyncJob),
31 Blocking {
32 class: BlockingDispatchClass,
33 job: BlockingJob,
34 },
35}
36
37pub struct PreparedExternalOperation {
38 kind: CompletionKind,
39 decoder: Option<Box<dyn CompletionDecoder>>,
40 resource: Option<ResourceClass>,
41 job: Option<PreparedJob>,
42}
43
44impl PreparedExternalOperation {
45 #[doc(hidden)]
46 pub fn completion_kind(&self) -> CompletionKind {
47 self.kind
48 }
49 pub fn interruptible_async<F, Fut>(
50 kind: CompletionKind,
51 decoder: Box<dyn CompletionDecoder>,
52 resource: InterruptibleResource,
53 job: F,
54 ) -> Self
55 where
56 F: FnOnce() -> Fut + Send + 'static,
57 Fut: Future<Output = JobResult> + Send + 'static,
58 {
59 let (resource_kind, hook) = resource.into_parts();
60 Self {
61 kind,
62 decoder: Some(decoder),
63 resource: Some(ResourceClass::interruptible(resource_kind, hook)),
64 job: Some(PreparedJob::Async(Box::new(move || Box::pin(job())))),
65 }
66 }
67
68 pub fn interruptible_blocking<F>(
69 kind: CompletionKind,
70 decoder: Box<dyn CompletionDecoder>,
71 resource: InterruptibleResource,
72 job: F,
73 ) -> Self
74 where
75 F: FnOnce() -> JobResult + Send + 'static,
76 {
77 let (resource_kind, hook) = resource.into_parts();
78 Self {
79 kind,
80 decoder: Some(decoder),
81 resource: Some(ResourceClass::interruptible(resource_kind, hook)),
82 job: Some(PreparedJob::Blocking {
83 class: BlockingDispatchClass::Interruptible,
84 job: Box::new(job),
85 }),
86 }
87 }
88
89 pub fn quarantined_blocking<F>(
90 kind: CompletionKind,
91 decoder: Box<dyn CompletionDecoder>,
92 bound: QuarantineBound,
93 job: F,
94 ) -> Self
95 where
96 F: FnOnce() -> JobResult + Send + 'static,
97 {
98 Self {
99 kind,
100 decoder: Some(decoder),
101 resource: Some(ResourceClass::quarantined(bound)),
102 job: Some(PreparedJob::Blocking {
103 class: BlockingDispatchClass::QuarantinedBounded,
104 job: Box::new(job),
105 }),
106 }
107 }
108}
109
110impl Trace for PreparedExternalOperation {
111 fn trace(&self, sink: &mut dyn FnMut(GcEdge<'_>)) -> bool {
112 self.decoder
113 .as_ref()
114 .is_none_or(|decoder| decoder.trace(sink))
115 && self
116 .resource
117 .as_ref()
118 .is_none_or(|resource| resource.trace(sink))
119 }
120}
121
122impl Drop for PreparedExternalOperation {
123 fn drop(&mut self) {
124 contained_drop_option(&mut self.decoder);
125 contained_drop_option(&mut self.resource);
126 contained_drop_option(&mut self.job);
127 }
128}
129
130#[derive(Debug, Eq, PartialEq)]
131pub struct RuntimeIssuedCompletionIdentity {
132 runtime_id: RuntimeId,
133 wait_id: WaitId,
134 generation: WaitGeneration,
135 operation_id: OperationId,
136 kind: CompletionKind,
137 authority: u64,
138}
139
140impl RuntimeIssuedCompletionIdentity {
141 pub fn runtime_id(&self) -> RuntimeId {
142 self.runtime_id
143 }
144
145 pub fn wait_id(&self) -> WaitId {
146 self.wait_id
147 }
148
149 pub fn generation(&self) -> WaitGeneration {
150 self.generation
151 }
152
153 pub fn operation_id(&self) -> OperationId {
154 self.operation_id
155 }
156
157 pub fn kind(&self) -> CompletionKind {
158 self.kind
159 }
160}
161
162#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
163pub enum BindCompletionError {
164 #[error("completion identity was issued by a different registrar")]
165 ForeignIdentity,
166 #[error(
167 "completion kind mismatch: identity expects {expected:?}, operation declares {declared:?}"
168 )]
169 KindMismatch {
170 expected: CompletionKind,
171 declared: CompletionKind,
172 },
173}
174
175impl BindCompletionError {
176 pub fn expected(&self) -> CompletionKind {
177 match self {
178 Self::KindMismatch { expected, .. } => *expected,
179 Self::ForeignIdentity => panic!("foreign identity has no expected kind"),
180 }
181 }
182 pub fn declared(&self) -> CompletionKind {
183 match self {
184 Self::KindMismatch { declared, .. } => *declared,
185 Self::ForeignIdentity => panic!("foreign identity has no declared kind"),
186 }
187 }
188}
189
190#[derive(Clone, Copy)]
191struct CompletionIdentity {
192 runtime_id: RuntimeId,
193 wait_id: WaitId,
194 generation: WaitGeneration,
195 operation_id: OperationId,
196 kind: CompletionKind,
197}
198
199#[doc(hidden)]
200pub struct CompletionRegistrar {
201 runtime_id: RuntimeId,
202 authority: u64,
203 sender: Arc<dyn CompletionSender>,
204 waits: RefCell<IdCounter<WaitId>>,
205 generations: RefCell<IdCounter<WaitGeneration>>,
206 operations: RefCell<IdCounter<OperationId>>,
207}
208
209impl CompletionRegistrar {
210 #[doc(hidden)]
211 pub fn register(
212 sender: Arc<dyn CompletionSender>,
213 ) -> Result<(RuntimeId, Self, super::RuntimeScopedIdIssuers), IdExhausted> {
214 let runtime_id = RuntimeId::allocate()?;
215 Ok((
216 runtime_id,
217 Self {
218 runtime_id,
219 authority: runtime_id.get(),
220 sender,
221 waits: RefCell::new(IdCounter::new()),
222 generations: RefCell::new(IdCounter::new()),
223 operations: RefCell::new(IdCounter::new()),
224 },
225 super::RuntimeScopedIdIssuers::new(runtime_id),
226 ))
227 }
228
229 #[doc(hidden)]
230 pub fn issue_wait_identity(&self) -> Result<(WaitId, WaitGeneration), IdExhausted> {
231 Ok((
232 self.waits.borrow_mut().allocate()?,
233 self.generations.borrow_mut().allocate()?,
234 ))
235 }
236
237 #[doc(hidden)]
238 pub fn issue_identity(
239 &self,
240 kind: CompletionKind,
241 ) -> Result<RuntimeIssuedCompletionIdentity, IdExhausted> {
242 let (wait_id, generation) = self.issue_wait_identity()?;
243 Ok(RuntimeIssuedCompletionIdentity {
244 runtime_id: self.runtime_id,
245 wait_id,
246 generation,
247 operation_id: self.operations.borrow_mut().allocate()?,
248 kind,
249 authority: self.authority,
250 })
251 }
252
253 #[doc(hidden)]
254 pub fn bind(
255 &self,
256 identity: RuntimeIssuedCompletionIdentity,
257 mut prepared: PreparedExternalOperation,
258 ) -> Result<ExternalOperationBinding, BindCompletionError> {
259 if identity.runtime_id != self.runtime_id || identity.authority != self.authority {
260 destroy_prepared(prepared);
261 return Err(BindCompletionError::ForeignIdentity);
262 }
263 if identity.kind != prepared.kind {
264 let error = BindCompletionError::KindMismatch {
265 expected: identity.kind,
266 declared: prepared.kind,
267 };
268 destroy_prepared(prepared);
269 return Err(error);
270 }
271 let identity = CompletionIdentity {
272 runtime_id: identity.runtime_id,
273 wait_id: identity.wait_id,
274 generation: identity.generation,
275 operation_id: identity.operation_id,
276 kind: identity.kind,
277 };
278 let decoder = prepared
279 .decoder
280 .take()
281 .expect("prepared operation owns decoder");
282 let resource = prepared
283 .resource
284 .take()
285 .expect("prepared operation owns resource");
286 let job = prepared.job.take().expect("prepared operation owns job");
287 let control = Arc::new(AtomicU8::new(QUEUED));
288 let sink = CompletionSink {
289 sender: Arc::clone(&self.sender),
290 identity,
291 };
292 Ok(ExternalOperationBinding {
293 runtime: Some(RuntimeOperationBinding {
294 decoder: Some(decoder),
295 resource: Some(resource),
296 queue_cancel: Some(ExecutorCancelHandle {
297 state: Arc::clone(&control),
298 }),
299 }),
300 submission: Some(ExecutorSubmission {
301 identity,
302 sink: Some(sink),
303 start: Some(ExecutorStartToken { state: control }),
304 job: Some(job),
305 }),
306 })
307 }
308}
309
310pub struct ExternalOperationBinding {
311 runtime: Option<RuntimeOperationBinding>,
312 submission: Option<ExecutorSubmission>,
313}
314
315impl ExternalOperationBinding {
316 #[doc(hidden)]
317 pub fn split(mut self) -> (RuntimeOperationBinding, ExecutorSubmission) {
318 (
319 self.runtime.take().expect("binding owns runtime half"),
320 self.submission
321 .take()
322 .expect("binding owns submission half"),
323 )
324 }
325}
326
327impl Drop for ExternalOperationBinding {
328 fn drop(&mut self) {
329 contained_drop_option(&mut self.runtime);
330 contained_drop_option(&mut self.submission);
331 }
332}
333
334pub struct RuntimeOperationBinding {
335 decoder: Option<Box<dyn CompletionDecoder>>,
336 resource: Option<ResourceClass>,
337 queue_cancel: Option<ExecutorCancelHandle>,
338}
339
340impl RuntimeOperationBinding {
341 #[doc(hidden)]
342 pub fn into_parts(
343 mut self,
344 ) -> (
345 Box<dyn CompletionDecoder>,
346 ResourceClass,
347 ExecutorCancelHandle,
348 ) {
349 (
350 self.decoder.take().expect("runtime binding owns decoder"),
351 self.resource.take().expect("runtime binding owns resource"),
352 self.queue_cancel
353 .take()
354 .expect("runtime binding owns cancel handle"),
355 )
356 }
357}
358
359impl Drop for RuntimeOperationBinding {
360 fn drop(&mut self) {
361 contained_drop_option(&mut self.decoder);
362 contained_drop_option(&mut self.resource);
363 }
364}
365
366const QUEUED: u8 = 0;
367const CANCELLED: u8 = 1;
368const RUNNING: u8 = 2;
369
370pub struct ExecutorCancelHandle {
371 state: Arc<AtomicU8>,
372}
373
374impl ExecutorCancelHandle {
375 pub fn cancel_before_start(&self) -> CancelBeforeStart {
376 match self
377 .state
378 .compare_exchange(QUEUED, CANCELLED, Ordering::AcqRel, Ordering::Acquire)
379 {
380 Ok(_) => CancelBeforeStart::CancelledQueued,
381 Err(CANCELLED) => CancelBeforeStart::AlreadyCancelled,
382 Err(RUNNING) => CancelBeforeStart::AlreadyRunning,
383 Err(_) => unreachable!("queue control has a valid state"),
384 }
385 }
386}
387
388#[derive(Clone, Copy, Debug, Eq, PartialEq)]
389pub enum CancelBeforeStart {
390 CancelledQueued,
391 AlreadyCancelled,
392 AlreadyRunning,
393}
394
395struct ExecutorStartToken {
396 state: Arc<AtomicU8>,
397}
398
399impl ExecutorStartToken {
400 fn claim_for_run(&self) -> ExecutorStartDecision {
401 match self
402 .state
403 .compare_exchange(QUEUED, RUNNING, Ordering::AcqRel, Ordering::Acquire)
404 {
405 Ok(_) | Err(RUNNING) => ExecutorStartDecision::Run,
406 Err(CANCELLED) => ExecutorStartDecision::CompleteCancelled,
407 Err(_) => unreachable!("queue control has a valid state"),
408 }
409 }
410}
411
412#[derive(Clone, Copy, Debug, Eq, PartialEq)]
413pub enum ExecutorStartDecision {
414 Run,
415 CompleteCancelled,
416}
417
418struct CompletionSink {
419 sender: Arc<dyn CompletionSender>,
420 identity: CompletionIdentity,
421}
422
423impl CompletionSink {
424 fn deliver(self, result: JobResult) -> CompletionDelivery {
425 self.sender.send(ExternalCompletion {
426 runtime_id: self.identity.runtime_id,
427 wait_id: self.identity.wait_id,
428 generation: self.identity.generation,
429 operation_id: self.identity.operation_id,
430 kind: self.identity.kind,
431 result,
432 })
433 }
434}
435
436pub struct ExecutorSubmission {
437 identity: CompletionIdentity,
438 sink: Option<CompletionSink>,
439 start: Option<ExecutorStartToken>,
440 job: Option<PreparedJob>,
441}
442
443impl ExecutorSubmission {
444 pub fn operation_id(&self) -> OperationId {
445 self.identity.operation_id
446 }
447
448 pub fn into_dispatch(mut self) -> ExecutorDispatch {
449 let sink = self.sink.take().expect("submission owns its sink");
450 let job = self.job.take().expect("submission owns its job");
451 let identity = self.identity;
452 match job {
453 PreparedJob::Async(job) => ExecutorDispatch::Async(AsyncExecutorDispatch {
454 identity,
455 sink: Some(sink),
456 start: self.start.take(),
457 job: Some(job),
458 }),
459 PreparedJob::Blocking { class, job } => {
460 ExecutorDispatch::Blocking(BlockingExecutorDispatch {
461 identity,
462 class,
463 sink: Some(sink),
464 start: self.start.take(),
465 job: Some(job),
466 })
467 }
468 }
469 }
470
471 pub fn reject(self, kind: SubmitErrorKind) -> SubmissionRejected {
472 SubmissionRejected {
473 kind,
474 submission: Some(Box::new(self)),
475 }
476 }
477}
478
479impl Drop for ExecutorSubmission {
480 fn drop(&mut self) {
481 contained_drop_option(&mut self.sink);
482 contained_drop_option(&mut self.job);
483 contained_drop_option(&mut self.start);
484 }
485}
486
487pub enum ExecutorDispatch {
488 Async(AsyncExecutorDispatch),
489 Blocking(BlockingExecutorDispatch),
490}
491
492#[derive(Clone, Copy, Debug, Eq, PartialEq)]
493pub enum BlockingDispatchClass {
494 Interruptible,
495 QuarantinedBounded,
496}
497
498#[derive(Clone, Copy, Debug, Eq, PartialEq)]
500pub enum ExecutorTerminal {
501 Completed,
502 Cancelled,
503 WorkerPanic,
504}
505
506#[derive(Clone, Copy, Debug, Eq, PartialEq)]
508pub struct ExecutorDriveReport {
509 pub terminal: ExecutorTerminal,
510 pub delivery: CompletionDelivery,
511}
512
513fn terminal_for(result: &JobResult) -> ExecutorTerminal {
514 match result {
515 Err(failure) if failure.code() == ExternalFailureCode::Cancelled => {
516 ExecutorTerminal::Cancelled
517 }
518 Err(failure) if failure.code() == ExternalFailureCode::WorkerPanic => {
519 ExecutorTerminal::WorkerPanic
520 }
521 Ok(_) | Err(_) => ExecutorTerminal::Completed,
522 }
523}
524
525pub struct BlockingExecutorDispatch {
526 identity: CompletionIdentity,
527 class: BlockingDispatchClass,
528 sink: Option<CompletionSink>,
529 start: Option<ExecutorStartToken>,
530 job: Option<BlockingJob>,
531}
532
533impl BlockingExecutorDispatch {
534 pub fn operation_id(&self) -> OperationId {
535 self.identity.operation_id
536 }
537
538 pub fn class(&self) -> BlockingDispatchClass {
539 self.class
540 }
541
542 pub fn run(mut self) -> ExecutorDriveReport {
543 let start = self.start.take().expect("dispatch owns start token");
544 let decision = start.claim_for_run();
545 contained_drop(start);
546 if decision == ExecutorStartDecision::CompleteCancelled {
547 let result = Err(ExternalFailure::cancelled());
548 let terminal = terminal_for(&result);
549 let delivery = self
550 .sink
551 .take()
552 .expect("dispatch owns sink")
553 .deliver(result);
554 if let Some(job) = self.job.take() {
555 contained_drop(job);
556 }
557 return ExecutorDriveReport { terminal, delivery };
558 }
559 let result = match decision {
560 ExecutorStartDecision::CompleteCancelled => unreachable!(),
561 ExecutorStartDecision::Run => run_blocking(self.job.take().expect("dispatch owns job")),
562 };
563 let terminal = terminal_for(&result);
564 let delivery = self
565 .sink
566 .take()
567 .expect("dispatch owns sink")
568 .deliver(result);
569 ExecutorDriveReport { terminal, delivery }
570 }
571}
572
573impl Drop for BlockingExecutorDispatch {
574 fn drop(&mut self) {
575 if let Some(sink) = self.sink.take() {
576 let _ = sink.deliver(Err(ExternalFailure::cancelled()));
577 }
578 if let Some(job) = self.job.take() {
579 contained_drop(job);
580 }
581 if let Some(start) = self.start.take() {
582 contained_drop(start);
583 }
584 }
585}
586
587fn run_blocking(job: BlockingJob) -> JobResult {
588 #[cfg(panic = "unwind")]
589 {
590 catch_unwind(AssertUnwindSafe(job)).unwrap_or_else(|_| Err(ExternalFailure::worker_panic()))
591 }
592 #[cfg(panic = "abort")]
593 {
594 job()
595 }
596}
597
598pub struct AsyncExecutorDispatch {
599 identity: CompletionIdentity,
600 sink: Option<CompletionSink>,
601 start: Option<ExecutorStartToken>,
602 job: Option<AsyncJob>,
603}
604
605impl AsyncExecutorDispatch {
606 pub fn operation_id(&self) -> OperationId {
607 self.identity.operation_id
608 }
609
610 pub fn into_future(mut self) -> AsyncDispatchFuture {
611 let start = self.start.take().expect("dispatch owns start token");
612 let decision = start.claim_for_run();
613 contained_drop(start);
614 let future = match decision {
615 ExecutorStartDecision::CompleteCancelled => AsyncFutureState::Immediate {
616 result: Some(Err(ExternalFailure::cancelled())),
617 unstarted_job: self.job.take(),
618 },
619 ExecutorStartDecision::Run => {
620 construct_async(self.job.take().expect("dispatch owns job"))
621 }
622 };
623 AsyncDispatchFuture {
624 identity: self.identity,
625 sink: self.sink.take(),
626 future,
627 }
628 }
629}
630
631impl Drop for AsyncExecutorDispatch {
632 fn drop(&mut self) {
633 if let Some(sink) = self.sink.take() {
634 let _ = sink.deliver(Err(ExternalFailure::cancelled()));
635 }
636 if let Some(job) = self.job.take() {
637 contained_drop(job);
638 }
639 if let Some(start) = self.start.take() {
640 contained_drop(start);
641 }
642 }
643}
644
645enum AsyncFutureState {
646 Running(AsyncJobFuture),
647 Immediate {
648 result: Option<JobResult>,
649 unstarted_job: Option<AsyncJob>,
650 },
651 Done,
652}
653
654fn construct_async(job: AsyncJob) -> AsyncFutureState {
655 #[cfg(panic = "unwind")]
656 {
657 match catch_unwind(AssertUnwindSafe(job)) {
658 Ok(future) => AsyncFutureState::Running(future),
659 Err(_) => AsyncFutureState::Immediate {
660 result: Some(Err(ExternalFailure::worker_panic())),
661 unstarted_job: None,
662 },
663 }
664 }
665 #[cfg(panic = "abort")]
666 {
667 AsyncFutureState::Running(job())
668 }
669}
670
671pub struct AsyncDispatchFuture {
672 identity: CompletionIdentity,
673 sink: Option<CompletionSink>,
674 future: AsyncFutureState,
675}
676
677impl AsyncDispatchFuture {
678 pub fn operation_id(&self) -> OperationId {
679 self.identity.operation_id
680 }
681}
682
683impl Future for AsyncDispatchFuture {
684 type Output = ExecutorDriveReport;
685
686 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
687 let result = match &mut self.future {
688 AsyncFutureState::Running(future) => {
689 #[cfg(panic = "unwind")]
690 let polled = catch_unwind(AssertUnwindSafe(|| future.as_mut().poll(context)));
691 #[cfg(panic = "unwind")]
692 match polled {
693 Ok(Poll::Pending) => return Poll::Pending,
694 Ok(Poll::Ready(result)) => result,
695 Err(_) => Err(ExternalFailure::worker_panic()),
696 }
697 #[cfg(panic = "abort")]
698 match future.as_mut().poll(context) {
699 Poll::Pending => return Poll::Pending,
700 Poll::Ready(result) => result,
701 }
702 }
703 AsyncFutureState::Immediate { result, .. } => {
704 result.take().expect("immediate result is polled once")
705 }
706 AsyncFutureState::Done => panic!("completed dispatch future polled again"),
707 };
708 let terminal = terminal_for(&result);
709 let delivery = self.sink.take().expect("future owns sink").deliver(result);
710 let old = std::mem::replace(&mut self.future, AsyncFutureState::Done);
711 contained_drop_async_state(old);
712 Poll::Ready(ExecutorDriveReport { terminal, delivery })
713 }
714}
715
716impl Drop for AsyncDispatchFuture {
717 fn drop(&mut self) {
718 if let Some(sink) = self.sink.take() {
719 let _ = sink.deliver(Err(ExternalFailure::cancelled()));
720 }
721 let old = std::mem::replace(&mut self.future, AsyncFutureState::Done);
722 contained_drop_async_state(old);
723 }
724}
725
726fn contained_drop_async_state(state: AsyncFutureState) {
727 #[cfg(panic = "unwind")]
728 if std::thread::panicking() {
729 std::mem::forget(state);
730 return;
731 }
732 match state {
733 AsyncFutureState::Running(future) => contained_drop(future),
734 AsyncFutureState::Immediate {
735 result,
736 unstarted_job,
737 } => {
738 contained_drop(result);
739 if let Some(job) = unstarted_job {
740 contained_drop(job);
741 }
742 }
743 AsyncFutureState::Done => {}
744 }
745}
746
747#[derive(Clone, Copy, Debug, Eq, PartialEq)]
748pub enum SubmitErrorKind {
749 Capacity,
750 ShuttingDown,
751 RuntimeDetached,
752}
753
754pub struct SubmissionRejected {
755 kind: SubmitErrorKind,
756 submission: Option<Box<ExecutorSubmission>>,
757}
758
759impl SubmissionRejected {
760 pub fn kind(&self) -> SubmitErrorKind {
761 self.kind
762 }
763
764 pub fn operation_id(&self) -> OperationId {
765 self.submission
766 .as_ref()
767 .expect("rejection owns submission")
768 .operation_id()
769 }
770
771 pub fn rollback(mut self) -> SubmitErrorKind {
772 if let Some(submission) = self.submission.take() {
773 contained_drop(submission);
774 }
775 self.kind
776 }
777}
778
779impl Drop for SubmissionRejected {
780 fn drop(&mut self) {
781 if let Some(submission) = self.submission.take() {
782 contained_drop(submission);
783 }
784 }
785}
786
787fn contained_drop<T>(value: T) {
788 #[cfg(panic = "unwind")]
789 {
790 if std::thread::panicking() {
794 std::mem::forget(value);
795 return;
796 }
797 let _ = catch_unwind(AssertUnwindSafe(|| drop(value)));
798 }
799 #[cfg(panic = "abort")]
800 {
801 drop(value);
802 }
803}
804
805fn contained_drop_option<T>(option: &mut Option<T>) {
806 if let Some(value) = option.take() {
807 contained_drop(value);
808 }
809}
810
811fn destroy_prepared(prepared: PreparedExternalOperation) {
812 drop(prepared);
813}
814
815#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
816pub struct ExecutorSnapshot {
817 pub queued: usize,
818 pub running_interruptible: usize,
819 pub running_quarantined: usize,
820 pub completed: usize,
821 pub cancelled: usize,
822 pub panicked: usize,
823 pub undeliverable: usize,
824}
825
826#[derive(Clone, Copy, Debug, Eq, PartialEq)]
827pub enum ExecutorShutdown {
828 Drained(ExecutorSnapshot),
829 DeadlineExceeded(ExecutorSnapshot),
830}
831
832#[derive(Clone, Copy, Debug, Eq, PartialEq)]
833pub struct RunningSubmission {
834 operation_id: OperationId,
835}
836impl RunningSubmission {
837 pub fn new(operation_id: OperationId) -> Self {
838 Self { operation_id }
839 }
840 pub fn operation_id(&self) -> OperationId {
841 self.operation_id
842 }
843}
844
845pub trait ExecutorLease: Send + Sync + 'static {
846 fn submit(
848 &self,
849 submission: ExecutorSubmission,
850 ) -> Result<RunningSubmission, SubmissionRejected>;
851 fn snapshot(&self) -> ExecutorSnapshot;
852 fn shutdown(&self, deadline: Instant) -> ExecutorShutdown;
853}
854
855pub trait IoExecutor: Send + Sync + 'static {
856 fn attach_runtime(
857 &self,
858 runtime_id: RuntimeId,
859 ) -> Result<Arc<dyn ExecutorLease>, ExecutorAttachError>;
860 fn snapshot(&self) -> ExecutorSnapshot;
861}
862
863#[derive(Clone, Copy, Debug, Eq, PartialEq)]
864pub enum ExecutorAttachError {
865 DuplicateRuntime { runtime_id: RuntimeId },
866 ShuttingDown,
867}
868
869#[cfg(test)]
870mod tests {
871 use std::cell::{Cell, RefCell};
872 use std::num::NonZeroU64;
873 use std::rc::Rc;
874 use std::sync::atomic::{AtomicUsize, Ordering};
875 use std::sync::Mutex;
876 use std::task::Waker;
877
878 use crate::cycle::GcEdge;
879 use crate::runtime::{
880 CancelDisposition, CancelHook, CancelHookError, CancellationView, CompletionDecoder,
881 ExternalFailureCode, NativeCallContext, TaskContextHandle, Trace,
882 };
883 use crate::Value;
884
885 use super::*;
886
887 struct RecordingSender {
888 completions: Mutex<Vec<ExternalCompletion>>,
889 attempts: AtomicUsize,
890 delivery: CompletionDelivery,
891 }
892
893 impl RecordingSender {
894 fn new(delivery: CompletionDelivery) -> Arc<Self> {
895 Arc::new(Self {
896 completions: Mutex::new(Vec::new()),
897 attempts: AtomicUsize::new(0),
898 delivery,
899 })
900 }
901
902 fn take(&self) -> ExternalCompletion {
903 self.completions.lock().unwrap().pop().unwrap()
904 }
905 }
906
907 impl CompletionSender for RecordingSender {
908 fn send(&self, completion: ExternalCompletion) -> CompletionDelivery {
909 self.attempts.fetch_add(1, Ordering::Relaxed);
910 self.completions.lock().unwrap().push(completion);
911 self.delivery
912 }
913 }
914
915 struct LocalDecoder(Rc<Cell<usize>>);
916
917 impl Trace for LocalDecoder {
918 fn trace(&self, _sink: &mut dyn FnMut(GcEdge<'_>)) -> bool {
919 true
920 }
921 }
922
923 impl CompletionDecoder for LocalDecoder {
924 fn decode(
925 self: Box<Self>,
926 _context: &mut NativeCallContext<'_>,
927 result: JobResult,
928 ) -> Result<Value, crate::SemaError> {
929 self.0.set(self.0.get() + 1);
930 result
931 .map(|_| Value::int(1))
932 .map_err(|failure| crate::SemaError::eval(failure.message()))
933 }
934 }
935
936 struct LocalHook(Rc<Cell<usize>>);
937
938 impl Trace for LocalHook {
939 fn trace(&self, _sink: &mut dyn FnMut(GcEdge<'_>)) -> bool {
940 true
941 }
942 }
943
944 impl CancelHook for LocalHook {
945 fn cancel(&mut self) -> Result<CancelDisposition, CancelHookError> {
946 self.0.set(self.0.get() + 1);
947 Ok(CancelDisposition::Reaped)
948 }
949
950 fn reap(&mut self) -> Result<CancelDisposition, CancelHookError> {
951 Ok(CancelDisposition::Reaped)
952 }
953 }
954
955 #[test]
956 fn prepared_operation_traces_decoder_then_interruptible_hook_never_job() {
957 struct EdgeDecoder(Value);
958 impl Trace for EdgeDecoder {
959 fn trace(&self, sink: &mut dyn FnMut(GcEdge<'_>)) -> bool {
960 sink(GcEdge::Value(&self.0));
961 true
962 }
963 }
964 impl CompletionDecoder for EdgeDecoder {
965 fn decode(
966 self: Box<Self>,
967 _context: &mut NativeCallContext<'_>,
968 _result: JobResult,
969 ) -> Result<Value, crate::SemaError> {
970 Ok(self.0)
971 }
972 }
973 struct EdgeHook(Value);
974 impl Trace for EdgeHook {
975 fn trace(&self, sink: &mut dyn FnMut(GcEdge<'_>)) -> bool {
976 sink(GcEdge::Value(&self.0));
977 true
978 }
979 }
980 impl CancelHook for EdgeHook {
981 fn cancel(&mut self) -> Result<CancelDisposition, CancelHookError> {
982 Ok(CancelDisposition::Reaped)
983 }
984 fn reap(&mut self) -> Result<CancelDisposition, CancelHookError> {
985 Ok(CancelDisposition::Reaped)
986 }
987 }
988
989 let value = Value::string("duplicate");
990 let prepared = PreparedExternalOperation::interruptible_blocking(
991 kind(7),
992 Box::new(EdgeDecoder(value.clone())),
993 InterruptibleResource::new("edge", Box::new(EdgeHook(value))),
994 || Ok(Box::new(1_u8)),
995 );
996 assert_eq!(prepared.completion_kind(), kind(7));
997 let mut edges = 0;
998 assert!(prepared.trace(&mut |_| edges += 1));
999 assert_eq!(edges, 2, "decoder and hook each own one duplicate edge");
1000
1001 let quarantined = PreparedExternalOperation::quarantined_blocking(
1002 kind(8),
1003 Box::new(EdgeDecoder(Value::NIL)),
1004 QuarantineBound::finite_work("unit", NonZeroU64::new(1).unwrap()),
1005 || Ok(Box::new(1_u8)),
1006 );
1007 let mut edges = 0;
1008 assert!(quarantined.trace(&mut |_| edges += 1));
1009 assert_eq!(edges, 1, "quarantined resources have no edges");
1010
1011 struct BorrowingHook {
1012 first: Value,
1013 second: Rc<RefCell<Value>>,
1014 }
1015 impl Trace for BorrowingHook {
1016 fn trace(&self, sink: &mut dyn FnMut(GcEdge<'_>)) -> bool {
1017 sink(GcEdge::Value(&self.first));
1018 match self.second.try_borrow() {
1019 Ok(second) => {
1020 sink(GcEdge::Value(&second));
1021 true
1022 }
1023 Err(_) => false,
1024 }
1025 }
1026 }
1027 impl CancelHook for BorrowingHook {
1028 fn cancel(&mut self) -> Result<CancelDisposition, CancelHookError> {
1029 Ok(CancelDisposition::Reaped)
1030 }
1031 fn reap(&mut self) -> Result<CancelDisposition, CancelHookError> {
1032 Ok(CancelDisposition::Reaped)
1033 }
1034 }
1035 let second = Rc::new(RefCell::new(Value::NIL));
1036 let borrow = second.borrow_mut();
1037 let failing = PreparedExternalOperation::interruptible_blocking(
1038 kind(9),
1039 Box::new(EdgeDecoder(Value::NIL)),
1040 InterruptibleResource::new(
1041 "borrowed",
1042 Box::new(BorrowingHook {
1043 first: Value::NIL,
1044 second: Rc::clone(&second),
1045 }),
1046 ),
1047 || Ok(Box::new(1_u8)),
1048 );
1049 let mut edges = 0;
1050 assert!(!failing.trace(&mut |_| edges += 1));
1051 assert_eq!(edges, 2, "decoder and hook output remains before failure");
1052 drop(borrow);
1053 }
1054
1055 fn interruptible() -> InterruptibleResource {
1056 InterruptibleResource::new("test", Box::new(LocalHook(Rc::new(Cell::new(0)))))
1057 }
1058
1059 fn kind(raw: u64) -> CompletionKind {
1060 CompletionKind::try_from_raw(raw).unwrap()
1061 }
1062
1063 fn decoder() -> Box<dyn CompletionDecoder> {
1064 Box::new(LocalDecoder(Rc::new(Cell::new(0))))
1065 }
1066
1067 fn registration(
1068 prepared: PreparedExternalOperation,
1069 selected_kind: CompletionKind,
1070 delivery: CompletionDelivery,
1071 ) -> (
1072 Arc<RecordingSender>,
1073 RuntimeOperationBinding,
1074 ExecutorSubmission,
1075 CompletionIdentity,
1076 ) {
1077 let sender = RecordingSender::new(delivery);
1078 let (_, registrar, _) = CompletionRegistrar::register(sender.clone()).unwrap();
1079 let identity = registrar.issue_identity(selected_kind).unwrap();
1080 let descriptor = CompletionIdentity {
1081 runtime_id: identity.runtime_id(),
1082 wait_id: identity.wait_id(),
1083 generation: identity.generation(),
1084 operation_id: identity.operation_id(),
1085 kind: identity.kind(),
1086 };
1087 let (runtime, submission) = registrar.bind(identity, prepared).unwrap().split();
1088 (sender, runtime, submission, descriptor)
1089 }
1090
1091 fn blocking(result: JobResult) -> PreparedExternalOperation {
1092 PreparedExternalOperation::interruptible_blocking(
1093 kind(1),
1094 decoder(),
1095 interruptible(),
1096 move || result,
1097 )
1098 }
1099
1100 fn poll_once(future: &mut AsyncDispatchFuture) -> Poll<ExecutorDriveReport> {
1101 let mut context = Context::from_waker(Waker::noop());
1102 Pin::new(future).poll(&mut context)
1103 }
1104
1105 #[test]
1106 fn send_halves_are_send_while_decoder_and_resource_accept_rc() {
1107 fn assert_send<T: Send>() {}
1108 assert_send::<ExternalCompletion>();
1109 assert_send::<ExecutorSubmission>();
1110 assert_send::<ExecutorDispatch>();
1111 assert_send::<AsyncExecutorDispatch>();
1112 assert_send::<BlockingExecutorDispatch>();
1113 assert_send::<AsyncDispatchFuture>();
1114
1115 let local = Rc::new(Cell::new(0));
1116 let prepared = PreparedExternalOperation::interruptible_blocking(
1117 kind(1),
1118 Box::new(LocalDecoder(Rc::clone(&local))),
1119 InterruptibleResource::new("local", Box::new(LocalHook(Rc::clone(&local)))),
1120 || Ok(Box::new(1_u8)),
1121 );
1122 let (_sender, runtime, _submission, _) =
1123 registration(prepared, kind(1), CompletionDelivery::Delivered);
1124 drop(runtime);
1125 }
1126
1127 #[test]
1128 fn resource_wrapper_cancels_once_and_reaps_repeatedly() {
1129 struct CountingHook {
1130 cancel: Rc<Cell<usize>>,
1131 reap: Rc<Cell<usize>>,
1132 }
1133 impl Trace for CountingHook {
1134 fn trace(&self, _sink: &mut dyn FnMut(GcEdge<'_>)) -> bool {
1135 true
1136 }
1137 }
1138 impl CancelHook for CountingHook {
1139 fn cancel(&mut self) -> Result<CancelDisposition, CancelHookError> {
1140 self.cancel.set(self.cancel.get() + 1);
1141 Ok(CancelDisposition::PendingReap)
1142 }
1143 fn reap(&mut self) -> Result<CancelDisposition, CancelHookError> {
1144 self.reap.set(self.reap.get() + 1);
1145 Ok(CancelDisposition::PendingReap)
1146 }
1147 }
1148 let cancel = Rc::new(Cell::new(0));
1149 let reap = Rc::new(Cell::new(0));
1150 let mut resource = ResourceClass::interruptible(
1151 "socket",
1152 Box::new(CountingHook {
1153 cancel: Rc::clone(&cancel),
1154 reap: Rc::clone(&reap),
1155 }),
1156 );
1157 assert_eq!(resource.kind(), "socket");
1158 assert_eq!(resource.bound(), None);
1159 assert!(resource.cancel().unwrap().is_ok());
1160 assert!(resource.cancel().is_none());
1161 assert!(resource.reap().unwrap().is_ok());
1162 assert!(resource.reap().unwrap().is_ok());
1163 assert_eq!(cancel.get(), 1);
1164 assert_eq!(reap.get(), 2);
1165
1166 let bound = QuarantineBound::finite_work("items", std::num::NonZeroU64::new(2).unwrap());
1167 let descriptor = bound.descriptor();
1168 let mut quarantined = ResourceClass::quarantined(bound);
1169 assert_eq!(quarantined.bound(), Some(descriptor));
1170 assert!(quarantined.cancel().is_none());
1171 assert!(quarantined.reap().is_none());
1172 }
1173
1174 #[test]
1175 fn decoder_consumes_typed_result_on_runtime_thread() {
1176 let count = Rc::new(Cell::new(0));
1177 let decoder: Box<dyn CompletionDecoder> = Box::new(LocalDecoder(Rc::clone(&count)));
1178 let eval_context = crate::EvalContext::new();
1179 let task_context = TaskContextHandle::default();
1180 let mut context = NativeCallContext {
1181 hof_host: None,
1182 eval_context: &eval_context,
1183 task_context,
1184 call_env: None,
1185 cancellation: CancellationView::default(),
1186 };
1187 assert_eq!(
1188 decoder.decode(&mut context, Ok(Box::new(1_u8))).unwrap(),
1189 Value::int(1)
1190 );
1191 assert_eq!(count.get(), 1);
1192 }
1193
1194 #[test]
1195 fn all_prepared_constructor_classes_bind() {
1196 let bound = QuarantineBound::finite_work("items", std::num::NonZeroU64::new(1).unwrap());
1197 let prepared = [
1198 PreparedExternalOperation::interruptible_async(
1199 kind(1),
1200 decoder(),
1201 interruptible(),
1202 || async { Ok(Box::new(1_u8) as SendPayload) },
1203 ),
1204 PreparedExternalOperation::interruptible_blocking(
1205 kind(1),
1206 decoder(),
1207 interruptible(),
1208 || Ok(Box::new(1_u8)),
1209 ),
1210 PreparedExternalOperation::quarantined_blocking(kind(1), decoder(), bound, || {
1211 Ok(Box::new(1_u8))
1212 }),
1213 ];
1214 for operation in prepared {
1215 let declared_kind = operation.kind;
1216 let (_sender, runtime, _submission, _) =
1217 registration(operation, declared_kind, CompletionDelivery::Delivered);
1218 let (_, resource, _) = runtime.into_parts();
1219 assert!(resource.kind() == "test" || resource.bound().is_some());
1220 }
1221 }
1222
1223 #[test]
1224 fn fresh_registrars_reject_foreign_identity() {
1225 let sender = RecordingSender::new(CompletionDelivery::Delivered);
1226 let (first_id, first, _) = CompletionRegistrar::register(sender.clone()).unwrap();
1227 let (second_id, second, _) = CompletionRegistrar::register(sender).unwrap();
1228 assert_ne!(first_id, second_id);
1229 let identity = first.issue_identity(kind(1)).unwrap();
1230 assert!(second.bind(identity, blocking(Ok(Box::new(1_u8)))).is_err());
1231 }
1232
1233 #[test]
1234 fn external_and_internal_wait_identities_share_the_registrar_allocator() {
1235 let sender = RecordingSender::new(CompletionDelivery::Delivered);
1236 let (_, registrar, _) = CompletionRegistrar::register(sender).unwrap();
1237
1238 let internal = registrar.issue_wait_identity().unwrap();
1239 let external = registrar.issue_identity(kind(1)).unwrap();
1240
1241 assert_ne!(internal.0, external.wait_id());
1242 assert_ne!(internal.1, external.generation());
1243 }
1244
1245 #[test]
1246 fn registrar_rejects_identity_kind_mismatch() {
1247 let sender = RecordingSender::new(CompletionDelivery::Delivered);
1248 let (_, registrar, _) = CompletionRegistrar::register(sender.clone()).unwrap();
1249 let identity = registrar.issue_identity(kind(7)).unwrap();
1250 let error = match registrar.bind(identity, blocking(Ok(Box::new(1_u8)))) {
1251 Err(error) => error,
1252 Ok(_) => panic!("mismatched completion kinds must be rejected"),
1253 };
1254 assert_eq!(error.expected(), kind(7));
1255 assert_eq!(error.declared(), kind(1));
1256 assert_eq!(sender.attempts.load(Ordering::Relaxed), 0);
1257 }
1258
1259 #[test]
1260 fn matching_identity_kind_delivers_declared_kind() {
1261 let (sender, _runtime, submission, identity) = registration(
1262 blocking(Ok(Box::new(1_u8))),
1263 kind(1),
1264 CompletionDelivery::Delivered,
1265 );
1266 let ExecutorDispatch::Blocking(dispatch) = submission.into_dispatch() else {
1267 panic!("blocking dispatch expected")
1268 };
1269 dispatch.run();
1270 let completion = sender.take();
1271 assert_eq!(completion.runtime_id, identity.runtime_id);
1272 assert_eq!(completion.wait_id, identity.wait_id);
1273 assert_eq!(completion.generation, identity.generation);
1274 assert_eq!(completion.operation_id, identity.operation_id);
1275 assert_eq!(completion.kind, kind(1));
1276 }
1277
1278 #[test]
1279 fn identity_is_consumed_by_bind() {
1280 fn bind_once(
1281 registrar: &CompletionRegistrar,
1282 identity: RuntimeIssuedCompletionIdentity,
1283 prepared: PreparedExternalOperation,
1284 ) {
1285 let _ = registrar.bind(identity, prepared);
1286 }
1288
1289 let sender = RecordingSender::new(CompletionDelivery::Delivered);
1290 let (_, registrar, _) = CompletionRegistrar::register(sender).unwrap();
1291 bind_once(
1292 ®istrar,
1293 registrar.issue_identity(kind(1)).unwrap(),
1294 blocking(Ok(Box::new(1_u8))),
1295 );
1296 }
1297
1298 #[test]
1299 fn rejection_is_silent_and_destroys_job_internally() {
1300 let dropped = Arc::new(AtomicUsize::new(0));
1301 struct DropCount(Arc<AtomicUsize>);
1302 impl Drop for DropCount {
1303 fn drop(&mut self) {
1304 self.0.fetch_add(1, Ordering::Relaxed);
1305 }
1306 }
1307 let captured = DropCount(Arc::clone(&dropped));
1308 let prepared = PreparedExternalOperation::interruptible_blocking(
1309 kind(1),
1310 decoder(),
1311 interruptible(),
1312 move || {
1313 drop(captured);
1314 Ok(Box::new(1_u8))
1315 },
1316 );
1317 let (sender, _runtime, submission, identity) =
1318 registration(prepared, kind(1), CompletionDelivery::Delivered);
1319 let rejection = submission.reject(SubmitErrorKind::Capacity);
1320 assert_eq!(rejection.operation_id(), identity.operation_id);
1321 assert_eq!(rejection.rollback(), SubmitErrorKind::Capacity);
1322 assert_eq!(sender.attempts.load(Ordering::Relaxed), 0);
1323 assert_eq!(dropped.load(Ordering::Relaxed), 1);
1324 }
1325
1326 #[test]
1327 #[cfg(panic = "unwind")]
1328 fn unadmitted_owner_abandonment_contains_hostile_destructors() {
1329 struct HostileDrop;
1330 impl Drop for HostileDrop {
1331 fn drop(&mut self) {
1332 panic!("hostile unadmitted owner drop");
1333 }
1334 }
1335
1336 fn hostile_prepared() -> PreparedExternalOperation {
1337 let hostile = HostileDrop;
1338 PreparedExternalOperation::interruptible_blocking(
1339 kind(1),
1340 decoder(),
1341 interruptible(),
1342 move || {
1343 drop(hostile);
1344 Ok(Box::new(1_u8))
1345 },
1346 )
1347 }
1348
1349 assert!(catch_unwind(AssertUnwindSafe(|| drop(hostile_prepared()))).is_ok());
1350
1351 let (sender, runtime, submission, _) =
1352 registration(hostile_prepared(), kind(1), CompletionDelivery::Delivered);
1353 assert!(catch_unwind(AssertUnwindSafe(|| drop(submission))).is_ok());
1354 assert_eq!(sender.attempts.load(Ordering::Relaxed), 0);
1355 drop(runtime);
1356
1357 let (sender, runtime, submission, _) =
1358 registration(hostile_prepared(), kind(1), CompletionDelivery::Delivered);
1359 let rejection = submission.reject(SubmitErrorKind::Capacity);
1360 assert!(catch_unwind(AssertUnwindSafe(|| drop(rejection))).is_ok());
1361 assert_eq!(sender.attempts.load(Ordering::Relaxed), 0);
1362 drop(runtime);
1363
1364 let sender = RecordingSender::new(CompletionDelivery::Delivered);
1365 let (_, registrar, _) = CompletionRegistrar::register(sender.clone()).unwrap();
1366 let identity = registrar.issue_identity(kind(1)).unwrap();
1367 let binding = registrar.bind(identity, hostile_prepared()).unwrap();
1368 assert!(catch_unwind(AssertUnwindSafe(|| drop(binding))).is_ok());
1369 assert_eq!(sender.attempts.load(Ordering::Relaxed), 0);
1370
1371 struct HostileDecoder;
1372 impl Trace for HostileDecoder {
1373 fn trace(&self, _sink: &mut dyn FnMut(GcEdge<'_>)) -> bool {
1374 true
1375 }
1376 }
1377 impl CompletionDecoder for HostileDecoder {
1378 fn decode(
1379 self: Box<Self>,
1380 _context: &mut NativeCallContext<'_>,
1381 _result: JobResult,
1382 ) -> Result<Value, crate::SemaError> {
1383 Ok(Value::NIL)
1384 }
1385 }
1386 impl Drop for HostileDecoder {
1387 fn drop(&mut self) {
1388 panic!("hostile runtime decoder drop");
1389 }
1390 }
1391 let prepared = PreparedExternalOperation::interruptible_blocking(
1392 kind(1),
1393 Box::new(HostileDecoder),
1394 interruptible(),
1395 || Ok(Box::new(1_u8)),
1396 );
1397 let (_sender, runtime, submission, _) =
1398 registration(prepared, kind(1), CompletionDelivery::Delivered);
1399 assert!(catch_unwind(AssertUnwindSafe(|| drop(runtime))).is_ok());
1400 drop(submission.reject(SubmitErrorKind::Capacity));
1401 }
1402
1403 #[test]
1404 #[cfg(panic = "unwind")]
1405 fn unadmitted_owner_abandonment_during_unwind_leaks_opaque_owners() {
1406 struct HostileDrop;
1407 impl Drop for HostileDrop {
1408 fn drop(&mut self) {
1409 panic!("hostile nested drop");
1410 }
1411 }
1412 struct DropDuringUnwind<T>(Option<T>);
1413 impl<T> Drop for DropDuringUnwind<T> {
1414 fn drop(&mut self) {
1415 drop(self.0.take());
1416 }
1417 }
1418 fn hostile_prepared() -> PreparedExternalOperation {
1419 let hostile = HostileDrop;
1420 PreparedExternalOperation::interruptible_blocking(
1421 kind(1),
1422 decoder(),
1423 interruptible(),
1424 move || {
1425 drop(hostile);
1426 Ok(Box::new(1_u8))
1427 },
1428 )
1429 }
1430
1431 let sender = RecordingSender::new(CompletionDelivery::Delivered);
1432 let (_, registrar, _) = CompletionRegistrar::register(sender.clone()).unwrap();
1433 let result = catch_unwind(AssertUnwindSafe(|| {
1434 let identity = registrar.issue_identity(kind(1)).unwrap();
1435 let binding = registrar.bind(identity, hostile_prepared()).unwrap();
1436 let (runtime, submission) = binding.split();
1437 let identity = registrar.issue_identity(kind(1)).unwrap();
1438 let rejection = registrar
1439 .bind(identity, hostile_prepared())
1440 .unwrap()
1441 .split()
1442 .1
1443 .reject(SubmitErrorKind::Capacity);
1444 let identity = registrar.issue_identity(kind(1)).unwrap();
1445 let direct_submission = registrar
1446 .bind(identity, hostile_prepared())
1447 .unwrap()
1448 .split()
1449 .1;
1450 let _prepared_guard = DropDuringUnwind(Some(hostile_prepared()));
1451 let _runtime_guard = DropDuringUnwind(Some(runtime));
1452 let _submission_guard = DropDuringUnwind(Some(submission));
1453 let _rejection_guard = DropDuringUnwind(Some(rejection));
1454 let identity = registrar.issue_identity(kind(1)).unwrap();
1455 let binding = registrar.bind(identity, hostile_prepared()).unwrap();
1456 let _binding_guard = DropDuringUnwind(Some(binding));
1457 let _direct_submission_guard = DropDuringUnwind(Some(direct_submission));
1458 panic!("outer unwind");
1459 }));
1460 assert!(result.is_err());
1461 assert_eq!(sender.attempts.load(Ordering::Relaxed), 0);
1462 }
1463
1464 #[test]
1465 fn cancel_before_start_prevents_blocking_body() {
1466 let ran = Arc::new(AtomicUsize::new(0));
1467 let ran_job = Arc::clone(&ran);
1468 let prepared = PreparedExternalOperation::interruptible_blocking(
1469 kind(1),
1470 decoder(),
1471 interruptible(),
1472 move || {
1473 ran_job.fetch_add(1, Ordering::Relaxed);
1474 Ok(Box::new(1_u8))
1475 },
1476 );
1477 let (sender, runtime, submission, _) =
1478 registration(prepared, kind(1), CompletionDelivery::Delivered);
1479 let (_, _, cancel) = runtime.into_parts();
1480 assert_eq!(
1481 cancel.cancel_before_start(),
1482 CancelBeforeStart::CancelledQueued
1483 );
1484 let ExecutorDispatch::Blocking(dispatch) = submission.into_dispatch() else {
1485 unreachable!()
1486 };
1487 dispatch.run();
1488 assert_eq!(ran.load(Ordering::Relaxed), 0);
1489 assert_eq!(
1490 sender.take().result.unwrap_err().code(),
1491 ExternalFailureCode::Cancelled
1492 );
1493 }
1494
1495 #[test]
1496 fn start_before_cancel_runs_body() {
1497 let (sender, runtime, submission, _) = registration(
1498 blocking(Ok(Box::new(4_u8))),
1499 kind(1),
1500 CompletionDelivery::Delivered,
1501 );
1502 let (_, _, cancel) = runtime.into_parts();
1503 let ExecutorDispatch::Blocking(dispatch) = submission.into_dispatch() else {
1504 unreachable!()
1505 };
1506 assert_eq!(dispatch.run().delivery, CompletionDelivery::Delivered);
1507 assert_eq!(
1508 cancel.cancel_before_start(),
1509 CancelBeforeStart::AlreadyRunning
1510 );
1511 assert!(sender.take().result.is_ok());
1512 }
1513
1514 #[test]
1515 fn blocking_return_error_and_panic_each_deliver_once() {
1516 let cases = [
1517 blocking(Ok(Box::new(1_u8))),
1518 blocking(Err(ExternalFailure::bound_exceeded("bound"))),
1519 blocking_panic(),
1520 ];
1521 for (index, prepared) in cases.into_iter().enumerate() {
1522 let (sender, _runtime, submission, _) =
1523 registration(prepared, kind(1), CompletionDelivery::Delivered);
1524 let ExecutorDispatch::Blocking(dispatch) = submission.into_dispatch() else {
1525 unreachable!()
1526 };
1527 dispatch.run();
1528 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1, "case {index}");
1529 }
1530 }
1531
1532 #[cfg(panic = "unwind")]
1533 fn blocking_panic() -> PreparedExternalOperation {
1534 PreparedExternalOperation::interruptible_blocking(
1535 kind(1),
1536 decoder(),
1537 interruptible(),
1538 || panic!("worker panic"),
1539 )
1540 }
1541
1542 #[cfg(panic = "abort")]
1543 fn blocking_panic() -> PreparedExternalOperation {
1544 blocking(Err(ExternalFailure::worker_panic()))
1545 }
1546
1547 #[test]
1548 fn dispatch_drop_is_post_arm_cancellation_not_rejection() {
1549 let (sender, _runtime, submission, _) = registration(
1550 blocking(Ok(Box::new(1_u8))),
1551 kind(1),
1552 CompletionDelivery::Delivered,
1553 );
1554 drop(submission.into_dispatch());
1555 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
1556 assert_eq!(
1557 sender.take().result.unwrap_err().code(),
1558 ExternalFailureCode::Cancelled
1559 );
1560 }
1561
1562 #[test]
1563 fn async_return_and_construction_panic_deliver_once() {
1564 let normal = PreparedExternalOperation::interruptible_async(
1565 kind(1),
1566 decoder(),
1567 interruptible(),
1568 || async { Ok(Box::new(1_u8) as SendPayload) },
1569 );
1570 let returned_error = PreparedExternalOperation::interruptible_async(
1571 kind(1),
1572 decoder(),
1573 interruptible(),
1574 || async { Err(ExternalFailure::deadline_exceeded("deadline")) },
1575 );
1576 #[cfg(panic = "unwind")]
1577 let panics = PreparedExternalOperation::interruptible_async(
1578 kind(1),
1579 decoder(),
1580 interruptible(),
1581 || -> std::future::Ready<JobResult> { panic!("construction panic") },
1582 );
1583 #[cfg(panic = "abort")]
1584 let panics = PreparedExternalOperation::interruptible_async(
1585 kind(1),
1586 decoder(),
1587 interruptible(),
1588 || std::future::ready(Err(ExternalFailure::worker_panic())),
1589 );
1590
1591 for prepared in [normal, returned_error, panics] {
1592 let (sender, _runtime, submission, _) =
1593 registration(prepared, kind(1), CompletionDelivery::Delivered);
1594 let ExecutorDispatch::Async(dispatch) = submission.into_dispatch() else {
1595 unreachable!()
1596 };
1597 let mut future = dispatch.into_future();
1598 let Poll::Ready(report) = poll_once(&mut future) else {
1599 panic!("fixture is immediately ready")
1600 };
1601 assert_eq!(report.delivery, CompletionDelivery::Delivered);
1602 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
1603 }
1604 }
1605
1606 #[test]
1607 fn async_queued_cancellation_does_not_construct_future() {
1608 let constructed = Arc::new(AtomicUsize::new(0));
1609 let job_constructed = Arc::clone(&constructed);
1610 let prepared = PreparedExternalOperation::interruptible_async(
1611 kind(1),
1612 decoder(),
1613 interruptible(),
1614 move || {
1615 job_constructed.fetch_add(1, Ordering::Relaxed);
1616 std::future::ready(Ok(Box::new(1_u8) as SendPayload))
1617 },
1618 );
1619 let (sender, runtime, submission, _) =
1620 registration(prepared, kind(1), CompletionDelivery::Delivered);
1621 let (_, _, cancel) = runtime.into_parts();
1622 assert_eq!(
1623 cancel.cancel_before_start(),
1624 CancelBeforeStart::CancelledQueued
1625 );
1626 let ExecutorDispatch::Async(dispatch) = submission.into_dispatch() else {
1627 unreachable!()
1628 };
1629 let mut future = dispatch.into_future();
1630 assert!(poll_once(&mut future).is_ready());
1631 assert_eq!(constructed.load(Ordering::Relaxed), 0);
1632 assert_eq!(
1633 sender.take().result.unwrap_err().code(),
1634 ExternalFailureCode::Cancelled
1635 );
1636 }
1637
1638 #[test]
1639 #[cfg(panic = "unwind")]
1640 fn async_poll_panic_maps_worker_panic_once() {
1641 let prepared = PreparedExternalOperation::interruptible_async(
1642 kind(1),
1643 decoder(),
1644 interruptible(),
1645 || async {
1646 panic!("poll panic");
1647 #[allow(unreachable_code)]
1648 Ok(Box::new(1_u8) as SendPayload)
1649 },
1650 );
1651 let (sender, _runtime, submission, _) =
1652 registration(prepared, kind(1), CompletionDelivery::Delivered);
1653 let ExecutorDispatch::Async(dispatch) = submission.into_dispatch() else {
1654 unreachable!()
1655 };
1656 let mut future = dispatch.into_future();
1657 assert!(matches!(poll_once(&mut future), Poll::Ready(_)));
1658 assert_eq!(
1659 sender.take().result.unwrap_err().code(),
1660 ExternalFailureCode::WorkerPanic
1661 );
1662 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
1663 }
1664
1665 #[test]
1666 fn async_future_drop_before_and_after_pending_delivers_once() {
1667 let retained_waker = Arc::new(Mutex::new(None::<Waker>));
1668 let waker_slot = Arc::clone(&retained_waker);
1669 let pending = PreparedExternalOperation::interruptible_async(
1670 kind(1),
1671 decoder(),
1672 interruptible(),
1673 move || {
1674 std::future::poll_fn(move |cx| {
1675 *waker_slot.lock().unwrap() = Some(cx.waker().clone());
1676 Poll::Pending
1677 })
1678 },
1679 );
1680 let (sender, _runtime, submission, _) =
1681 registration(pending, kind(1), CompletionDelivery::Delivered);
1682 let ExecutorDispatch::Async(dispatch) = submission.into_dispatch() else {
1683 unreachable!()
1684 };
1685 let mut future = dispatch.into_future();
1686 assert!(poll_once(&mut future).is_pending());
1687 drop(future);
1688 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
1689
1690 let ready = PreparedExternalOperation::interruptible_async(
1691 kind(1),
1692 decoder(),
1693 interruptible(),
1694 || async { Ok(Box::new(1_u8) as SendPayload) },
1695 );
1696 let (sender, _runtime, submission, _) =
1697 registration(ready, kind(1), CompletionDelivery::Delivered);
1698 let ExecutorDispatch::Async(dispatch) = submission.into_dispatch() else {
1699 unreachable!()
1700 };
1701 drop(dispatch.into_future());
1702 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
1703 }
1704
1705 #[test]
1706 fn closed_inbox_is_accounted_by_sender_once() {
1707 let (sender, _runtime, submission, _) = registration(
1708 blocking(Ok(Box::new(1_u8))),
1709 kind(1),
1710 CompletionDelivery::InboxClosed,
1711 );
1712 let ExecutorDispatch::Blocking(dispatch) = submission.into_dispatch() else {
1713 unreachable!()
1714 };
1715 assert_eq!(dispatch.run().delivery, CompletionDelivery::InboxClosed);
1716 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
1717 }
1718
1719 #[test]
1720 fn queue_control_has_deterministic_cas_outcomes() {
1721 let state = Arc::new(AtomicU8::new(QUEUED));
1722 let cancel = ExecutorCancelHandle {
1723 state: Arc::clone(&state),
1724 };
1725 let start = ExecutorStartToken { state };
1726 assert_eq!(
1727 cancel.cancel_before_start(),
1728 CancelBeforeStart::CancelledQueued
1729 );
1730 assert_eq!(
1731 start.claim_for_run(),
1732 ExecutorStartDecision::CompleteCancelled
1733 );
1734
1735 let state = Arc::new(AtomicU8::new(QUEUED));
1736 let cancel = ExecutorCancelHandle {
1737 state: Arc::clone(&state),
1738 };
1739 let start = ExecutorStartToken { state };
1740 assert_eq!(start.claim_for_run(), ExecutorStartDecision::Run);
1741 assert_eq!(
1742 cancel.cancel_before_start(),
1743 CancelBeforeStart::AlreadyRunning
1744 );
1745 }
1746
1747 #[test]
1748 fn runtime_binding_decoder_can_be_invoked_after_split() {
1749 let count = Rc::new(Cell::new(0));
1750 let prepared = PreparedExternalOperation::interruptible_blocking(
1751 kind(1),
1752 Box::new(LocalDecoder(Rc::clone(&count))),
1753 interruptible(),
1754 || Ok(Box::new(1_u8)),
1755 );
1756 let (_sender, runtime, _submission, _) =
1757 registration(prepared, kind(1), CompletionDelivery::Delivered);
1758 let (decoder, _, _) = runtime.into_parts();
1759 let eval_context = crate::EvalContext::new();
1760 let task = TaskContextHandle::default();
1761 let mut context = NativeCallContext {
1762 hof_host: None,
1763 eval_context: &eval_context,
1764 task_context: task,
1765 call_env: None,
1766 cancellation: CancellationView::default(),
1767 };
1768 decoder.decode(&mut context, Ok(Box::new(1_u8))).unwrap();
1769 assert_eq!(count.get(), 1);
1770 }
1771
1772 #[test]
1773 fn no_duplicate_after_async_ready() {
1774 let prepared = PreparedExternalOperation::interruptible_async(
1775 kind(1),
1776 decoder(),
1777 interruptible(),
1778 || async { Ok(Box::new(1_u8) as SendPayload) },
1779 );
1780 let (sender, _runtime, submission, _) =
1781 registration(prepared, kind(1), CompletionDelivery::Delivered);
1782 let ExecutorDispatch::Async(dispatch) = submission.into_dispatch() else {
1783 unreachable!()
1784 };
1785 let mut future = dispatch.into_future();
1786 assert!(poll_once(&mut future).is_ready());
1787 drop(future);
1788 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
1789 }
1790
1791 #[test]
1792 #[cfg(panic = "unwind")]
1793 fn ready_result_is_delivered_before_panicking_future_drop() {
1794 struct HostileFuture;
1795 impl Future for HostileFuture {
1796 type Output = JobResult;
1797 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
1798 Poll::Ready(Ok(Box::new(1_u8)))
1799 }
1800 }
1801 impl Drop for HostileFuture {
1802 fn drop(&mut self) {
1803 panic!("hostile future drop");
1804 }
1805 }
1806
1807 let prepared = PreparedExternalOperation::interruptible_async(
1808 kind(1),
1809 decoder(),
1810 interruptible(),
1811 || HostileFuture,
1812 );
1813 let (sender, _runtime, submission, _) =
1814 registration(prepared, kind(1), CompletionDelivery::Delivered);
1815 let ExecutorDispatch::Async(dispatch) = submission.into_dispatch() else {
1816 unreachable!()
1817 };
1818 let mut future = dispatch.into_future();
1819 assert!(catch_unwind(AssertUnwindSafe(|| poll_once(&mut future))).is_ok());
1820 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
1821 assert!(sender.take().result.is_ok());
1822 }
1823
1824 #[test]
1825 #[cfg(panic = "unwind")]
1826 fn poll_panic_is_delivered_before_panicking_future_drop() {
1827 struct HostileFuture;
1828 impl Future for HostileFuture {
1829 type Output = JobResult;
1830 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
1831 panic!("hostile poll");
1832 }
1833 }
1834 impl Drop for HostileFuture {
1835 fn drop(&mut self) {
1836 panic!("hostile future drop");
1837 }
1838 }
1839
1840 let prepared = PreparedExternalOperation::interruptible_async(
1841 kind(1),
1842 decoder(),
1843 interruptible(),
1844 || HostileFuture,
1845 );
1846 let (sender, _runtime, submission, _) =
1847 registration(prepared, kind(1), CompletionDelivery::Delivered);
1848 let ExecutorDispatch::Async(dispatch) = submission.into_dispatch() else {
1849 unreachable!()
1850 };
1851 let mut future = dispatch.into_future();
1852 assert!(catch_unwind(AssertUnwindSafe(|| poll_once(&mut future))).is_ok());
1853 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
1854 assert_eq!(
1855 sender.take().result.unwrap_err().code(),
1856 ExternalFailureCode::WorkerPanic
1857 );
1858 }
1859
1860 #[test]
1861 #[cfg(panic = "unwind")]
1862 fn pending_abandonment_delivers_before_containing_future_drop_panic() {
1863 struct HostilePending;
1864 impl Future for HostilePending {
1865 type Output = JobResult;
1866 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
1867 Poll::Pending
1868 }
1869 }
1870 impl Drop for HostilePending {
1871 fn drop(&mut self) {
1872 panic!("hostile pending drop");
1873 }
1874 }
1875 let prepared = PreparedExternalOperation::interruptible_async(
1876 kind(1),
1877 decoder(),
1878 interruptible(),
1879 || HostilePending,
1880 );
1881 let (sender, _runtime, submission, _) =
1882 registration(prepared, kind(1), CompletionDelivery::Delivered);
1883 let ExecutorDispatch::Async(dispatch) = submission.into_dispatch() else {
1884 unreachable!()
1885 };
1886 let mut future = dispatch.into_future();
1887 assert!(poll_once(&mut future).is_pending());
1888 assert!(catch_unwind(AssertUnwindSafe(|| drop(future))).is_ok());
1889 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
1890 assert_eq!(
1891 sender.take().result.unwrap_err().code(),
1892 ExternalFailureCode::Cancelled
1893 );
1894 }
1895
1896 #[test]
1897 #[cfg(panic = "unwind")]
1898 fn queued_blocking_cancellation_contains_job_drop_panic() {
1899 struct HostileDrop;
1900 impl Drop for HostileDrop {
1901 fn drop(&mut self) {
1902 panic!("hostile job drop");
1903 }
1904 }
1905 let hostile = HostileDrop;
1906 let prepared = PreparedExternalOperation::interruptible_blocking(
1907 kind(1),
1908 decoder(),
1909 interruptible(),
1910 move || {
1911 drop(hostile);
1912 Ok(Box::new(1_u8))
1913 },
1914 );
1915 let (sender, runtime, submission, _) =
1916 registration(prepared, kind(1), CompletionDelivery::Delivered);
1917 let (_, _, cancel) = runtime.into_parts();
1918 assert_eq!(
1919 cancel.cancel_before_start(),
1920 CancelBeforeStart::CancelledQueued
1921 );
1922 let ExecutorDispatch::Blocking(dispatch) = submission.into_dispatch() else {
1923 unreachable!()
1924 };
1925 assert!(catch_unwind(AssertUnwindSafe(|| dispatch.run())).is_ok());
1926 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
1927 assert_eq!(
1928 sender.take().result.unwrap_err().code(),
1929 ExternalFailureCode::Cancelled
1930 );
1931 }
1932
1933 #[test]
1934 #[cfg(panic = "unwind")]
1935 fn rejection_rollback_contains_owned_destructor_panic() {
1936 struct HostileDrop;
1937 impl Drop for HostileDrop {
1938 fn drop(&mut self) {
1939 panic!("hostile rollback drop");
1940 }
1941 }
1942 let hostile = HostileDrop;
1943 let prepared = PreparedExternalOperation::interruptible_blocking(
1944 kind(1),
1945 decoder(),
1946 interruptible(),
1947 move || {
1948 drop(hostile);
1949 Ok(Box::new(1_u8))
1950 },
1951 );
1952 let (sender, _runtime, submission, _) =
1953 registration(prepared, kind(1), CompletionDelivery::Delivered);
1954 let result = catch_unwind(AssertUnwindSafe(|| {
1955 submission.reject(SubmitErrorKind::Capacity).rollback()
1956 }));
1957 assert_eq!(result.unwrap(), SubmitErrorKind::Capacity);
1958 assert_eq!(sender.attempts.load(Ordering::Relaxed), 0);
1959 }
1960
1961 #[test]
1962 #[cfg(panic = "unwind")]
1963 fn queued_async_cancellation_delivers_before_containing_job_drop_panic() {
1964 struct HostileDrop;
1965 impl Drop for HostileDrop {
1966 fn drop(&mut self) {
1967 panic!("hostile queued async job drop");
1968 }
1969 }
1970 let hostile = HostileDrop;
1971 let prepared = PreparedExternalOperation::interruptible_async(
1972 kind(1),
1973 decoder(),
1974 interruptible(),
1975 move || {
1976 drop(hostile);
1977 std::future::ready(Ok(Box::new(1_u8) as SendPayload))
1978 },
1979 );
1980 let (sender, runtime, submission, _) =
1981 registration(prepared, kind(1), CompletionDelivery::Delivered);
1982 let (_, _, cancel) = runtime.into_parts();
1983 assert_eq!(
1984 cancel.cancel_before_start(),
1985 CancelBeforeStart::CancelledQueued
1986 );
1987 let ExecutorDispatch::Async(dispatch) = submission.into_dispatch() else {
1988 unreachable!()
1989 };
1990 let mut future = dispatch.into_future();
1991 let report = catch_unwind(AssertUnwindSafe(|| poll_once(&mut future)))
1992 .expect("single destructor panic is contained");
1993 assert_eq!(
1994 report,
1995 Poll::Ready(ExecutorDriveReport {
1996 terminal: ExecutorTerminal::Cancelled,
1997 delivery: CompletionDelivery::Delivered,
1998 })
1999 );
2000 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
2001
2002 let hostile = HostileDrop;
2003 let prepared = PreparedExternalOperation::interruptible_async(
2004 kind(1),
2005 decoder(),
2006 interruptible(),
2007 move || {
2008 drop(hostile);
2009 std::future::ready(Ok(Box::new(1_u8) as SendPayload))
2010 },
2011 );
2012 let (sender, runtime, submission, _) =
2013 registration(prepared, kind(1), CompletionDelivery::Delivered);
2014 let (_, _, cancel) = runtime.into_parts();
2015 assert_eq!(
2016 cancel.cancel_before_start(),
2017 CancelBeforeStart::CancelledQueued
2018 );
2019 let ExecutorDispatch::Async(dispatch) = submission.into_dispatch() else {
2020 unreachable!()
2021 };
2022 let future = dispatch.into_future();
2023 assert!(catch_unwind(AssertUnwindSafe(|| drop(future))).is_ok());
2024 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
2025 assert_eq!(
2026 sender.take().result.unwrap_err().code(),
2027 ExternalFailureCode::Cancelled
2028 );
2029 }
2030
2031 #[test]
2032 #[cfg(panic = "unwind")]
2033 fn armed_dispatch_drop_delivers_before_containing_job_drop_panic() {
2034 struct HostileDrop;
2035 impl Drop for HostileDrop {
2036 fn drop(&mut self) {
2037 panic!("hostile armed job drop");
2038 }
2039 }
2040 for asynchronous in [false, true] {
2041 let hostile = HostileDrop;
2042 let prepared = if asynchronous {
2043 PreparedExternalOperation::interruptible_async(
2044 kind(1),
2045 decoder(),
2046 interruptible(),
2047 move || {
2048 drop(hostile);
2049 std::future::ready(Ok(Box::new(1_u8) as SendPayload))
2050 },
2051 )
2052 } else {
2053 PreparedExternalOperation::interruptible_blocking(
2054 kind(1),
2055 decoder(),
2056 interruptible(),
2057 move || {
2058 drop(hostile);
2059 Ok(Box::new(1_u8))
2060 },
2061 )
2062 };
2063 let (sender, _runtime, submission, _) =
2064 registration(prepared, kind(1), CompletionDelivery::Delivered);
2065 assert!(catch_unwind(AssertUnwindSafe(|| drop(submission.into_dispatch()))).is_ok());
2066 assert_eq!(sender.attempts.load(Ordering::Relaxed), 1);
2067 assert_eq!(
2068 sender.take().result.unwrap_err().code(),
2069 ExternalFailureCode::Cancelled
2070 );
2071 }
2072 }
2073
2074 #[test]
2075 fn terminal_report_classifies_worker_results_without_exposing_payload() {
2076 let cases = [
2077 (blocking(Ok(Box::new(1_u8))), ExecutorTerminal::Completed),
2078 (
2079 blocking(Err(ExternalFailure::bound_exceeded("bound"))),
2080 ExecutorTerminal::Completed,
2081 ),
2082 (blocking_panic(), ExecutorTerminal::WorkerPanic),
2083 ];
2084 for (prepared, terminal) in cases {
2085 let (_sender, _runtime, submission, _) =
2086 registration(prepared, kind(1), CompletionDelivery::Delivered);
2087 let ExecutorDispatch::Blocking(dispatch) = submission.into_dispatch() else {
2088 unreachable!()
2089 };
2090 assert_eq!(
2091 dispatch.run(),
2092 ExecutorDriveReport {
2093 terminal,
2094 delivery: CompletionDelivery::Delivered,
2095 }
2096 );
2097 }
2098 }
2099}