Skip to main content

sema_core/runtime/
executor.rs

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
9// `web_time::Instant` is `std::time::Instant` on every target except
10// wasm32-unknown-unknown (where the latter's `now()` panics); used here so
11// `ExecutorLease::shutdown`'s deadline type-checks against sema-vm's runtime
12// module, which uses the same substitute throughout.
13use 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/// Terminal executor accounting, intentionally independent of payload details.
499#[derive(Clone, Copy, Debug, Eq, PartialEq)]
500pub enum ExecutorTerminal {
501    Completed,
502    Cancelled,
503    WorkerPanic,
504}
505
506/// The terminal class and whether its private completion reached the runtime inbox.
507#[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        // Starting an opaque destructor during an active unwind could abort on a
791        // second panic. Leak it instead. A single destructor panic outside an
792        // unwind is caught; a destructor that double-panics internally is fatal.
793        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    /// Reserve capacity before arming with `into_dispatch`; enqueue only the armed dispatch.
847    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            // A second bind cannot be expressed because `identity` has moved.
1287        }
1288
1289        let sender = RecordingSender::new(CompletionDelivery::Delivered);
1290        let (_, registrar, _) = CompletionRegistrar::register(sender).unwrap();
1291        bind_once(
1292            &registrar,
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}