Skip to main content

query_runtime/attempt/
owner.rs

1//! Attempt ownership, boundary recording, and independent completion delivery.
2//!
3//! One attempt is split into capabilities of different authority. The
4//! `AttemptOwner` alone fixes the terminal outcome. `AttemptHandle` clones
5//! record boundaries and failures. `AttemptAdmissionBinding::claim` moves the
6//! attempt's single finalizer into an `AttemptAdmission`, after which only the
7//! query service's release path can finish the attempt. Completion receivers
8//! observe one immutable report once that finalizer is consumed.
9//!
10//! # Lock order
11//!
12//! The finalizer slot is taken before the attempt lock, in
13//! `AttemptFinalizerSlot::claim`, and no path takes them the other way round.
14//! The attempt lock covers the lifecycle and the completion state together.
15//! `AttemptShared::changed` coordinates both the publication wait in
16//! `finish_with_footer` and the blocking wait of completion receivers.
17use std::{
18    fmt,
19    future::Future,
20    mem,
21    ops::{Deref, DerefMut},
22    panic,
23    panic::AssertUnwindSafe,
24    pin::Pin,
25    sync,
26    sync::{Arc, Condvar, Mutex, MutexGuard, atomic::Ordering},
27    task::{Context, Poll, Waker},
28    thread,
29    time::{Duration, Instant},
30};
31
32use query_planning::{
33    correlation::{QueryId, TraceId},
34    error::QueryError,
35};
36
37use super::{
38    AttemptBackendCounters, AttemptCancellation, AttemptCompletionQueue, AttemptLifecycle, AttemptLifecycleError,
39    observation::AttemptObservation,
40};
41#[cfg(test)]
42use crate::observability::{
43    CleanupFailurePhase, CleanupFailureSummary, RuntimeCleanupFailureObservation, RuntimeObservationDeliveryGate,
44    RuntimeObservationPayload,
45};
46#[cfg(any(test, feature = "test-hooks"))]
47use crate::runtime::BackendLifecycle;
48use crate::{
49    observability::{
50        AttemptAdmissionFact, AttemptAdmissionWaitFact, AttemptAlternateDeviceEligibility, AttemptCapacityDisposition,
51        AttemptCleanupDisposition, AttemptComposedScheduleProjection, AttemptDisposition, AttemptFailureOperation,
52        AttemptFailureSummary, AttemptId, AttemptQueryId, AttemptReportSealProvider, AttemptRetryAdvice,
53        AttemptTerminalCommit, AttemptTraceContext, CleanupFailureTally, QUERY_ATTEMPT_REPORT_SCHEMA,
54        QueryAttemptReport, QueryCleanupSealDelivery, RuntimeObservationClock, RuntimeObservationConfig,
55        RuntimeObservationFooter, RuntimeObservationHandle, RuntimeTerminalOutcome, SealedRuntimeObservationSnapshot,
56        SystemRuntimeObservationClock,
57    },
58    runtime::ObservationPermit,
59};
60
61/// Caller inputs to begin one execution attempt before planning.
62///
63/// Correlation values are validated by their constructors; oversized input is
64/// rejected structurally before planning and is never truncated.
65pub struct BeginAttemptRequest {
66    pub query_id: Option<AttemptQueryId>,
67    pub trace_context: Option<AttemptTraceContext>,
68    pub deadline: Option<Instant>,
69    pub observation: RuntimeObservationConfig,
70}
71
72impl Default for BeginAttemptRequest {
73    fn default() -> Self {
74        Self {
75            query_id: None,
76            trace_context: None,
77            deadline: None,
78            observation: RuntimeObservationConfig::native_execution(),
79        }
80    }
81}
82
83impl fmt::Debug for BeginAttemptRequest {
84    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85        formatter
86            .debug_struct("BeginAttemptRequest")
87            .field("query_id", &self.query_id)
88            .field("trace_context", &self.trace_context)
89            .field("deadline", &self.deadline)
90            .finish_non_exhaustive()
91    }
92}
93
94/// The attempt owner and its completion factory, created together at begin.
95pub struct AttemptBegin {
96    pub owner: AttemptOwner,
97    pub completion: AttemptCompletionFactory,
98}
99
100impl fmt::Debug for AttemptBegin {
101    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
102        formatter.debug_struct("AttemptBegin").field("attempt_id", &self.owner.attempt_id()).finish_non_exhaustive()
103    }
104}
105
106/// Terminalization authority for one execution attempt.
107///
108/// This type is intentionally not [`Clone`]: boundary recording is shared
109/// through [`AttemptHandle`], but exactly one owner may terminalize.
110pub struct AttemptOwner(Arc<AttemptInner>);
111
112/// Cloneable record-only capability shared by every native fragment of one
113/// invocation. It cannot terminalize the attempt.
114pub struct AttemptHandle(Arc<AttemptCore>);
115
116/// Opaque authority for rejecting an admission request before it receives a
117/// grant.
118///
119/// Narrowed authority compared to [`AttemptOwner`], restricted to committing
120/// structured pre-admission rejections or cancellations without terminalizing
121/// admitted attempts or recording arbitrary boundaries.
122pub struct AttemptAdmissionRejection {
123    core: Arc<AttemptCore>,
124    finalizer_slot: AttemptFinalizerSlot,
125}
126
127/// Opaque, identity-bound admission attachment for one execution attempt.
128///
129/// [`AttemptOwner`] mints this as one unit so an admission request can never
130/// combine one attempt's observation owner with another attempt's cleanup
131/// capability. It exposes neither terminalization nor observation-seal
132/// authority to the request caller.
133#[derive(Clone)]
134pub struct AttemptAdmissionBinding {
135    core: Arc<AttemptCore>,
136    finalizer_slot: AttemptFinalizerSlot,
137}
138
139/// Non-cloneable post-cleanup authority minted for an attempt.
140///
141/// Records canonical terminal outcomes before cleanup, and establishes
142/// finalization and report readiness upon consumption.
143pub(crate) struct AttemptFinalizer(Arc<AttemptCore>);
144
145/// Admission token claimed by [`AttemptAdmissionBinding::claim`] before an
146/// admission request accesses queues, ledgers, streams, or the lifecycle.
147///
148/// Encloses the attempt finalizer. While held, the attempt owner records
149/// terminal outcomes, and the admission token finalizes the attempt either
150/// upon rejection or after GPU cleanup.
151pub(crate) struct AttemptAdmission(AttemptFinalizer);
152
153/// Builds the attempt report when the attempt's observation seals. The
154/// holder of a finished admission passes it to that seal.
155pub(crate) struct AttemptReportPublisher(Arc<AttemptCore>);
156
157impl AttemptReportPublisher {
158    /// Records that the attempt's cleanup began.
159    pub(crate) fn begin_cleanup(&self) {
160        self.0.observation.begin_cleanup();
161    }
162
163    /// Seals the attempt's observation and its report with the attempt's
164    /// terminal outcome, `fallback` only when none is recorded yet; observer
165    /// delivery is deferred to the returned value.
166    pub(crate) fn seal(&self, fallback: RuntimeTerminalOutcome) -> QueryCleanupSealDelivery {
167        self.0.seal_observation(fallback)
168    }
169
170    /// The attempt's record-only capability.
171    pub(crate) fn attempt(&self) -> AttemptHandle {
172        AttemptHandle(Arc::clone(&self.0))
173    }
174}
175
176impl fmt::Debug for AttemptReportPublisher {
177    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
178        formatter.debug_struct("AttemptReportPublisher").field("attempt_id", &self.0.attempt_id).finish_non_exhaustive()
179    }
180}
181
182/// Shared routing for one [`AttemptFinalizer`]. Cloning an admission binding
183/// clones this routing handle, never the finalizer itself. Before admission,
184/// the owner-side path consumes it through `finalize_not_admitted`; the
185/// admission claim takes it exactly once.
186#[derive(Clone)]
187pub(crate) struct AttemptFinalizerSlot(Arc<Mutex<Option<AttemptFinalizer>>>);
188
189impl Clone for AttemptHandle {
190    fn clone(&self) -> Self {
191        Self(Arc::clone(&self.0))
192    }
193}
194
195impl Clone for AttemptAdmissionRejection {
196    fn clone(&self) -> Self {
197        Self { core: Arc::clone(&self.core), finalizer_slot: self.finalizer_slot.clone() }
198    }
199}
200
201/// Independent receiver for the bounded terminal attempt report.
202///
203/// Cloning or dropping receivers never cancels cleanup, report production, or
204/// exact metric updates, and holding a receiver retains no device lease.
205///
206/// Receiver counts allow publication to route unreceived reports to the
207/// optional abandoned-report queue. Adapter internals hold an
208/// [`AttemptCompletionFactory`] instead, preventing an attempt object or result
209/// stream from masking caller abandonment.
210pub struct AttemptCompletion {
211    shared: Arc<AttemptShared>,
212}
213
214impl Clone for AttemptCompletion {
215    fn clone(&self) -> Self {
216        let mut state = self.shared.lock();
217        state.completion.receivers = state.completion.receivers.saturating_add(1);
218        Self { shared: Arc::clone(&self.shared) }
219    }
220}
221
222/// Adapter-internal minting capability for one attempt's completion receivers.
223///
224/// The factory crosses crates so the adapter attempt object can issue
225/// independent counted receivers on demand and read the sealed report for its
226/// seal observer. It is never an application receiver itself: cloning or
227/// dropping a factory never changes the active receiver count, so it cannot
228/// mask caller abandonment. It cannot terminalize the attempt or store a
229/// second report.
230pub struct AttemptCompletionFactory {
231    shared: Arc<AttemptShared>,
232}
233
234impl Clone for AttemptCompletionFactory {
235    fn clone(&self) -> Self {
236        Self { shared: Arc::clone(&self.shared) }
237    }
238}
239
240impl AttemptCompletionFactory {
241    /// Mints one independent counted receiver for the attempt's immutable
242    /// terminal report.
243    pub fn completion(&self) -> AttemptCompletion {
244        let mut state = self.shared.lock();
245        state.completion.receivers = state.completion.receivers.saturating_add(1);
246        AttemptCompletion { shared: Arc::clone(&self.shared) }
247    }
248
249    /// Returns the published report without waiting, when ready.
250    pub fn try_report(&self) -> Option<Arc<QueryAttemptReport>> {
251        self.shared.lock().completion.visible_report()
252    }
253}
254
255/// A read-only callback invoked while the one immutable report is being
256/// published. It intentionally has no terminalization, retry, or storage
257/// authority; consumers may only project the report they receive.
258struct SealedAttemptReportSink {
259    notify: Box<dyn Fn(Arc<QueryAttemptReport>) + Send + Sync>,
260}
261
262impl SealedAttemptReportSink {
263    fn invoke(&self, report: Arc<QueryAttemptReport>) {
264        // A projection must never prevent the owning attempt from publishing
265        // its already-finalized report.
266        let _ = panic::catch_unwind(AssertUnwindSafe(|| (self.notify)(report)));
267    }
268}
269
270struct CompletionState {
271    // The canonical report allocation. It remains unreadable until every
272    // pre-registered projection consumed it, so there is no second sink-side
273    // report store and no completion observer can race ahead of a projection.
274    report: Option<Arc<QueryAttemptReport>>,
275    report_visible: bool,
276    sealed_report_sinks: Vec<SealedAttemptReportSink>,
277    wakers: Vec<Waker>,
278    // Receiver counting shares the publication mutex so clone/drop linearize
279    // against the abandoned-at-publish decision.
280    receivers: u64,
281}
282
283impl CompletionState {
284    fn visible_report(&self) -> Option<Arc<QueryAttemptReport>> {
285        self.report_visible.then(|| self.report.as_ref().map(Arc::clone)).flatten()
286    }
287}
288
289/// Releases the grant of an admitted attempt.
290///
291/// The attempt owns its admission: every native fragment it executes shares
292/// the one grant, so the grant is released when the attempt's outcome turns
293/// terminal. The end of one fragment's stream or the drop of its query handle
294/// does not release it. The release itself waits for fragments still running.
295pub(crate) trait AdmittedGrantRelease: Send + Sync {
296    fn request_release(&self);
297}
298
299/// The attempt lifecycle and its completion delivery under one lock, so the
300/// report-ready boundary and the report a receiver sees cannot disagree.
301struct AttemptState {
302    lifecycle: AttemptLifecycle,
303    completion: CompletionState,
304    /// Bound when admission publishes a grant; taken by the terminal commit.
305    /// The attempt holds the release strongly, so the grant outlives every
306    /// query handle of the attempt until the attempt is terminal.
307    admitted_release: Option<Arc<dyn AdmittedGrantRelease>>,
308}
309
310/// State shared by the attempt core and its completion receivers. Receivers
311/// hold only this, never the observation session behind the core.
312struct AttemptShared {
313    state: Mutex<AttemptState>,
314    changed: Condvar,
315}
316
317impl AttemptShared {
318    fn lock(&self) -> MutexGuard<'_, AttemptState> {
319        self.state.lock().unwrap_or_else(sync::PoisonError::into_inner)
320    }
321}
322
323/// The attempt lock viewed as its lifecycle.
324struct LifecycleGuard<'a>(MutexGuard<'a, AttemptState>);
325
326impl Deref for LifecycleGuard<'_> {
327    type Target = AttemptLifecycle;
328
329    fn deref(&self) -> &AttemptLifecycle {
330        &self.0.lifecycle
331    }
332}
333
334impl DerefMut for LifecycleGuard<'_> {
335    fn deref_mut(&mut self) -> &mut AttemptLifecycle {
336        &mut self.0.lifecycle
337    }
338}
339
340/// Everything behind an attempt that outlives the owner: shared by the owner,
341/// every handle, the admission binding, and the finalizer. Completion
342/// receivers hold only `shared`.
343struct AttemptCore {
344    attempt_id: AttemptId,
345    query_id: Option<AttemptQueryId>,
346    trace_context: Option<AttemptTraceContext>,
347    shared: Arc<AttemptShared>,
348    /// Source of every lifecycle timestamp. Tests inject a deterministic
349    /// clock; the query service samples it through the finalizer so a
350    /// release-commit fact uses the attempt's clock and no other.
351    clock: Arc<dyn RuntimeObservationClock>,
352    cancellation: AttemptCancellation,
353    observation: AttemptObservation,
354    counters: Arc<AttemptBackendCounters>,
355    completion_queue: Option<Arc<AttemptCompletionQueue>>,
356    // The begin-vs-close linearization artifact. It fences observation
357    // quiescence only until the report is published. A caller holding a
358    // finished stream or completion must never delay backend close.
359    observation_permit: Mutex<Option<ObservationPermit>>,
360}
361
362struct AttemptInner {
363    core: Arc<AttemptCore>,
364    finalizer_slot: AttemptFinalizerSlot,
365}
366
367impl fmt::Debug for AttemptCore {
368    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
369        formatter.debug_struct("AttemptCore").field("attempt_id", &self.attempt_id).finish_non_exhaustive()
370    }
371}
372
373impl AttemptCore {
374    fn lock_lifecycle(&self) -> LifecycleGuard<'_> {
375        LifecycleGuard(self.shared.lock())
376    }
377
378    fn monotonic_ns(&self) -> u64 {
379        self.clock.monotonic_ns()
380    }
381
382    /// Requests the release of the admitted grant once the outcome is
383    /// terminal. The hook is taken under the attempt lock and called after
384    /// it is released.
385    fn release_admitted_grant(&self) {
386        let release = self.shared.lock().admitted_release.take();
387        if let Some(release) = release {
388            release.request_release();
389        }
390    }
391
392    fn register_sealed_report_sink<F>(&self, sink: F)
393    where F: Fn(Arc<QueryAttemptReport>) + Send + Sync + 'static {
394        let mut sink = Some(SealedAttemptReportSink { notify: Box::new(sink) });
395        let published = {
396            let mut state = self.shared.lock();
397            let completion = &mut state.completion;
398            match &completion.report {
399                Some(report) if completion.report_visible => Some(Arc::clone(report)),
400                Some(_) => {
401                    // Publication is draining the pre-visibility queue. The sink
402                    // joins that drain and is not invoked independently, so a
403                    // concurrent late registration cannot race completion visibility.
404                    completion.sealed_report_sinks.push(sink.take().expect("sink exists"));
405                    None
406                },
407                None => {
408                    completion.sealed_report_sinks.push(sink.take().expect("sink exists"));
409                    None
410                },
411            }
412        };
413        if let Some(report) = published {
414            sink.expect("unregistered sink exists").invoke(report);
415        }
416    }
417
418    /// Builds the fixed report from lifecycle state. The sole seal commit
419    /// applies observation completeness before this value is made visible to
420    /// either the sealed snapshot or completion receivers.
421    fn report(&self, lifecycle: &AttemptLifecycle, cleanup_failures: &CleanupFailureTally) -> QueryAttemptReport {
422        let terminal_commit = lifecycle.cleanup_commit();
423        let cleanup_failure_count = cleanup_failures.count();
424        let outcome = lifecycle.terminal_outcome().cloned().unwrap_or(RuntimeTerminalOutcome::Dropped);
425        let primary_failure = lifecycle.primary_failure_summary().cloned();
426        let capacity_disposition = terminal_commit
427            .map(AttemptTerminalCommit::capacity_disposition)
428            .unwrap_or(AttemptCapacityDisposition::NotAdmitted);
429        let health_transition = terminal_commit.map(AttemptTerminalCommit::health_transition);
430        let native_fault_evidence = terminal_commit.and_then(AttemptTerminalCommit::native_fault_evidence);
431        let resource_evidence = terminal_commit.and_then(AttemptTerminalCommit::resource_evidence);
432        let alternate_device_eligibility = terminal_commit
433            .map(AttemptTerminalCommit::alternate_device_eligibility)
434            .unwrap_or(AttemptAlternateDeviceEligibility::Unavailable);
435        let retry_advice = AttemptRetryAdvice::from_sealed_attempt(
436            &outcome,
437            primary_failure.as_ref(),
438            capacity_disposition,
439            health_transition,
440            native_fault_evidence,
441            resource_evidence,
442            alternate_device_eligibility,
443        );
444        QueryAttemptReport {
445            schema: QUERY_ATTEMPT_REPORT_SCHEMA.to_string(),
446            attempt_id: self.attempt_id,
447            query_id: self.query_id.clone(),
448            trace_context: self.trace_context.clone(),
449            disposition: lifecycle.recorded_disposition().unwrap_or(AttemptDisposition::Unplanned),
450            outcome,
451            primary_failure,
452            retry_advice,
453            device_ordinal: lifecycle.admission_fact().map(|fact| fact.device_ordinal),
454            admission: lifecycle.admission_fact().cloned(),
455            admission_wait: lifecycle.admission_wait_fact(),
456            composed_schedule: lifecycle.composed_schedule_projection().cloned(),
457            timestamps: lifecycle.timestamps(),
458            cleanup_disposition: if lifecycle.admission_fact().is_some() {
459                if cleanup_failure_count == 0 {
460                    AttemptCleanupDisposition::Completed
461                } else {
462                    AttemptCleanupDisposition::CompletedWithFailures
463                }
464            } else {
465                AttemptCleanupDisposition::NotRequired
466            },
467            cleanup_failure_count,
468            cleanup_failure_count_complete: cleanup_failures.complete(),
469            first_cleanup_failure: cleanup_failures.first().cloned(),
470            // `finish_with_footer` requires finalization before publication.
471            // An admitted report therefore always has a terminal commit;
472            // `NotAdmitted` is the only valid no-commit projection.
473            capacity_disposition,
474            final_domain_usage: terminal_commit.and_then(AttemptTerminalCommit::final_domain_usage),
475            health_transition,
476            native_fault_evidence,
477            resource_evidence,
478            observation_lost_records: 0,
479            observation_retained_records: 0,
480            observation_retained_bytes: 0,
481            observation_truncated: false,
482            observation_degraded: false,
483            unix_epoch_ns: self.clock.unix_epoch_ns(),
484        }
485    }
486
487    /// Establishes the lifecycle's sole terminal outcome. A terminal already
488    /// committed by the attempt owner always wins over a release fallback.
489    fn terminalize_for_release(
490        &self,
491        fallback: RuntimeTerminalOutcome,
492    ) -> Result<RuntimeTerminalOutcome, AttemptLifecycleError> {
493        let mut lifecycle = self.lock_lifecycle();
494        if lifecycle.terminal_outcome().is_some() {
495            return Err(AttemptLifecycleError::DoubleTerminalization);
496        }
497        lifecycle.terminalize(fallback, self.monotonic_ns())?;
498        Ok(lifecycle.terminal_outcome().cloned().expect("terminal outcome was recorded"))
499    }
500
501    fn terminalize_for_release_with_primary_failure(
502        &self,
503        outcome: RuntimeTerminalOutcome,
504        summary: AttemptFailureSummary,
505    ) -> Result<RuntimeTerminalOutcome, AttemptLifecycleError> {
506        let mut lifecycle = self.lock_lifecycle();
507        if lifecycle.terminal_outcome().is_some() {
508            return Err(AttemptLifecycleError::DoubleTerminalization);
509        }
510        lifecycle.primary_failure(summary);
511        lifecycle.terminalize(outcome, self.monotonic_ns())?;
512        Ok(lifecycle.terminal_outcome().cloned().expect("terminal outcome was recorded"))
513    }
514
515    /// Resolves the attempt's sole terminal outcome before cleanup. A pending
516    /// outcome the recorder observed is only a fallback: the attempt
517    /// lifecycle remains canonical once it has a terminal state, and every
518    /// seal of the attempt's observation uses that outcome.
519    fn canonical_terminal_outcome(&self, fallback: RuntimeTerminalOutcome) -> RuntimeTerminalOutcome {
520        let mut lifecycle = self.lock_lifecycle();
521        if let Some(outcome) = lifecycle.terminal_outcome() {
522            return outcome.clone();
523        }
524        // A caller can capture a borrowed native failure before it returns the
525        // error to a transport wrapper. In particular, an admitted handle may
526        // start cleanup before that outer wrapper receives the error. The stored
527        // summary is therefore the only truthful terminal classification
528        // available at this boundary; do not replace it with a mechanical
529        // drop/cancellation fallback.
530        let fallback =
531            lifecycle.primary_failure_summary().map(RuntimeTerminalOutcome::from_primary_failure).unwrap_or(fallback);
532        let at = self.monotonic_ns();
533        // The seal path has no error channel, so a refused transition is not
534        // reported here. A lifecycle that still cannot terminalize surfaces in
535        // `finish_with_footer`, whose `ready` transition then fails.
536        lifecycle.terminalize(fallback.clone(), at).unwrap_or(());
537        lifecycle.terminal_outcome().cloned().unwrap_or(fallback)
538    }
539
540    /// Seals the attempt's observation with its canonical terminal outcome, so
541    /// the footer, the terminal boundary event, and the report built from the
542    /// lifecycle carry one value. A recovery seal's mechanical fallback never
543    /// replaces an outcome the lifecycle already holds.
544    fn seal_observation(self: &Arc<Self>, fallback: RuntimeTerminalOutcome) -> QueryCleanupSealDelivery {
545        let outcome = self.canonical_terminal_outcome(fallback);
546        self.observation.seal(outcome, &self.report_seal_provider())
547    }
548
549    /// The report provider for this attempt's observation seal. Every seal of
550    /// the attempt's session passes it, so the sealed snapshot embeds the
551    /// report that completion receivers publish.
552    fn report_seal_provider(self: &Arc<Self>) -> AttemptReportSealProvider {
553        let core = Arc::clone(self);
554        Arc::new(move |footer, cleanup_failures| {
555            core.finish_with_footer(footer, cleanup_failures)
556                .expect("attempt lifecycle must complete while sealing its sole immutable report")
557        })
558    }
559
560    /// The single seal-and-completion commit: a previously consumed finalizer,
561    /// exact aggregates, report-ready, and publication of one immutable report.
562    /// The returned `Arc` is embedded directly in the sealed snapshot.
563    fn finish_with_footer(
564        &self,
565        footer: &RuntimeObservationFooter,
566        cleanup_failures: &CleanupFailureTally,
567    ) -> Result<Arc<QueryAttemptReport>, AttemptLifecycleError> {
568        // Report-ready, the report built from that lifecycle, and its storage
569        // share one lock hold. The stored report stays unreadable until every
570        // pre-registered projection consumed it. Sinks consume that exact Arc
571        // before the visibility flag wakes any completion receiver; late
572        // registrations clone this canonical allocation directly. They do not
573        // retain another copy.
574        let report = {
575            let mut state = self.shared.lock();
576            loop {
577                match &state.completion.report {
578                    Some(existing) if state.completion.report_visible => return Ok(Arc::clone(existing)),
579                    Some(_) => state = self.shared.changed.wait(state).unwrap_or_else(sync::PoisonError::into_inner),
580                    None => break,
581                }
582            }
583            if !state.lifecycle.is_ready() {
584                state.lifecycle.ready(self.monotonic_ns())?;
585            }
586            let mut report = self.report(&state.lifecycle, cleanup_failures);
587            report.set_observation_completeness(footer);
588            let report = Arc::new(report);
589            state.completion.report = Some(Arc::clone(&report));
590            report
591        };
592
593        // Keep draining under a visibility fence. A sink may register another
594        // sink, and another thread may register while this publication is in
595        // flight; every such pre-visibility sink receives this exact Arc
596        // before any completion reader can observe it.
597        let (wakers, abandoned) = loop {
598            let sinks = mem::take(&mut self.shared.lock().completion.sealed_report_sinks);
599            for sink in sinks {
600                sink.invoke(Arc::clone(&report));
601            }
602            let mut state = self.shared.lock();
603            let completion = &mut state.completion;
604            if !completion.sealed_report_sinks.is_empty() {
605                continue;
606            }
607            debug_assert!(completion.report.as_ref().is_some_and(|existing| Arc::ptr_eq(existing, &report)));
608            completion.report_visible = true;
609            break (mem::take(&mut completion.wakers), completion.receivers == 0);
610        };
611        self.counters.reports_ready.fetch_add(1, Ordering::AcqRel);
612        self.shared.changed.notify_all();
613        for waker in wakers {
614            waker.wake();
615        }
616        // Abandoned-report delivery is best-effort. Overflow is counted and
617        // never blocks planning, GPU workers, or cleanup.
618        if abandoned && let Some(queue) = &self.completion_queue {
619            if queue.try_push(Arc::clone(&report)) {
620                self.counters.completion_queue_pushed.fetch_add(1, Ordering::AcqRel);
621            } else {
622                self.counters.completion_queue_overflowed.fetch_add(1, Ordering::AcqRel);
623            }
624        }
625        // The report is durable. The attempt's observation permit has nothing
626        // left to fence and is released before any waiter observes close
627        // quiescence. Report and completion state carry no lease.
628        self.observation_permit.lock().unwrap_or_else(sync::PoisonError::into_inner).take();
629        Ok(report)
630    }
631}
632
633impl AttemptOwner {
634    pub(crate) fn begin(
635        request: BeginAttemptRequest,
636        permit: ObservationPermit,
637        counters: Arc<AttemptBackendCounters>,
638        queue: Option<Arc<AttemptCompletionQueue>>,
639    ) -> AttemptBegin {
640        Self::begin_with_clock(request, permit, counters, queue, Arc::new(SystemRuntimeObservationClock::new()))
641    }
642
643    /// Creates the attempt core, its one finalizer, and the completion factory.
644    ///
645    /// `permit` is the backend observation permit `begin_attempt` took; the
646    /// core releases it when the report is published, which is the point at
647    /// which backend close stops waiting on this attempt. `queue` receives the
648    /// report when no completion receiver is alive at publication.
649    pub(crate) fn begin_with_clock(
650        request: BeginAttemptRequest,
651        permit: ObservationPermit,
652        counters: Arc<AttemptBackendCounters>,
653        queue: Option<Arc<AttemptCompletionQueue>>,
654        clock: Arc<dyn RuntimeObservationClock>,
655    ) -> AttemptBegin {
656        // A disabled diagnostic config is normalized to a zero-limit bounded
657        // session so the recorder still seals and delivers observers. The fixed
658        // summary and completion must survive any diagnostic loss.
659        let observation = if request.observation.is_enabled() {
660            request.observation
661        } else {
662            RuntimeObservationConfig::bounded(0, 0)
663        };
664        let shared = Arc::new(AttemptShared {
665            state: Mutex::new(AttemptState {
666                lifecycle: AttemptLifecycle::new(clock.monotonic_ns()),
667                completion: CompletionState {
668                    report: None,
669                    report_visible: false,
670                    sealed_report_sinks: Vec::new(),
671                    wakers: Vec::new(),
672                    receivers: 0,
673                },
674                admitted_release: None,
675            }),
676            changed: Condvar::new(),
677        });
678        // The observation manifest uses the canonical unassigned sentinel when
679        // the caller supplied no correlation; the attempt id is the identity.
680        let observation_query_id = request
681            .query_id
682            .as_ref()
683            .map(|query_id| QueryId::new(query_id.as_str()))
684            .unwrap_or_else(|| QueryId::new(""));
685        let trace_id = request.trace_context.as_ref().map(|trace| TraceId::new(trace.as_str()));
686        let core = Arc::new(AttemptCore {
687            attempt_id: AttemptId::mint(),
688            query_id: request.query_id,
689            trace_context: request.trace_context,
690            shared: Arc::clone(&shared),
691            clock: Arc::clone(&clock),
692            cancellation: AttemptCancellation::new(request.deadline),
693            observation: AttemptObservation::new(observation_query_id, trace_id, observation, clock),
694            counters,
695            completion_queue: queue,
696            observation_permit: Mutex::new(Some(permit)),
697        });
698        let finalizer_slot = AttemptFinalizerSlot::new(AttemptFinalizer(Arc::clone(&core)));
699        AttemptBegin {
700            owner: Self(Arc::new(AttemptInner { core, finalizer_slot })),
701            completion: AttemptCompletionFactory { shared },
702        }
703    }
704
705    /// A fresh attempt observed outside any backend, for tests that admit
706    /// without a planning frontend.
707    #[cfg(any(test, feature = "test-hooks"))]
708    #[doc(hidden)]
709    pub fn begin_for_test(request: BeginAttemptRequest) -> AttemptBegin {
710        Self::begin(
711            request,
712            BackendLifecycle::new().try_begin_observation().expect("a new backend lifecycle admits observation"),
713            Arc::new(AttemptBackendCounters::default()),
714            None,
715        )
716    }
717
718    /// A fresh attempt classified as native and observed outside any backend,
719    /// for tests that admit without a planning frontend.
720    #[cfg(any(test, feature = "test-hooks"))]
721    pub(crate) fn native_for_test() -> Self {
722        let begin = Self::begin_for_test(BeginAttemptRequest::default());
723        begin.owner.handle().set_disposition(AttemptDisposition::Native).expect("a new attempt accepts a disposition");
724        begin.owner
725    }
726
727    /// The engine-minted identity of this invocation.
728    pub fn attempt_id(&self) -> AttemptId {
729        self.0.core.attempt_id
730    }
731
732    /// Record-only capability for native fragments of this invocation.
733    pub fn handle(&self) -> AttemptHandle {
734        AttemptHandle(Arc::clone(&self.0.core))
735    }
736
737    /// Register a read-only projection of the immutable terminal report.
738    ///
739    /// The sink receives the report before completion receivers are notified,
740    /// and late registration receives the already-published report. It has no
741    /// lifecycle, retry, or aggregation authority; backend metrics consumes
742    /// the exact sealed allocation without introducing another store.
743    pub fn register_sealed_report_sink<F>(&self, sink: F)
744    where F: Fn(Arc<QueryAttemptReport>) + Send + Sync + 'static {
745        self.0.core.register_sealed_report_sink(sink);
746    }
747
748    /// Opaque capability that can terminalize only an admission request that
749    /// was rejected before an admission grant was published.
750    pub fn admission_rejection(&self) -> AttemptAdmissionRejection {
751        AttemptAdmissionRejection { core: Arc::clone(&self.0.core), finalizer_slot: self.0.finalizer_slot.clone() }
752    }
753
754    /// Opaque, atomic attachment for the query-service admission request.
755    ///
756    /// Its observation and release capability originate from this same
757    /// attempt and are consumed together by admission.
758    pub fn admission_binding(&self) -> AttemptAdmissionBinding {
759        AttemptAdmissionBinding { core: Arc::clone(&self.0.core), finalizer_slot: self.0.finalizer_slot.clone() }
760    }
761
762    /// An independent receiver for the terminal report.
763    pub fn completion(&self) -> AttemptCompletion {
764        self.0.core.shared.lock().completion.receivers += 1;
765        AttemptCompletion { shared: Arc::clone(&self.0.core.shared) }
766    }
767
768    /// The attempt-scoped cooperative cancellation signal and deadline.
769    pub fn cancellation(&self) -> AttemptCancellation {
770        self.0.core.cancellation.clone()
771    }
772
773    /// The sealed observation snapshot, once the attempt has sealed.
774    #[cfg(test)]
775    pub(crate) fn sealed_snapshot_for_test(&self) -> Option<SealedRuntimeObservationSnapshot> {
776        self.0.core.observation.sealed_snapshot()
777    }
778
779    /// Recording access to the attempt's observation.
780    #[cfg(test)]
781    pub(crate) fn observation_for_test(&self) -> RuntimeObservationHandle {
782        self.0.core.observation.handle()
783    }
784
785    /// Holds the attempt's seal observer delivery until the gate opens.
786    #[cfg(test)]
787    pub(crate) fn arm_delivery_gate_for_test(&self) -> Arc<RuntimeObservationDeliveryGate> {
788        self.0.core.observation.arm_delivery_gate()
789    }
790
791    /// The one terminal commit hook makes the outcome terminal. For admitted
792    /// attempts, cleanup-complete and report-ready are deferred to the
793    /// post-GPU-cleanup seal; otherwise the commit completes immediately.
794    pub fn commit_terminal(&self, outcome: RuntimeTerminalOutcome) -> Result<(), AttemptLifecycleError> {
795        let terminal = self.0.core.terminalize_for_release(outcome)?;
796        self.commit_resolved_terminal(terminal)
797    }
798
799    /// Publishes an already-linearized terminal outcome without attempting a
800    /// second terminal lifecycle write. Borrowed-error capture records the
801    /// primary summary and terminal outcome together before reaching this
802    /// common finalization path.
803    fn commit_resolved_terminal(&self, terminal: RuntimeTerminalOutcome) -> Result<(), AttemptLifecycleError> {
804        match self.0.finalizer_slot.finalize_not_admitted(terminal) {
805            Ok(terminal) => {
806                let _ = self.0.core.seal_observation(terminal).deliver();
807                Ok(())
808            },
809            // An admission claim holds the finalizer, or a not-admitted
810            // commit already consumed it. Either way the terminal outcome is
811            // recorded and its holder publishes the report; an admitted
812            // claim publishes after its grant is released.
813            Err(AttemptLifecycleError::FinalizationUnavailable) => {
814                self.0.core.release_admitted_grant();
815                Ok(())
816            },
817            Err(error) => Err(error),
818        }
819    }
820
821    /// Captures the native error by reference before reducing it to the
822    /// closed terminal outcome. The caller retains the untouched error and
823    /// its complete source chain.
824    pub fn commit_query_error(&self, error: &QueryError) -> Result<(), AttemptLifecycleError> {
825        let operation = self.0.core.lock_lifecycle().failure_operation();
826        let outcome = RuntimeTerminalOutcome::from_query_error(error);
827        let terminal = match RuntimeTerminalOutcome::primary_failure_from_query_error(error, operation) {
828            Some(summary) => self.0.core.terminalize_for_release_with_primary_failure(outcome, summary)?,
829            None => self.0.core.terminalize_for_release(outcome)?,
830        };
831        self.commit_resolved_terminal(terminal)
832    }
833
834    /// Commits a closed adapter fallback without retaining adapter display or
835    /// source-chain text in the engine-owned report.
836    pub fn commit_failure_summary(&self, summary: AttemptFailureSummary) -> Result<(), AttemptLifecycleError> {
837        let terminal = self
838            .0
839            .core
840            .terminalize_for_release_with_primary_failure(RuntimeTerminalOutcome::failed(&summary), summary)?;
841        self.commit_resolved_terminal(terminal)
842    }
843}
844
845impl Drop for AttemptOwner {
846    /// Backstop for an owner dropped before its report is ready. A panicking
847    /// thread records `Panicked`, a cancelled attempt records its typed
848    /// cancellation, and anything else records `Dropped`. Commit errors are
849    /// ignored because another path may have terminalized first, in which
850    /// case that outcome stands.
851    fn drop(&mut self) {
852        let report_ready = self.0.core.lock_lifecycle().is_ready();
853        if report_ready {
854            return;
855        }
856        if thread::panicking() {
857            let _ = self.commit_terminal(RuntimeTerminalOutcome::Panicked);
858            return;
859        }
860        // An attempt dropped after its cancellation took effect ends with that
861        // cancellation. Without cancellation, the drop records `Dropped`.
862        let _ = match self.0.core.cancellation.check("query_engine.attempt.owner_drop") {
863            Err(cancellation) => self.commit_query_error(&cancellation),
864            Ok(()) => self.commit_terminal(RuntimeTerminalOutcome::Dropped),
865        };
866    }
867}
868
869impl fmt::Debug for AttemptOwner {
870    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
871        formatter.debug_struct("AttemptOwner").field("attempt_id", &self.attempt_id()).finish_non_exhaustive()
872    }
873}
874
875impl fmt::Debug for AttemptHandle {
876    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
877        formatter.debug_struct("AttemptHandle").field("attempt_id", &self.attempt_id()).finish_non_exhaustive()
878    }
879}
880
881impl fmt::Debug for AttemptAdmissionRejection {
882    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
883        formatter
884            .debug_struct("AttemptAdmissionRejection")
885            .field("attempt_id", &self.core.attempt_id)
886            .finish_non_exhaustive()
887    }
888}
889
890impl fmt::Debug for AttemptFinalizer {
891    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
892        formatter.debug_struct("AttemptFinalizer").field("attempt_id", &self.0.attempt_id).finish_non_exhaustive()
893    }
894}
895
896impl fmt::Debug for AttemptAdmission {
897    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
898        formatter.debug_struct("AttemptAdmission").field("attempt_id", &self.0.0.attempt_id).finish_non_exhaustive()
899    }
900}
901
902impl fmt::Debug for AttemptFinalizerSlot {
903    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
904        formatter.debug_struct("AttemptFinalizerSlot").finish_non_exhaustive()
905    }
906}
907
908impl fmt::Debug for AttemptAdmissionBinding {
909    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
910        formatter.debug_struct("AttemptAdmissionBinding").finish_non_exhaustive()
911    }
912}
913
914impl AttemptAdmissionBinding {
915    /// Engine-minted identity retained only for post-callback allocator-event
916    /// correlation. Request callers cannot replace or synthesize it.
917    pub(crate) fn attempt_id(&self) -> AttemptId {
918        self.core.attempt_id
919    }
920
921    /// Returns recording-only access to the attempt observation.
922    ///
923    /// The returned handle cannot seal the observation.  Admission must take
924    /// this binding as a whole to receive the paired cleanup commitment.
925    pub fn observation_handle(&self) -> RuntimeObservationHandle {
926        self.core.observation.handle()
927    }
928
929    /// Registers a read-only notification for the immutable attempt seal,
930    /// without exposing any terminal-seal authority to callers.
931    pub fn register_seal_observer<F>(&self, observer: F)
932    where F: Fn(SealedRuntimeObservationSnapshot) + Send + Sync + 'static {
933        self.core.observation.register_seal_observer(observer);
934    }
935
936    /// Takes the attempt's only admission right. It must be the first step of
937    /// an admission request, before any queue, ledger, stream, or lifecycle
938    /// change. A rejected claim changes nothing and returns
939    /// [`AttemptLifecycleError::AdmissionAlreadyClaimed`] once the right is
940    /// consumed.
941    pub(crate) fn claim(&self) -> Result<AttemptAdmission, AttemptLifecycleError> {
942        self.finalizer_slot.claim()
943    }
944}
945
946impl AttemptAdmissionRejection {
947    /// Records the structured outcome of a rejected or cancelled admission
948    /// request. The lifecycle accepts it only after admission was requested
949    /// and before a grant or physical execution boundary exists.
950    pub fn reject(&self, error: &QueryError) -> Result<(), AttemptLifecycleError> {
951        // An effective cancellation, by the caller or by the deadline,
952        // supersedes the queue's own error so the report classifies the
953        // attempt as `Cancelled` with the cancellation's reason.
954        let cancellation_error = self.core.cancellation.is_cancelled().then(|| {
955            self.core
956                .cancellation
957                .check("query_engine.attempt.admission_rejection")
958                .expect_err("an effective cancellation must produce a terminal error")
959        });
960        let (outcome, summary) = if let Some(cancellation_error) = cancellation_error {
961            (
962                RuntimeTerminalOutcome::Cancelled,
963                RuntimeTerminalOutcome::primary_failure_from_query_error(
964                    &cancellation_error,
965                    AttemptFailureOperation::Admission,
966                ),
967            )
968        } else {
969            let summary = if error.fact_value("reason") == Some("backend_closing") {
970                Some(AttemptFailureSummary::backend_closing())
971            } else {
972                RuntimeTerminalOutcome::primary_failure_from_query_error(error, AttemptFailureOperation::Admission)
973            };
974            (RuntimeTerminalOutcome::from_query_error(error), summary)
975        };
976        {
977            let mut lifecycle = self.core.lock_lifecycle();
978            // A claimed admission, including one whose second claim was just
979            // rejected, is finished by its claim; this rejection changes nothing.
980            if lifecycle.is_claimed() {
981                return Ok(());
982            }
983            lifecycle.terminalize_admission_rejection(outcome.clone(), summary, self.core.monotonic_ns())?;
984        }
985        let outcome = self.finalizer_slot.finalize_not_admitted(outcome)?;
986        let _ = self.core.seal_observation(outcome).deliver();
987        Ok(())
988    }
989}
990
991impl AttemptFinalizerSlot {
992    fn new(finalizer: AttemptFinalizer) -> Self {
993        Self(Arc::new(Mutex::new(Some(finalizer))))
994    }
995
996    /// Consumes the finalizer for a not-admitted finalization. The slot lock
997    /// is released before the lifecycle is touched, which keeps the module's
998    /// lock order; only `claim` holds both.
999    fn take(&self) -> Result<AttemptFinalizer, AttemptLifecycleError> {
1000        self.0
1001            .lock()
1002            .unwrap_or_else(sync::PoisonError::into_inner)
1003            .take()
1004            .ok_or(AttemptLifecycleError::FinalizationUnavailable)
1005    }
1006
1007    /// Takes the finalizer as the attempt's admission right. The slot lock is
1008    /// held while the lifecycle validates the claim, so a rejected claim
1009    /// leaves both the slot and the lifecycle unchanged.
1010    fn claim(&self) -> Result<AttemptAdmission, AttemptLifecycleError> {
1011        let mut slot = self.0.lock().unwrap_or_else(sync::PoisonError::into_inner);
1012        let core = &slot.as_ref().ok_or(AttemptLifecycleError::AdmissionAlreadyClaimed)?.0;
1013        core.lock_lifecycle().admission_requested(core.monotonic_ns())?;
1014        Ok(AttemptAdmission(slot.take().expect("the claimed finalizer is present")))
1015    }
1016
1017    pub(crate) fn finalize_not_admitted(
1018        &self,
1019        fallback: RuntimeTerminalOutcome,
1020    ) -> Result<RuntimeTerminalOutcome, AttemptLifecycleError> {
1021        self.take()?.finalize_not_admitted(fallback)
1022    }
1023}
1024
1025impl AttemptFinalizer {
1026    /// Samples the attempt-owned monotonic clock for a release-commit fact.
1027    ///
1028    /// The query service must not manufacture a timestamp when no admission
1029    /// binding transferred this linear finalizer into its ownership.
1030    pub(crate) fn monotonic_ns(&self) -> u64 {
1031        self.0.monotonic_ns()
1032    }
1033
1034    pub(crate) fn record_primary_failure(&self, summary: AttemptFailureSummary) {
1035        self.0.lock_lifecycle().primary_failure(summary);
1036    }
1037
1038    /// Establishes the lifecycle terminal outcome before committing admitted
1039    /// cleanup and capacity facts. The resolved outcome must then be passed
1040    /// to the observation seal so footer, report, and terminal boundary agree.
1041    pub(crate) fn terminalize_for_release(
1042        &self,
1043        fallback: RuntimeTerminalOutcome,
1044    ) -> Result<RuntimeTerminalOutcome, AttemptLifecycleError> {
1045        // Only the attempt's terminal commit requests the release, so the
1046        // committed outcome is already in place; the linear finalizer
1047        // resolves it and does not manufacture a second commit.
1048        Ok(self.0.canonical_terminal_outcome(fallback))
1049    }
1050
1051    fn finalize_not_admitted(
1052        self,
1053        fallback: RuntimeTerminalOutcome,
1054    ) -> Result<RuntimeTerminalOutcome, AttemptLifecycleError> {
1055        let mut lifecycle = self.0.lock_lifecycle();
1056        let outcome = match lifecycle.terminal_outcome() {
1057            Some(outcome) => outcome.clone(),
1058            None => {
1059                lifecycle.terminalize(fallback, self.0.monotonic_ns())?;
1060                lifecycle.terminal_outcome().cloned().expect("terminal outcome was recorded")
1061            },
1062        };
1063        lifecycle.finalize_not_admitted(self.0.monotonic_ns())?;
1064        Ok(outcome)
1065    }
1066
1067    /// Consumes the sole post-cleanup finalizer. This remains crate-private so
1068    /// request callers cannot manufacture cleanup/capacity facts for another
1069    /// attempt or race the envelope that owns the immutable grant.
1070    pub(crate) fn finalize_admitted(self, commit: AttemptTerminalCommit) -> Result<(), AttemptLifecycleError> {
1071        let mut lifecycle = self.0.lock_lifecycle();
1072        lifecycle.terminal_commit(commit, self.0.monotonic_ns())?;
1073        Ok(())
1074    }
1075}
1076
1077impl AttemptAdmission {
1078    pub(crate) fn monotonic_ns(&self) -> u64 {
1079        self.0.monotonic_ns()
1080    }
1081
1082    /// The attempt's report publication, which the admission's holder uses to
1083    /// seal the attempt's observation after it finishes the admission.
1084    pub(crate) fn report_publisher(&self) -> AttemptReportPublisher {
1085        AttemptReportPublisher(Arc::clone(&self.0.0))
1086    }
1087
1088    pub(crate) fn record_primary_failure(&self, summary: AttemptFailureSummary) {
1089        self.0.record_primary_failure(summary);
1090    }
1091
1092    /// Records the published grant and final queue state. The query service
1093    /// calls this under its admission mutex while publishing the envelope;
1094    /// it fails when the attempt owner committed a terminal outcome first.
1095    pub(crate) fn record_admitted(
1096        &self,
1097        fact: AttemptAdmissionFact,
1098        wait: Option<AttemptAdmissionWaitFact>,
1099    ) -> Result<(), AttemptLifecycleError> {
1100        let mut lifecycle = self.0.0.lock_lifecycle();
1101        lifecycle.admitted(fact, self.0.monotonic_ns())?;
1102        match wait {
1103            Some(wait) => lifecycle.admission_wait(wait),
1104            None => Ok(()),
1105        }
1106    }
1107
1108    pub(crate) fn is_admitted(&self) -> bool {
1109        self.0.0.lock_lifecycle().admission_fact().is_some()
1110    }
1111
1112    /// Ties the release of this admission's grant to the attempt's terminal
1113    /// outcome. Returns whether the outcome is already terminal, in which case
1114    /// the caller requests the release itself.
1115    pub(crate) fn bind_grant_release(&self, release: Arc<dyn AdmittedGrantRelease>) -> bool {
1116        let mut state = self.0.0.shared.lock();
1117        if state.lifecycle.terminal_outcome().is_some() {
1118            return true;
1119        }
1120        state.admitted_release = Some(release);
1121        false
1122    }
1123
1124    pub(crate) fn terminalize_for_release(
1125        &self,
1126        fallback: RuntimeTerminalOutcome,
1127    ) -> Result<RuntimeTerminalOutcome, AttemptLifecycleError> {
1128        self.0.terminalize_for_release(fallback)
1129    }
1130
1131    /// Finishes an admission that ended without a grant: rejection,
1132    /// cancellation, or queue timeout. A terminal outcome the owner already
1133    /// committed wins over `fallback`.
1134    pub(crate) fn finish_not_admitted(
1135        self,
1136        fallback: RuntimeTerminalOutcome,
1137    ) -> Result<RuntimeTerminalOutcome, AttemptLifecycleError> {
1138        self.0.finalize_not_admitted(fallback)
1139    }
1140
1141    /// Consumes the admission after GPU cleanup with its capacity and health
1142    /// facts.
1143    pub(crate) fn finish_admitted(self, commit: AttemptTerminalCommit) -> Result<(), AttemptLifecycleError> {
1144        self.0.finalize_admitted(commit)
1145    }
1146}
1147
1148impl AttemptHandle {
1149    /// The engine-minted identity of this invocation.
1150    pub fn attempt_id(&self) -> AttemptId {
1151        self.0.attempt_id
1152    }
1153
1154    /// Recording access to the attempt's observation, without seal authority.
1155    pub(crate) fn observation(&self) -> RuntimeObservationHandle {
1156        self.0.observation.handle()
1157    }
1158
1159    /// The attempt-scoped cooperative cancellation signal and deadline.
1160    pub fn cancellation(&self) -> AttemptCancellation {
1161        self.0.cancellation.clone()
1162    }
1163
1164    /// Returns whether the attempt outcome is already terminal.
1165    pub fn is_terminal(&self) -> bool {
1166        self.0.lock_lifecycle().terminal_outcome().is_some()
1167    }
1168
1169    /// The current disposition, when planning or execution has classified it.
1170    pub fn disposition(&self) -> Option<AttemptDisposition> {
1171        self.0.lock_lifecycle().recorded_disposition()
1172    }
1173
1174    pub fn record_planning_started(&self) -> Result<(), AttemptLifecycleError> {
1175        self.0.lock_lifecycle().planning_started(self.0.monotonic_ns())
1176    }
1177
1178    pub fn record_planning_finished(&self) -> Result<(), AttemptLifecycleError> {
1179        self.0.lock_lifecycle().planning_finished(self.0.monotonic_ns())
1180    }
1181
1182    pub fn set_disposition(&self, disposition: AttemptDisposition) -> Result<(), AttemptLifecycleError> {
1183        self.0.lock_lifecycle().disposition(disposition)
1184    }
1185
1186    /// Publishes the engine-owned composed schedule into this attempt's
1187    /// immutable report. The lifecycle verifies the projection's attempt
1188    /// placement against the already-recorded admission fact; callers cannot
1189    /// replace the schedule after finalization.
1190    pub fn record_composed_schedule_projection(
1191        &self,
1192        projection: AttemptComposedScheduleProjection,
1193    ) -> Result<(), AttemptLifecycleError> {
1194        if projection.attempt_id != self.0.attempt_id {
1195            return Err(AttemptLifecycleError::InvalidOrder {
1196                boundary: "composed_schedule",
1197                reason: "attempt_id_mismatch",
1198            });
1199        }
1200        self.0.lock_lifecycle().composed_schedule(projection)
1201    }
1202
1203    pub fn record_physical_execution_started(&self) -> Result<(), AttemptLifecycleError> {
1204        self.0.lock_lifecycle().physical(self.0.monotonic_ns())
1205    }
1206
1207    pub fn record_gpu_execution_started(&self) -> Result<(), AttemptLifecycleError> {
1208        self.0.lock_lifecycle().gpu(self.0.monotonic_ns())
1209    }
1210
1211    /// Retains the safe, closed projection of a borrowed native error without
1212    /// granting terminalization or sealing authority.
1213    ///
1214    /// The record-only capability is used by adapter paths whose admitted
1215    /// handle may begin cleanup before the outer DataFusion wrapper returns
1216    /// the original error to the attempt owner.  The full error remains on
1217    /// that returned path; this records only its bounded stable summary.
1218    pub fn record_native_failure(&self, error: &QueryError) {
1219        let operation = self.0.lock_lifecycle().failure_operation();
1220        if let Some(summary) = RuntimeTerminalOutcome::primary_failure_from_query_error(error, operation) {
1221            self.0.lock_lifecycle().primary_failure(summary);
1222        }
1223    }
1224
1225    /// Counts one secondary cleanup failure in the lifecycle before trying to
1226    /// retain its optional timeline projection. Timeline loss therefore never
1227    /// changes the sealed report's exact cleanup accounting.
1228    #[cfg(test)]
1229    pub(crate) fn record_cleanup_failure(&self, phase: CleanupFailurePhase, cleanup_error: &QueryError) {
1230        let summary = CleanupFailureSummary::from_native_error(phase, cleanup_error);
1231        let observation = self.0.observation.handle();
1232        observation.record_attempt_cleanup_failure(summary.clone());
1233        observation.record(|| {
1234            RuntimeObservationPayload::CleanupFailure(Box::new(RuntimeCleanupFailureObservation { summary }))
1235        });
1236    }
1237}
1238
1239impl AttemptCompletion {
1240    /// Returns whether the terminal report has been published.
1241    pub fn is_ready(&self) -> bool {
1242        self.shared.lock().completion.report_visible
1243    }
1244
1245    /// Returns the published report without waiting, when ready.
1246    pub fn try_report(&self) -> Option<Arc<QueryAttemptReport>> {
1247        self.shared.lock().completion.visible_report()
1248    }
1249
1250    /// Blocks the current thread until the report is published or the timeout
1251    /// elapses. Intended for synchronous tests and diagnostics.
1252    pub fn wait_timeout(&self, timeout: Duration) -> Option<Arc<QueryAttemptReport>> {
1253        // Spurious wakeups and the publication-side wait share `changed`, so
1254        // the predicate re-checks visibility on every wake.
1255        let state = self.shared.lock();
1256        let (state, _) = self
1257            .shared
1258            .changed
1259            .wait_timeout_while(state, timeout, |state| !state.completion.report_visible)
1260            .unwrap_or_else(sync::PoisonError::into_inner);
1261        state.completion.visible_report()
1262    }
1263}
1264
1265impl Future for AttemptCompletion {
1266    type Output = Arc<QueryAttemptReport>;
1267
1268    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1269        let mut state = self.shared.lock();
1270        let completion = &mut state.completion;
1271        if let Some(report) = completion.visible_report() {
1272            return Poll::Ready(report);
1273        }
1274        // Deduplicated with `will_wake` because one task polling repeatedly
1275        // would otherwise grow the list until publication.
1276        if !completion.wakers.iter().any(|waker| waker.will_wake(cx.waker())) {
1277            completion.wakers.push(cx.waker().clone());
1278        }
1279        Poll::Pending
1280    }
1281}
1282
1283impl Drop for AttemptCompletion {
1284    fn drop(&mut self) {
1285        let mut state = self.shared.lock();
1286        state.completion.receivers = state.completion.receivers.saturating_sub(1);
1287    }
1288}
1289
1290impl fmt::Debug for AttemptCompletion {
1291    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1292        formatter.debug_struct("AttemptCompletion").field("ready", &self.is_ready()).finish_non_exhaustive()
1293    }
1294}
1295
1296impl fmt::Debug for AttemptCompletionFactory {
1297    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1298        formatter.debug_struct("AttemptCompletionFactory").finish_non_exhaustive()
1299    }
1300}
1301
1302#[cfg(test)]
1303mod tests {
1304    use std::sync::mpsc;
1305
1306    use query_planning::error::{AttemptFailureCode, QueryError, QueryErrorCode};
1307
1308    use super::*;
1309    use crate::{
1310        observability::{
1311            AttemptDeviceHealth, AttemptDeviceHealthStatus, AttemptFailureFactKey, AttemptFailureFactValue,
1312            AttemptFailureOrigin, AttemptFailureStatus, AttemptFaultIsolation, AttemptFinalDomainUsage,
1313            AttemptHealthTransition, AttemptNativeFault, AttemptNativeFaultEvidence, AttemptNativeFaultKind,
1314            AttemptNativeStatus, AttemptTeardownHealth, DeterministicRuntimeObservationClock, PlanExecutionId,
1315            QueryBoundaryKind, RuntimeObservationConfig, RuntimeObservationRecord, SourceReadFact,
1316            write_runtime_observation_jsonl,
1317        },
1318        runtime::BackendLifecycle,
1319    };
1320
1321    fn test_begin(
1322        observation: RuntimeObservationConfig,
1323        queue: Option<Arc<AttemptCompletionQueue>>,
1324    ) -> (AttemptBegin, Arc<DeterministicRuntimeObservationClock>, Arc<AttemptBackendCounters>) {
1325        let lifecycle = BackendLifecycle::new();
1326        let permit = lifecycle.try_begin_observation().expect("test permit");
1327        let counters = Arc::new(AttemptBackendCounters::default());
1328        let clock = Arc::new(DeterministicRuntimeObservationClock::new(Some(42)));
1329        let begin = AttemptOwner::begin_with_clock(
1330            BeginAttemptRequest { observation, ..BeginAttemptRequest::default() },
1331            permit,
1332            Arc::clone(&counters),
1333            queue,
1334            Arc::clone(&clock) as _,
1335        );
1336        (begin, clock, counters)
1337    }
1338
1339    fn native_disposition(begin: &AttemptBegin) {
1340        let handle = begin.owner.handle();
1341        handle.record_planning_started().unwrap();
1342        handle.record_planning_finished().unwrap();
1343        handle.set_disposition(AttemptDisposition::Native).unwrap();
1344    }
1345
1346    /// Seals the attempt's observation after GPU cleanup the way the query
1347    /// service does: with the lifecycle's resolved outcome and the attempt's
1348    /// report publisher.
1349    fn seal_after_cleanup(begin: &AttemptBegin, publisher: &AttemptReportPublisher, fallback: RuntimeTerminalOutcome) {
1350        let outcome = begin.owner.0.core.canonical_terminal_outcome(fallback);
1351        let _ = publisher.seal(outcome).deliver();
1352    }
1353
1354    fn claim_admitted(begin: &AttemptBegin, fact: AttemptAdmissionFact) -> AttemptAdmission {
1355        let admission = begin.owner.admission_binding().claim().unwrap();
1356        admission.record_admitted(fact, None).unwrap();
1357        admission
1358    }
1359
1360    #[test]
1361    fn non_admitted_commit_publishes_immediately_in_order() {
1362        let (begin, clock, counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1363        let completion = begin.completion.completion();
1364        native_disposition(&begin);
1365        clock.set_monotonic_ns(10);
1366        begin.owner.commit_terminal(RuntimeTerminalOutcome::Succeeded).unwrap();
1367        let report = completion.wait_timeout(Duration::from_secs(1)).expect("report published");
1368        assert!(matches!(report.outcome, RuntimeTerminalOutcome::Succeeded));
1369        assert_eq!(report.disposition, AttemptDisposition::Native);
1370        assert_eq!(report.cleanup_disposition, AttemptCleanupDisposition::NotRequired);
1371        assert_eq!(report.capacity_disposition, AttemptCapacityDisposition::NotAdmitted);
1372        assert_eq!(report.final_domain_usage, None, "unadmitted attempts have no final domain sample");
1373        assert_eq!(report.health_transition, None, "unadmitted attempts have no release health transition");
1374        assert_eq!(report.timestamps.cleanup_complete_ns, None);
1375        assert_eq!(report.timestamps.report_ready_ns, Some(10));
1376        assert_eq!(report.timestamps.outcome_terminal_ns, 10);
1377        assert_eq!(counters.snapshot().reports_ready, 1);
1378        // The sealed observation and completion share one immutable report.
1379        let snapshot = begin.owner.sealed_snapshot_for_test().expect("sealed");
1380        let embedded = snapshot.attempt_report().expect("attempt report embedded");
1381        assert_eq!(embedded, report.as_ref());
1382        assert!(Arc::ptr_eq(
1383            snapshot.footer().attempt_report.as_ref().expect("attempt report allocation embedded"),
1384            &report
1385        ));
1386        let mut jsonl = Vec::new();
1387        write_runtime_observation_jsonl(&snapshot, &mut jsonl).expect("attempt JSONL exports");
1388        let exported_report = std::str::from_utf8(&jsonl)
1389            .expect("attempt JSONL is UTF-8")
1390            .lines()
1391            .map(|line| serde_json::from_str::<RuntimeObservationRecord>(line).expect("attempt JSONL record parses"))
1392            .find_map(|record| match record {
1393                RuntimeObservationRecord::Footer(footer) => footer.attempt_report,
1394                _ => None,
1395            })
1396            .expect("JSONL footer retains the attempt report");
1397        assert_eq!(exported_report.as_ref(), report.as_ref());
1398    }
1399
1400    #[test]
1401    fn sealed_report_sink_receives_the_exact_report_before_completion_visibility() {
1402        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1403        let factory = begin.completion.clone();
1404        let completion_for_sink = factory.clone();
1405        let completion = factory.completion();
1406        let delivered = Arc::new(Mutex::new(Vec::new()));
1407        let delivered_for_sink = Arc::clone(&delivered);
1408        let observed_before_completion = Arc::new(std::sync::atomic::AtomicBool::new(false));
1409        let observed_before_completion_for_sink = Arc::clone(&observed_before_completion);
1410        begin.owner.register_sealed_report_sink(move |report| {
1411            observed_before_completion_for_sink
1412                .store(completion_for_sink.try_report().is_none(), std::sync::atomic::Ordering::Release);
1413            delivered_for_sink.lock().unwrap().push(report);
1414        });
1415
1416        native_disposition(&begin);
1417        begin.owner.commit_terminal(RuntimeTerminalOutcome::Succeeded).unwrap();
1418
1419        let report = completion.try_report().expect("completion is published after sink delivery");
1420        let delivered = delivered.lock().unwrap();
1421        assert_eq!(delivered.len(), 1);
1422        assert!(Arc::ptr_eq(&delivered[0], &report));
1423        assert!(observed_before_completion.load(std::sync::atomic::Ordering::Acquire));
1424    }
1425
1426    #[test]
1427    fn sealed_report_sink_can_write_back_to_its_attempt_observation() {
1428        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1429        let completion = begin.completion.completion();
1430        let observation = begin.owner.observation_for_test();
1431        begin.owner.register_sealed_report_sink(move |_report| {
1432            observation.record_source_read(&PlanExecutionId::new("late-plan"), SourceReadFact::InputBatch { rows: 1 });
1433        });
1434        native_disposition(&begin);
1435
1436        let (sealed, seal_finished) = mpsc::channel();
1437        let owner = begin.owner;
1438        thread::spawn(move || sealed.send(owner.commit_terminal(RuntimeTerminalOutcome::Succeeded)));
1439        seal_finished
1440            .recv_timeout(Duration::from_secs(5))
1441            .expect("the seal finishes while its report sink writes to the same observation")
1442            .unwrap();
1443
1444        assert!(completion.try_report().is_some());
1445    }
1446
1447    #[test]
1448    fn seal_uses_the_attempt_terminal_outcome_for_footer_report_and_boundary() {
1449        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1450        let completion = begin.completion.completion();
1451        native_disposition(&begin);
1452
1453        begin.owner.commit_terminal(RuntimeTerminalOutcome::Cancelled).unwrap();
1454        let report = completion.wait_timeout(Duration::from_secs(1)).expect("report published");
1455        let snapshot = begin.owner.sealed_snapshot_for_test().expect("sealed");
1456
1457        assert_eq!(report.outcome, RuntimeTerminalOutcome::Cancelled);
1458        assert_eq!(snapshot.footer().terminal, Some(RuntimeTerminalOutcome::Cancelled));
1459        assert_eq!(snapshot.attempt_report().expect("attempt report embedded"), report.as_ref());
1460        assert!(snapshot.events().iter().any(|event| {
1461            matches!(event.payload, RuntimeObservationPayload::QueryBoundary { boundary: QueryBoundaryKind::Cancelled })
1462        }));
1463    }
1464
1465    #[test]
1466    fn admitted_commit_defers_until_post_cleanup_seal() {
1467        let (begin, clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1468        let completion = begin.completion.completion();
1469        native_disposition(&begin);
1470        let handle = begin.owner.handle();
1471        let admission = claim_admitted(
1472            &begin,
1473            AttemptAdmissionFact { admission_sequence: 3, admission_wait_ns: 1, device_ordinal: 1, grant_bytes: 1024 },
1474        );
1475        let publisher = admission.report_publisher();
1476        handle.record_physical_execution_started().unwrap();
1477        handle.record_gpu_execution_started().unwrap();
1478        clock.set_monotonic_ns(20);
1479        begin.owner.commit_terminal(RuntimeTerminalOutcome::Succeeded).unwrap();
1480        assert!(!completion.is_ready(), "admitted attempt must wait for the post-cleanup seal");
1481        // The engine seals the observation owner after GPU cleanup.
1482        clock.set_monotonic_ns(30);
1483        let final_domain_usage = AttemptFinalDomainUsage::from(cudf::QueryMemoryDomainUsage {
1484            domain_id: 101,
1485            active: false,
1486            active_members: 0,
1487            ceiling_bytes: 1024,
1488            allocation_limit_bytes: 768,
1489            current_bytes: 0,
1490            peak_bytes: 512,
1491            total_bytes: 1024,
1492            current_aligned_bytes: 0,
1493            peak_aligned_bytes: 640,
1494            total_allocations: 9,
1495            current_allocations: 0,
1496            peak_allocations: 4,
1497            failed_allocation_count: 2,
1498            largest_failed_allocation_bytes: 64,
1499        });
1500        admission
1501            .finish_admitted(AttemptTerminalCommit::new(
1502                AttemptCapacityDisposition::Released,
1503                Some(final_domain_usage),
1504                AttemptHealthTransition::HealthyReuse,
1505            ))
1506            .unwrap();
1507        seal_after_cleanup(&begin, &publisher, RuntimeTerminalOutcome::Succeeded);
1508        let report = completion.wait_timeout(Duration::from_secs(1)).expect("report published after seal");
1509        assert_eq!(report.timestamps.outcome_terminal_ns, 20);
1510        assert_eq!(report.timestamps.cleanup_complete_ns, Some(30));
1511        assert_eq!(report.timestamps.report_ready_ns, Some(30));
1512        assert_eq!(report.device_ordinal, Some(1));
1513        assert_eq!(report.cleanup_disposition, AttemptCleanupDisposition::Completed);
1514        assert_eq!(report.capacity_disposition, AttemptCapacityDisposition::Released);
1515        assert_eq!(report.final_domain_usage, Some(final_domain_usage), "report preserves every final sample scalar");
1516        assert_eq!(report.health_transition, Some(AttemptHealthTransition::HealthyReuse));
1517        assert_eq!(report.admission.as_ref().map(|fact| fact.grant_bytes), Some(1024));
1518        let snapshot = begin.owner.sealed_snapshot_for_test().expect("sealed");
1519        assert_eq!(snapshot.attempt_report().expect("attempt report embedded"), report.as_ref());
1520    }
1521
1522    #[test]
1523    fn admitted_terminal_commit_projects_typed_native_fault_evidence() {
1524        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1525        native_disposition(&begin);
1526        let admission = claim_admitted(
1527            &begin,
1528            AttemptAdmissionFact { admission_sequence: 3, admission_wait_ns: 1, device_ordinal: 1, grant_bytes: 1024 },
1529        );
1530        let publisher = admission.report_publisher();
1531        let native_fault_evidence = AttemptNativeFaultEvidence {
1532            isolation: AttemptFaultIsolation::DeviceLocal,
1533            teardown_health: AttemptTeardownHealth::Completed,
1534            device_health: Some(AttemptDeviceHealth {
1535                status: AttemptDeviceHealthStatus::Unhealthy,
1536                fault_count: 1,
1537                rejected_operation_count: 0,
1538                quarantined_teardown_count: 0,
1539            }),
1540            first_fault: Some(AttemptNativeFault {
1541                kind: AttemptNativeFaultKind::FatalDeviceOperation,
1542                // The cuDF ABI is unsigned; this sentinel guards against
1543                // narrowing it through an i32-only report field.
1544                status: Some(AttemptNativeStatus::Cudf(u32::MAX)),
1545            }),
1546        };
1547        begin.owner.commit_terminal(RuntimeTerminalOutcome::Succeeded).unwrap();
1548        admission
1549            .finish_admitted(
1550                AttemptTerminalCommit::new(
1551                    AttemptCapacityDisposition::RetainedQuarantine,
1552                    None,
1553                    AttemptHealthTransition::DeviceQuarantined,
1554                )
1555                .with_native_fault_evidence(native_fault_evidence),
1556            )
1557            .unwrap();
1558        seal_after_cleanup(&begin, &publisher, RuntimeTerminalOutcome::Succeeded);
1559
1560        let report = begin.completion.completion().wait_timeout(Duration::from_secs(1)).expect("report");
1561        assert_eq!(report.capacity_disposition, AttemptCapacityDisposition::RetainedQuarantine);
1562        assert_eq!(report.health_transition, Some(AttemptHealthTransition::DeviceQuarantined));
1563        assert_eq!(report.native_fault_evidence, Some(native_fault_evidence));
1564    }
1565
1566    #[test]
1567    fn terminal_commit_projects_actual_cleanup_and_capacity_before_seal() {
1568        let (begin, clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1569        let completion = begin.completion.completion();
1570        native_disposition(&begin);
1571        let admission = claim_admitted(
1572            &begin,
1573            AttemptAdmissionFact { admission_sequence: 3, admission_wait_ns: 1, device_ordinal: 1, grant_bytes: 1024 },
1574        );
1575        let publisher = admission.report_publisher();
1576        clock.set_monotonic_ns(20);
1577        begin.owner.commit_terminal(RuntimeTerminalOutcome::Succeeded).unwrap();
1578        clock.set_monotonic_ns(30);
1579        admission
1580            .finish_admitted(AttemptTerminalCommit::new(
1581                AttemptCapacityDisposition::RetainedQuarantine,
1582                None,
1583                AttemptHealthTransition::BackendUnhealthy,
1584            ))
1585            .unwrap();
1586        assert!(
1587            matches!(begin.owner.admission_binding().claim(), Err(AttemptLifecycleError::AdmissionAlreadyClaimed)),
1588            "the admission right is linear and cannot be claimed a second time"
1589        );
1590        seal_after_cleanup(&begin, &publisher, RuntimeTerminalOutcome::Succeeded);
1591        let report = completion.wait_timeout(Duration::from_secs(1)).expect("report");
1592        assert!(matches!(report.outcome, RuntimeTerminalOutcome::Succeeded));
1593        assert_eq!(report.cleanup_disposition, AttemptCleanupDisposition::Completed);
1594        assert_eq!(report.cleanup_failure_count, 0);
1595        assert!(report.cleanup_failure_count_complete);
1596        assert!(report.first_cleanup_failure.is_none());
1597        assert_eq!(report.capacity_disposition, AttemptCapacityDisposition::RetainedQuarantine);
1598        assert_eq!(report.final_domain_usage, None, "unavailable sample is never replaced with all-zero usage");
1599        assert_eq!(report.health_transition, Some(AttemptHealthTransition::BackendUnhealthy));
1600        assert_eq!(report.timestamps.cleanup_complete_ns, Some(30));
1601        assert_eq!(report.timestamps.report_ready_ns, Some(30));
1602        let snapshot = begin.owner.sealed_snapshot_for_test().expect("sealed");
1603        assert_eq!(snapshot.attempt_report().expect("attempt report embedded"), report.as_ref());
1604    }
1605
1606    #[test]
1607    fn double_terminalization_is_exactly_once() {
1608        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1609        begin.owner.commit_terminal(RuntimeTerminalOutcome::Dropped).unwrap();
1610        assert_eq!(
1611            begin.owner.commit_terminal(RuntimeTerminalOutcome::Succeeded),
1612            Err(AttemptLifecycleError::DoubleTerminalization)
1613        );
1614        let report = begin.completion.completion().wait_timeout(Duration::from_secs(1)).expect("report");
1615        assert!(matches!(report.outcome, RuntimeTerminalOutcome::Dropped));
1616    }
1617
1618    #[test]
1619    fn dropping_a_cancelled_owner_commits_the_cancellation() {
1620        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1621        let completion = begin.completion.completion();
1622        begin.owner.cancellation().cancel();
1623        drop(begin.owner);
1624        let report = completion.wait_timeout(Duration::from_secs(1)).expect("drop publishes the report");
1625        assert_eq!(report.outcome, RuntimeTerminalOutcome::Cancelled);
1626    }
1627
1628    #[test]
1629    fn owner_drop_backstop_never_loses_the_report() {
1630        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1631        let completion = begin.completion.completion();
1632        drop(begin.owner);
1633        let report = completion.wait_timeout(Duration::from_secs(1)).expect("drop publishes the report");
1634        assert!(matches!(report.outcome, RuntimeTerminalOutcome::Dropped));
1635    }
1636
1637    #[test]
1638    fn fixed_summary_survives_disabled_and_zero_limit_diagnostics() {
1639        for observation in [RuntimeObservationConfig::disabled(), RuntimeObservationConfig::bounded(0, 0)] {
1640            let (begin, _clock, _counters) = test_begin(observation, None);
1641            let completion = begin.completion.completion();
1642            native_disposition(&begin);
1643            begin
1644                .owner
1645                .commit_terminal(RuntimeTerminalOutcome::Failed {
1646                    error_code: AttemptFailureCode::Query(QueryErrorCode::Internal),
1647                    error_status: AttemptFailureStatus::Permanent,
1648                })
1649                .unwrap();
1650            let report = completion.wait_timeout(Duration::from_secs(1)).expect("report");
1651            assert_eq!(report.disposition, AttemptDisposition::Native);
1652            assert!(matches!(report.outcome, RuntimeTerminalOutcome::Failed { .. }));
1653            assert!(report.timestamps.report_ready_ns.is_some());
1654            assert_eq!(report.cleanup_disposition, AttemptCleanupDisposition::NotRequired);
1655        }
1656    }
1657
1658    #[test]
1659    fn receiver_abandonment_still_publishes_and_overflow_is_counted() {
1660        let queue = Arc::new(AttemptCompletionQueue::new(1).expect("queue"));
1661        let (begin, _clock, counters) =
1662            test_begin(RuntimeObservationConfig::native_execution(), Some(Arc::clone(&queue)));
1663        drop(begin.completion.completion());
1664        begin.owner.commit_terminal(RuntimeTerminalOutcome::Succeeded).unwrap();
1665        assert_eq!(counters.snapshot().reports_ready, 1);
1666        assert_eq!(counters.snapshot().completion_queue_pushed, 1);
1667        assert_eq!(counters.snapshot().completion_queue_overflowed, 0);
1668        let drained = queue.drain(8);
1669        assert_eq!(drained.len(), 1);
1670        assert!(matches!(drained[0].outcome, RuntimeTerminalOutcome::Succeeded));
1671
1672        // The second abandoned report fills the capacity-one queue.
1673        let (begin2, _clock2, counters2) =
1674            test_begin(RuntimeObservationConfig::native_execution(), Some(Arc::clone(&queue)));
1675        drop(begin2.completion.completion());
1676        begin2.owner.commit_terminal(RuntimeTerminalOutcome::Dropped).unwrap();
1677        assert_eq!(counters2.snapshot().completion_queue_pushed, 1);
1678        let (begin3, _clock3, counters3) =
1679            test_begin(RuntimeObservationConfig::native_execution(), Some(Arc::clone(&queue)));
1680        drop(begin3.completion.completion());
1681        begin3.owner.commit_terminal(RuntimeTerminalOutcome::Dropped).unwrap();
1682        assert_eq!(counters3.snapshot().completion_queue_overflowed, 1);
1683        // The capacity-one queue still holds the second report; the third was
1684        // counted as delivery loss without blocking the commit.
1685        assert_eq!(queue.drain(8).len(), 1);
1686    }
1687
1688    #[test]
1689    fn contended_completion_push_blocks_for_capacity_instead_of_reporting_overflow() {
1690        let queue = Arc::new(AttemptCompletionQueue::new(4).expect("queue"));
1691        let (begin, _clock, _counters) =
1692            test_begin(RuntimeObservationConfig::native_execution(), Some(Arc::clone(&queue)));
1693        drop(begin.completion.completion());
1694        begin.owner.commit_terminal(RuntimeTerminalOutcome::Succeeded).unwrap();
1695        let report = queue.drain(8).pop().expect("abandoned report");
1696        // Hold the queue mutex while a pusher arrives: with spare capacity, the
1697        // push waits for the lock and does not fail as if the queue were full.
1698        // The barrier plus hold window prove the pusher attempted its push while
1699        // contended.
1700        let guard = queue.hold_lock_for_test();
1701        let barrier = sync::Barrier::new(2);
1702        let (pushed_tx, pushed_rx) = sync::mpsc::channel();
1703        std::thread::scope(|scope| {
1704            scope.spawn(|| {
1705                barrier.wait();
1706                pushed_tx.send(queue.try_push(Arc::clone(&report))).expect("push result");
1707            });
1708            barrier.wait();
1709            std::thread::sleep(Duration::from_millis(20));
1710            assert!(pushed_rx.try_recv().is_err(), "contended push must block, not fail fast");
1711            drop(guard);
1712            assert!(pushed_rx.recv().expect("push result after release"));
1713        });
1714        assert_eq!(queue.drain(8).len(), 1);
1715    }
1716
1717    #[test]
1718    fn held_receivers_do_not_route_to_the_abandoned_queue() {
1719        let queue = Arc::new(AttemptCompletionQueue::new(4).expect("queue"));
1720        let (begin, _clock, counters) =
1721            test_begin(RuntimeObservationConfig::native_execution(), Some(Arc::clone(&queue)));
1722        let _held = begin.completion.completion();
1723        begin.owner.commit_terminal(RuntimeTerminalOutcome::Succeeded).unwrap();
1724        assert_eq!(counters.snapshot().completion_queue_pushed, 0);
1725        assert!(queue.drain(4).is_empty());
1726        assert!(_held.is_ready());
1727    }
1728
1729    #[test]
1730    fn cleanup_failures_are_counted_exactly_without_replacing_the_primary_outcome() {
1731        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1732        native_disposition(&begin);
1733        let handle = begin.owner.handle();
1734        let admission = claim_admitted(
1735            &begin,
1736            AttemptAdmissionFact { admission_sequence: 1, admission_wait_ns: 0, device_ordinal: 0, grant_bytes: 1 },
1737        );
1738        let publisher = admission.report_publisher();
1739        let primary_error =
1740            QueryError::new(QueryErrorCode::CudfTemporaryFailure, "test.primary", "primary native failure");
1741        begin.owner.commit_query_error(&primary_error).unwrap();
1742        let cleanup_error = QueryError::new(QueryErrorCode::Internal, "test.cleanup", "injected cleanup failure");
1743        handle.record_cleanup_failure(CleanupFailurePhase::QueryServiceCleanup, &cleanup_error);
1744        handle.record_cleanup_failure(CleanupFailurePhase::QueryServiceCleanup, &cleanup_error);
1745        admission
1746            .finish_admitted(AttemptTerminalCommit::new(
1747                AttemptCapacityDisposition::Released,
1748                None,
1749                AttemptHealthTransition::HealthyReuse,
1750            ))
1751            .unwrap();
1752        seal_after_cleanup(
1753            &begin,
1754            &publisher,
1755            RuntimeTerminalOutcome::Failed {
1756                error_code: AttemptFailureCode::Query(QueryErrorCode::CudfTemporaryFailure),
1757                error_status: AttemptFailureStatus::Temporary,
1758            },
1759        );
1760        let report = begin.completion.completion().wait_timeout(Duration::from_secs(1)).expect("report");
1761        assert_eq!(report.cleanup_failure_count, 2);
1762        assert!(report.cleanup_failure_count_complete);
1763        assert_eq!(report.cleanup_disposition, AttemptCleanupDisposition::CompletedWithFailures);
1764        assert!(matches!(
1765            report.outcome,
1766            RuntimeTerminalOutcome::Failed { ref error_code, .. }
1767                if *error_code == AttemptFailureCode::Query(QueryErrorCode::CudfTemporaryFailure)
1768        ));
1769        let primary = report.primary_failure.as_ref().expect("primary failure summary");
1770        assert_eq!(primary.code(), AttemptFailureCode::Query(QueryErrorCode::CudfTemporaryFailure));
1771        assert!(primary.text().is_none(), "native error text must not enter the stable report");
1772        let first_cleanup = report.first_cleanup_failure.as_ref().expect("first cleanup failure summary");
1773        assert_eq!(first_cleanup.phase(), CleanupFailurePhase::QueryServiceCleanup);
1774        assert_eq!(first_cleanup.failure().code(), AttemptFailureCode::Query(QueryErrorCode::Internal));
1775        assert!(!first_cleanup.complete(), "the first retained summary is incomplete after a later cleanup failure");
1776        // The bounded timeline carries the same facts when capture allows.
1777        let snapshot = begin.owner.sealed_snapshot_for_test().expect("sealed");
1778        let cleanup_events = snapshot
1779            .events()
1780            .iter()
1781            .filter(|event| matches!(event.payload, RuntimeObservationPayload::CleanupFailure(_)))
1782            .count();
1783        assert_eq!(cleanup_events, 2);
1784    }
1785
1786    #[test]
1787    fn borrowed_primary_capture_preserves_the_returned_error_source_chain() {
1788        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1789        native_disposition(&begin);
1790        let error = QueryError::new(QueryErrorCode::CudfTemporaryFailure, "test.primary", "native failure")
1791            .with_source(std::io::Error::other("original source cause"));
1792        assert_eq!(error.source_error().map(ToString::to_string).as_deref(), Some("original source cause"));
1793
1794        begin.owner.commit_query_error(&error).unwrap();
1795
1796        assert_eq!(error.source_error().map(ToString::to_string).as_deref(), Some("original source cause"));
1797        let report = begin.completion.completion().wait_timeout(Duration::from_secs(1)).expect("report");
1798        let summary = report.primary_failure.as_ref().expect("borrowed native summary");
1799        assert_eq!(summary.code(), AttemptFailureCode::Query(QueryErrorCode::CudfTemporaryFailure));
1800        assert_eq!(summary.status(), AttemptFailureStatus::Temporary);
1801        assert!(summary.text().is_none(), "the returned error, not the report, retains native message text");
1802    }
1803
1804    #[test]
1805    fn borrowed_primary_capture_wins_when_admitted_cleanup_finalizes_before_outer_error_handling() {
1806        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1807        native_disposition(&begin);
1808        let handle = begin.owner.handle();
1809        let admission = claim_admitted(
1810            &begin,
1811            AttemptAdmissionFact { admission_sequence: 1, admission_wait_ns: 0, device_ordinal: 0, grant_bytes: 1 },
1812        );
1813        let publisher = admission.report_publisher();
1814
1815        // This models a synchronous `input.execute` failure. The adapter still
1816        // borrows the native error while dropping the admitted handle starts
1817        // cleanup and finalization before the outer DataFusion boundary can
1818        // commit its own generic fallback.
1819        let error = QueryError::new(
1820            QueryErrorCode::CudfTemporaryFailure,
1821            "test.native_admission_execute",
1822            "application-specific native failure text must remain on the returned error",
1823        )
1824        .with_fact("reason", "deadline_expired")
1825        .with_source(std::io::Error::other("original source cause"));
1826        handle.record_native_failure(&error);
1827
1828        let terminal = admission.terminalize_for_release(RuntimeTerminalOutcome::Dropped).unwrap();
1829        admission
1830            .finish_admitted(AttemptTerminalCommit::new(
1831                AttemptCapacityDisposition::Released,
1832                None,
1833                AttemptHealthTransition::HealthyReuse,
1834            ))
1835            .unwrap();
1836        seal_after_cleanup(&begin, &publisher, terminal);
1837
1838        assert_eq!(error.source_error().map(ToString::to_string).as_deref(), Some("original source cause"));
1839        let report = begin.completion.completion().wait_timeout(Duration::from_secs(1)).expect("report");
1840        assert!(matches!(
1841            report.outcome,
1842            RuntimeTerminalOutcome::Failed {
1843                error_code: AttemptFailureCode::Query(QueryErrorCode::CudfTemporaryFailure),
1844                error_status: AttemptFailureStatus::Temporary,
1845            }
1846        ));
1847        let summary = report.primary_failure.as_ref().expect("captured native primary summary");
1848        assert_eq!(summary.code(), AttemptFailureCode::Query(QueryErrorCode::CudfTemporaryFailure));
1849        assert_eq!(summary.status(), AttemptFailureStatus::Temporary);
1850        assert_eq!(summary.operation(), AttemptFailureOperation::Execution);
1851        assert_eq!(summary.facts().len(), 1);
1852        assert_eq!(summary.facts()[0].key(), AttemptFailureFactKey::Reason);
1853        assert_eq!(summary.facts()[0].value(), AttemptFailureFactValue::DeadlineExpired);
1854        assert!(summary.text().is_none());
1855    }
1856
1857    #[test]
1858    fn execution_failure_with_plan_in_operation_seals_as_execution() {
1859        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1860        native_disposition(&begin);
1861        let handle = begin.owner.handle();
1862        let admission = claim_admitted(
1863            &begin,
1864            AttemptAdmissionFact { admission_sequence: 1, admission_wait_ns: 0, device_ordinal: 0, grant_bytes: 1 },
1865        );
1866        let publisher = admission.report_publisher();
1867
1868        let error = QueryError::new(
1869            QueryErrorCode::CudfTemporaryFailure,
1870            "query_engine.exec.execute_query_plan",
1871            "execution failure",
1872        );
1873        handle.record_native_failure(&error);
1874
1875        let terminal = admission.terminalize_for_release(RuntimeTerminalOutcome::Dropped).unwrap();
1876        admission
1877            .finish_admitted(AttemptTerminalCommit::new(
1878                AttemptCapacityDisposition::Released,
1879                None,
1880                AttemptHealthTransition::HealthyReuse,
1881            ))
1882            .unwrap();
1883        seal_after_cleanup(&begin, &publisher, terminal);
1884
1885        let report = begin.completion.completion().wait_timeout(Duration::from_secs(1)).expect("report");
1886        let summary = report.primary_failure.as_ref().expect("captured native primary summary");
1887        assert_eq!(summary.operation(), AttemptFailureOperation::Execution);
1888    }
1889
1890    #[test]
1891    fn cleanup_facts_survive_a_full_timeline_ring() {
1892        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::bounded(0, 0), None);
1893        native_disposition(&begin);
1894        let handle = begin.owner.handle();
1895        let admission = claim_admitted(
1896            &begin,
1897            AttemptAdmissionFact { admission_sequence: 1, admission_wait_ns: 0, device_ordinal: 0, grant_bytes: 1 },
1898        );
1899        let publisher = admission.report_publisher();
1900        begin.owner.commit_terminal(RuntimeTerminalOutcome::Succeeded).unwrap();
1901        let cleanup_error = QueryError::new(QueryErrorCode::Internal, "test.cleanup", "ring-full cleanup failure");
1902        handle.record_cleanup_failure(CleanupFailurePhase::QueryServiceCleanup, &cleanup_error);
1903        admission
1904            .finish_admitted(AttemptTerminalCommit::new(
1905                AttemptCapacityDisposition::Released,
1906                None,
1907                AttemptHealthTransition::HealthyReuse,
1908            ))
1909            .unwrap();
1910        seal_after_cleanup(&begin, &publisher, RuntimeTerminalOutcome::Succeeded);
1911
1912        let report = begin.completion.completion().wait_timeout(Duration::from_secs(1)).expect("report");
1913        assert_eq!(report.cleanup_failure_count, 1);
1914        assert!(report.cleanup_failure_count_complete);
1915        assert_eq!(
1916            report.first_cleanup_failure.as_ref().map(CleanupFailureSummary::phase),
1917            Some(CleanupFailurePhase::QueryServiceCleanup)
1918        );
1919        let snapshot = begin.owner.sealed_snapshot_for_test().expect("sealed");
1920        assert!(
1921            snapshot
1922                .events()
1923                .iter()
1924                .all(|event| !matches!(event.payload, RuntimeObservationPayload::CleanupFailure(_)))
1925        );
1926    }
1927
1928    #[test]
1929    fn deadline_expired_cancelled_error_retains_its_bounded_reason() {
1930        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1931        let error = QueryError::new(QueryErrorCode::Cancelled, "test.deadline", "deadline elapsed")
1932            .with_fact("reason", "deadline_expired")
1933            .with_fact("cancellation_requested", true);
1934
1935        begin.owner.commit_query_error(&error).unwrap();
1936
1937        let report = begin.completion.completion().wait_timeout(Duration::from_secs(1)).expect("report");
1938        assert_eq!(report.outcome, RuntimeTerminalOutcome::Cancelled);
1939        let summary = report.primary_failure.as_ref().expect("deadline summary");
1940        assert_eq!(summary.code(), AttemptFailureCode::Query(QueryErrorCode::Cancelled));
1941        assert_eq!(summary.origin(), AttemptFailureOrigin::Native);
1942        assert_eq!(summary.facts().len(), 1);
1943        assert_eq!(summary.facts()[0].value(), AttemptFailureFactValue::DeadlineExpired);
1944    }
1945
1946    #[test]
1947    fn caller_and_generic_cancellation_keep_the_control_summary() {
1948        for error in [
1949            QueryError::new(QueryErrorCode::Cancelled, "test.caller_cancelled", "caller cancelled")
1950                .with_fact("reason", "caller_cancelled"),
1951            QueryError::new(QueryErrorCode::Cancelled, "test.generic_cancelled", "cancelled"),
1952        ] {
1953            let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1954            begin.owner.commit_query_error(&error).unwrap();
1955
1956            let report = begin.completion.completion().wait_timeout(Duration::from_secs(1)).expect("report");
1957            assert_eq!(report.outcome, RuntimeTerminalOutcome::Cancelled);
1958            let summary = report.primary_failure.as_ref().expect("control cancellation summary");
1959            assert_eq!(summary.code(), AttemptFailureCode::Query(QueryErrorCode::Cancelled));
1960            assert_eq!(summary.origin(), AttemptFailureOrigin::AttemptControl);
1961            assert!(summary.facts().is_empty());
1962        }
1963    }
1964
1965    #[test]
1966    fn control_terminal_paths_publish_text_free_primary_summaries() {
1967        for (outcome, code) in [
1968            (RuntimeTerminalOutcome::Cancelled, AttemptFailureCode::Query(QueryErrorCode::Cancelled)),
1969            (RuntimeTerminalOutcome::Dropped, AttemptFailureCode::Dropped),
1970            (RuntimeTerminalOutcome::Panicked, AttemptFailureCode::Panicked),
1971        ] {
1972            let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1973            begin.owner.commit_terminal(outcome).unwrap();
1974            let report = begin.completion.completion().wait_timeout(Duration::from_secs(1)).expect("report");
1975            let summary = report.primary_failure.as_ref().expect("control summary");
1976            assert_eq!(summary.code(), code);
1977            assert_eq!(summary.origin(), AttemptFailureOrigin::AttemptControl);
1978            assert_eq!(summary.status(), AttemptFailureStatus::NotApplicable);
1979            assert!(summary.text().is_none());
1980        }
1981    }
1982
1983    #[test]
1984    fn cancellation_projects_into_the_engine_execution_signal() {
1985        let (begin, _clock, _counters) = test_begin(RuntimeObservationConfig::native_execution(), None);
1986        let cancellation = begin.owner.cancellation();
1987        assert!(!cancellation.is_cancelled());
1988        cancellation.cancel();
1989        assert!(cancellation.is_cancelled());
1990        assert!(cancellation.execution_cancellation().is_cancelled());
1991        assert!(cancellation.check("test.operation").is_err());
1992    }
1993}
1994
1995#[cfg(test)]
1996mod terminalize_race_tests {
1997    use query_planning::error::{AttemptFailureCode, QueryErrorCode};
1998
1999    use super::*;
2000    use crate::{
2001        observability::{AttemptFailureStatus, DeterministicRuntimeObservationClock},
2002        runtime::BackendLifecycle,
2003    };
2004
2005    #[test]
2006    fn admission_rejection_terminalized_at_the_binding_wins_over_the_wrapper_commit() {
2007        let lifecycle = BackendLifecycle::new();
2008        let permit = lifecycle.try_begin_observation().expect("test permit");
2009        let counters = Arc::new(AttemptBackendCounters::default());
2010        let clock = Arc::new(DeterministicRuntimeObservationClock::new(Some(1)));
2011        let begin = AttemptOwner::begin_with_clock(BeginAttemptRequest::default(), permit, counters, None, clock);
2012        let handle = begin.owner.handle();
2013        let admission_rejection = begin.owner.admission_rejection();
2014        handle.record_planning_started().unwrap();
2015        handle.record_planning_finished().unwrap();
2016        handle.set_disposition(AttemptDisposition::Native).unwrap();
2017        // The admission rejection terminalizes the request at its source.
2018        admission_rejection
2019            .reject(&QueryError::new(
2020                QueryErrorCode::ServiceOverloaded,
2021                "test.admission_rejection",
2022                "injected admission rejection",
2023            ))
2024            .unwrap();
2025        // The wrapper's later commit is refused with DoubleTerminalization.
2026        assert_eq!(
2027            begin.owner.commit_terminal(RuntimeTerminalOutcome::Failed {
2028                error_code: AttemptFailureCode::DatafusionError,
2029                error_status: AttemptFailureStatus::Permanent,
2030            }),
2031            Err(AttemptLifecycleError::DoubleTerminalization)
2032        );
2033        // The rejection published the truthful outcome.
2034        let report = begin.completion.completion().wait_timeout(Duration::from_secs(1)).expect("report");
2035        assert!(matches!(
2036            report.outcome,
2037            RuntimeTerminalOutcome::Failed { ref error_code, .. }
2038                if *error_code == AttemptFailureCode::Query(QueryErrorCode::ServiceOverloaded)
2039        ));
2040        assert_eq!(report.disposition, AttemptDisposition::Native);
2041        assert_eq!(report.timestamps.admission_requested_ns, None);
2042        assert_eq!(report.timestamps.admitted_ns, None);
2043        assert_eq!(report.timestamps.physical_execution_started_ns, None);
2044    }
2045}
2046
2047#[cfg(test)]
2048mod abandonment_race_tests {
2049    use super::*;
2050    use crate::{
2051        observability::{DeterministicRuntimeObservationClock, RuntimeObservationConfig},
2052        runtime::BackendLifecycle,
2053    };
2054
2055    fn begin(queue: Option<Arc<AttemptCompletionQueue>>) -> AttemptBegin {
2056        let lifecycle = BackendLifecycle::new();
2057        let permit = lifecycle.try_begin_observation().expect("test permit");
2058        AttemptOwner::begin_with_clock(
2059            BeginAttemptRequest::default(),
2060            permit,
2061            Arc::new(AttemptBackendCounters::default()),
2062            queue,
2063            Arc::new(DeterministicRuntimeObservationClock::new(Some(1))),
2064        )
2065    }
2066
2067    #[test]
2068    fn last_receiver_drop_and_publish_have_one_coherent_linearization() {
2069        for _ in 0..64 {
2070            let queue = Arc::new(AttemptCompletionQueue::new(4).expect("queue"));
2071            let begin = begin(Some(Arc::clone(&queue)));
2072            let completion = begin.completion.completion();
2073            let (dropped_tx, dropped_rx) = std::sync::mpsc::channel::<()>();
2074            let publisher = std::thread::spawn(move || {
2075                dropped_rx.recv().expect("drop signal");
2076                begin.owner.commit_terminal(RuntimeTerminalOutcome::Succeeded).unwrap();
2077                begin.owner
2078            });
2079            drop(completion);
2080            dropped_tx.send(()).expect("publish signal");
2081            let owner = publisher.join().expect("publisher joins");
2082            // The drop strictly precedes the publish in this interleaving, so
2083            // the report must be routed to the abandoned queue.
2084            assert_eq!(queue.drain(4).len(), 1);
2085            drop(owner);
2086        }
2087    }
2088
2089    #[test]
2090    fn clone_during_publication_is_delivery_loss_never_report_loss() {
2091        for _ in 0..64 {
2092            let queue = Arc::new(AttemptCompletionQueue::new(4).expect("queue"));
2093            let begin = begin(Some(Arc::clone(&queue)));
2094            let factory = begin.completion.clone();
2095            let (go_tx, go_rx) = std::sync::mpsc::channel::<()>();
2096            let cloner_factory = factory.clone();
2097            let cloner = std::thread::spawn(move || {
2098                go_rx.recv().expect("go");
2099                cloner_factory.completion()
2100            });
2101            go_tx.send(()).expect("go");
2102            begin.owner.commit_terminal(RuntimeTerminalOutcome::Succeeded).unwrap();
2103            let receiver = cloner.join().expect("cloner joins");
2104            // Whatever the interleaving, the cloned receiver can always read
2105            // the published report; the queue decision was made under one lock.
2106            assert!(matches!(
2107                receiver.wait_timeout(Duration::from_secs(1)).map(|report| report.outcome.clone()),
2108                Some(RuntimeTerminalOutcome::Succeeded)
2109            ));
2110            drop(receiver);
2111        }
2112    }
2113
2114    #[test]
2115    fn deadline_expiry_is_a_distinct_typed_cancellation() {
2116        let lifecycle = BackendLifecycle::new();
2117        let permit = lifecycle.try_begin_observation().expect("test permit");
2118        let begin = AttemptOwner::begin_with_clock(
2119            BeginAttemptRequest {
2120                deadline: Some(Instant::now() - Duration::from_millis(1)),
2121                observation: RuntimeObservationConfig::native_execution(),
2122                ..BeginAttemptRequest::default()
2123            },
2124            permit,
2125            Arc::new(AttemptBackendCounters::default()),
2126            None,
2127            Arc::new(DeterministicRuntimeObservationClock::new(Some(1))),
2128        );
2129        let cancellation = begin.owner.cancellation();
2130        assert!(cancellation.is_expired());
2131        let execution_cancellation = cancellation.execution_cancellation();
2132        assert!(execution_cancellation.is_expired(), "native execution must retain the attempt deadline");
2133        assert_eq!(execution_cancellation.deadline(), cancellation.deadline());
2134        let error = cancellation.check("test.deadline").unwrap_err();
2135        assert_eq!(error.fact_value("reason"), Some("deadline_expired"));
2136        assert_eq!(error.fact_value("cancellation_requested"), Some("true"));
2137        let execution_error = execution_cancellation.check("test.native_deadline").unwrap_err();
2138        assert_eq!(execution_error.fact_value("reason"), Some("deadline_expired"));
2139        assert!(cancellation.is_cancelled(), "expiry is an effective cancellation at native safe boundaries");
2140    }
2141}