1use 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
61pub 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
94pub 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
106pub struct AttemptOwner(Arc<AttemptInner>);
111
112pub struct AttemptHandle(Arc<AttemptCore>);
115
116pub struct AttemptAdmissionRejection {
123 core: Arc<AttemptCore>,
124 finalizer_slot: AttemptFinalizerSlot,
125}
126
127#[derive(Clone)]
134pub struct AttemptAdmissionBinding {
135 core: Arc<AttemptCore>,
136 finalizer_slot: AttemptFinalizerSlot,
137}
138
139pub(crate) struct AttemptFinalizer(Arc<AttemptCore>);
144
145pub(crate) struct AttemptAdmission(AttemptFinalizer);
152
153pub(crate) struct AttemptReportPublisher(Arc<AttemptCore>);
156
157impl AttemptReportPublisher {
158 pub(crate) fn begin_cleanup(&self) {
160 self.0.observation.begin_cleanup();
161 }
162
163 pub(crate) fn seal(&self, fallback: RuntimeTerminalOutcome) -> QueryCleanupSealDelivery {
167 self.0.seal_observation(fallback)
168 }
169
170 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#[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
201pub 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
222pub 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 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 pub fn try_report(&self) -> Option<Arc<QueryAttemptReport>> {
251 self.shared.lock().completion.visible_report()
252 }
253}
254
255struct SealedAttemptReportSink {
259 notify: Box<dyn Fn(Arc<QueryAttemptReport>) + Send + Sync>,
260}
261
262impl SealedAttemptReportSink {
263 fn invoke(&self, report: Arc<QueryAttemptReport>) {
264 let _ = panic::catch_unwind(AssertUnwindSafe(|| (self.notify)(report)));
267 }
268}
269
270struct CompletionState {
271 report: Option<Arc<QueryAttemptReport>>,
275 report_visible: bool,
276 sealed_report_sinks: Vec<SealedAttemptReportSink>,
277 wakers: Vec<Waker>,
278 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
289pub(crate) trait AdmittedGrantRelease: Send + Sync {
296 fn request_release(&self);
297}
298
299struct AttemptState {
302 lifecycle: AttemptLifecycle,
303 completion: CompletionState,
304 admitted_release: Option<Arc<dyn AdmittedGrantRelease>>,
308}
309
310struct 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
323struct 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
340struct AttemptCore {
344 attempt_id: AttemptId,
345 query_id: Option<AttemptQueryId>,
346 trace_context: Option<AttemptTraceContext>,
347 shared: Arc<AttemptShared>,
348 clock: Arc<dyn RuntimeObservationClock>,
352 cancellation: AttemptCancellation,
353 observation: AttemptObservation,
354 counters: Arc<AttemptBackendCounters>,
355 completion_queue: Option<Arc<AttemptCompletionQueue>>,
356 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 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 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 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 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 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 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 let fallback =
531 lifecycle.primary_failure_summary().map(RuntimeTerminalOutcome::from_primary_failure).unwrap_or(fallback);
532 let at = self.monotonic_ns();
533 lifecycle.terminalize(fallback.clone(), at).unwrap_or(());
537 lifecycle.terminal_outcome().cloned().unwrap_or(fallback)
538 }
539
540 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 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 fn finish_with_footer(
564 &self,
565 footer: &RuntimeObservationFooter,
566 cleanup_failures: &CleanupFailureTally,
567 ) -> Result<Arc<QueryAttemptReport>, AttemptLifecycleError> {
568 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 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 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 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 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 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 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 #[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 #[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 pub fn attempt_id(&self) -> AttemptId {
729 self.0.core.attempt_id
730 }
731
732 pub fn handle(&self) -> AttemptHandle {
734 AttemptHandle(Arc::clone(&self.0.core))
735 }
736
737 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 pub fn admission_rejection(&self) -> AttemptAdmissionRejection {
751 AttemptAdmissionRejection { core: Arc::clone(&self.0.core), finalizer_slot: self.0.finalizer_slot.clone() }
752 }
753
754 pub fn admission_binding(&self) -> AttemptAdmissionBinding {
759 AttemptAdmissionBinding { core: Arc::clone(&self.0.core), finalizer_slot: self.0.finalizer_slot.clone() }
760 }
761
762 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 pub fn cancellation(&self) -> AttemptCancellation {
770 self.0.core.cancellation.clone()
771 }
772
773 #[cfg(test)]
775 pub(crate) fn sealed_snapshot_for_test(&self) -> Option<SealedRuntimeObservationSnapshot> {
776 self.0.core.observation.sealed_snapshot()
777 }
778
779 #[cfg(test)]
781 pub(crate) fn observation_for_test(&self) -> RuntimeObservationHandle {
782 self.0.core.observation.handle()
783 }
784
785 #[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 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 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 Err(AttemptLifecycleError::FinalizationUnavailable) => {
814 self.0.core.release_admitted_grant();
815 Ok(())
816 },
817 Err(error) => Err(error),
818 }
819 }
820
821 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 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 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 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 pub(crate) fn attempt_id(&self) -> AttemptId {
918 self.core.attempt_id
919 }
920
921 pub fn observation_handle(&self) -> RuntimeObservationHandle {
926 self.core.observation.handle()
927 }
928
929 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 pub(crate) fn claim(&self) -> Result<AttemptAdmission, AttemptLifecycleError> {
942 self.finalizer_slot.claim()
943 }
944}
945
946impl AttemptAdmissionRejection {
947 pub fn reject(&self, error: &QueryError) -> Result<(), AttemptLifecycleError> {
951 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 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 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 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 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 pub(crate) fn terminalize_for_release(
1042 &self,
1043 fallback: RuntimeTerminalOutcome,
1044 ) -> Result<RuntimeTerminalOutcome, AttemptLifecycleError> {
1045 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 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 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 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 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 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 pub(crate) fn finish_admitted(self, commit: AttemptTerminalCommit) -> Result<(), AttemptLifecycleError> {
1144 self.0.finalize_admitted(commit)
1145 }
1146}
1147
1148impl AttemptHandle {
1149 pub fn attempt_id(&self) -> AttemptId {
1151 self.0.attempt_id
1152 }
1153
1154 pub(crate) fn observation(&self) -> RuntimeObservationHandle {
1156 self.0.observation.handle()
1157 }
1158
1159 pub fn cancellation(&self) -> AttemptCancellation {
1161 self.0.cancellation.clone()
1162 }
1163
1164 pub fn is_terminal(&self) -> bool {
1166 self.0.lock_lifecycle().terminal_outcome().is_some()
1167 }
1168
1169 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 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 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 #[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 pub fn is_ready(&self) -> bool {
1242 self.shared.lock().completion.report_visible
1243 }
1244
1245 pub fn try_report(&self) -> Option<Arc<QueryAttemptReport>> {
1247 self.shared.lock().completion.visible_report()
1248 }
1249
1250 pub fn wait_timeout(&self, timeout: Duration) -> Option<Arc<QueryAttemptReport>> {
1253 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 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 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 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 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 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 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 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 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 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 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 admission_rejection
2019 .reject(&QueryError::new(
2020 QueryErrorCode::ServiceOverloaded,
2021 "test.admission_rejection",
2022 "injected admission rejection",
2023 ))
2024 .unwrap();
2025 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 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 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 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}