Skip to main content

query_runtime/runtime/context/
mod.rs

1//! Native execution context state and query-local runtime controls.
2//!
3//! An `ExecutionContext` serves as the runtime environment passed to each native operator.
4//! It composes scopes defined in `scope`, including the per-device runtime, the query scope
5//! (admitted attempt or standalone execution), the stream binding, and per-plan overlays.
6//! Context cloning is lightweight and shares these scopes, while derivation selectively
7//! substitutes individual scopes.
8//!
9//! In an admitted context, resource acquisitions (runtime, pool, grant, stream)
10//! validate against a `QueryServiceResourceEpoch`. Cloned pools, grants, and stream
11//! leases hold this epoch, preventing the service from reaching idle while active
12//! and failing remaining work once the service or query handle closes.
13//! Host synchronization fences on the context stream acquire no resources and proceed
14//! without gating (see `synchronize_stream`), while standalone contexts bypass epoch tracking.
15//! Governing admission and lifecycle contracts reside in
16//! `website/docs/design/admission.mdx#from-capability-validation-to-admission` and
17//! `website/docs/design/admission.mdx#resident-handoff-and-release`.
18
19use std::sync::Arc;
20
21use arrow::datatypes::SchemaRef;
22use cudf::{DeviceId, Error as CudfError, Runtime as CudfRuntime};
23use query_planning::{
24    admission::AttemptCapabilityEvidence,
25    capability::AggregateStrategyExecutionOptions,
26    chunk::SourceChunkPlanningOptions,
27    correlation::{QueryId, TraceId},
28    error::{QueryError, QueryErrorCode, Result},
29    execution_contracts::{MemoryControlPolicy, SourceChunkPlanningMode},
30    mode::StatisticalAggregateExecutionMode,
31    plan::PlanFingerprint,
32    policy::PlanningPolicy,
33};
34
35use super::{
36    GpuExecutionPool, GpuExecutorLease, MemoryGrant, ServiceStreamPool, SourceSchedulerPolicy,
37    attempt_domain::AttemptStreamProviderRoute,
38    cache::local_parquet::GpuLocalParquetSourceCache,
39    gpu::{GrantIdentity, ledger::ResidentOwnerLease},
40    io_lifecycle::{IoLifecycle, IoLifecycleLease},
41    resident::{GpuResidentInputMap, GpuResultHandle},
42    service_stream::StreamCleanupOutcome,
43};
44#[cfg(feature = "cugraph")]
45use super::{allocator_correlation::CacheScopeCorrelationGuard, cache::resident_graph::GpuResidentGraphCache};
46use crate::observability::{
47    AttemptId, PlanExecutionId, RuntimeExecutionObservationScope, RuntimeObservationHandle,
48    RuntimePlanExecutionLifecycle, RuntimeQueryLifecycle, RuntimeStreamLeaseScope, RuntimeTerminalOutcome,
49    SealedRuntimeObservationSnapshot,
50};
51
52mod admitted;
53mod auxiliary_slots;
54mod cancellation;
55mod epoch;
56mod lakehouse;
57mod leases;
58mod scope;
59#[cfg(any(test, feature = "test-hooks"))]
60mod standalone;
61mod stream_pool;
62
63pub(in crate::runtime) use admitted::AdmittedPlanIdentities;
64pub(crate) use auxiliary_slots::{AttemptAuxiliaryPermit, AttemptAuxiliarySlots};
65pub use cancellation::ExecutionCancellation;
66pub(crate) use epoch::{
67    QueryResourceAnchor, QueryResourceEpoch, QueryServiceEpoch, QueryServiceEpochUser, QueryServiceResourceEpoch,
68};
69use scope::QueryScope;
70pub(in crate::runtime) use scope::{AdmittedAttempt, DeviceRuntime, PlanScope, StreamBinding};
71#[cfg(any(test, feature = "test-hooks"))]
72pub use standalone::{RuntimeObservationBuildError, RuntimeObservedExecutionContext, StandaloneConfig};
73pub(crate) use stream_pool::QueryServiceStreamPool;
74
75/// Immutable execution configuration shared by native query execution.
76///
77/// The context owns or shares the [`cudf::Runtime`] used for all device work in
78/// a query. The runtime determines the CUDA device, stream, and memory resource;
79/// the query engine layers its own chunk-planning, reservation, retry, and
80/// boundary policies on top of that runtime. Cloning the context is cheap and
81/// keeps the same runtime/cache handles.
82#[derive(Debug, Clone)]
83pub struct ExecutionContext {
84    // Fields drop in declaration order, which attempt teardown depends on: the
85    // plan scope holds the outward query anchor, and the query scope's grant
86    // and domain clones go before the stream binding, whose root lease drop
87    // runs the attempt's terminal cleanup (see `StreamBinding`).
88    plan: Arc<PlanScope>,
89    query: QueryScope,
90    stream: Arc<StreamBinding>,
91    device: Arc<DeviceRuntime>,
92}
93
94/// The deferred-resident phase one plan scope activated from its materialized
95/// inputs. It pins the evidence proven at activation and the grant that owned
96/// the input storage; later executions of the same scope validate against it
97/// in `admitted`.
98#[derive(Debug)]
99pub(super) struct ActivatedDeferredResidentPhase {
100    consumer_execution_scope_fingerprint: PlanFingerprint,
101    inputs: Vec<ActivatedDeferredResidentInput>,
102    capability_evidence: AttemptCapabilityEvidence,
103    grant_identity: GrantIdentity,
104}
105
106/// One input that activated a deferred-resident phase. Every later execution
107/// of the phase must present an input with the same data facts and the same
108/// allocation under the same grant.
109#[derive(Debug)]
110pub(super) struct ActivatedDeferredResidentInput {
111    input_name: String,
112    facts: DeferredResidentFacts,
113    memory_identity: cudf::DataFrameMemoryIdentity,
114}
115
116/// Data facts of one deferred resident input: the producer scope that wrote
117/// it, its schema, and its row count.
118#[derive(Debug, PartialEq)]
119pub(super) struct DeferredResidentFacts {
120    producer_execution_scope_fingerprint: PlanFingerprint,
121    schema: SchemaRef,
122    row_count: u64,
123}
124
125impl DeferredResidentFacts {
126    pub(super) fn read(input_name: &str, input: &GpuResultHandle) -> Result<Self> {
127        let producer_execution_scope_fingerprint = input.producer_execution_scope().ok_or_else(|| {
128            QueryError::new(
129                QueryErrorCode::InternalInvariantViolation,
130                "query_engine.runtime.context",
131                "an admitted deferred resident occurrence has no producer scope",
132            )
133            .with_fact("reason", "deferred_resident_input_scope_missing")
134            .with_fact("input_name", input_name)
135        })?;
136        let row_count = u64::try_from(input.frame().num_rows()).map_err(|_| {
137            QueryError::new(
138                QueryErrorCode::SizeOverflow,
139                "query_engine.runtime.context",
140                "the resident input row count exceeds the admission integer range",
141            )
142            .with_fact("reason", "deferred_resident_input_row_count_overflow")
143            .with_fact("input_name", input_name)
144        })?;
145        Ok(Self { producer_execution_scope_fingerprint, schema: Arc::clone(input.schema()), row_count })
146    }
147
148    pub(super) fn producer_execution_scope_fingerprint(&self) -> PlanFingerprint {
149        self.producer_execution_scope_fingerprint
150    }
151
152    pub(super) fn schema(&self) -> &SchemaRef {
153        &self.schema
154    }
155}
156
157impl ActivatedDeferredResidentPhase {
158    fn validate_inputs(&self, inputs: &GpuResidentInputMap, grant: &MemoryGrant) -> Result<()> {
159        for activated in &self.inputs {
160            let input = inputs.get(&activated.input_name).ok_or_else(|| {
161                QueryError::new(
162                    QueryErrorCode::InternalInvariantViolation,
163                    "query_engine.runtime.context",
164                    "the activated deferred resident input is missing",
165                )
166                .with_fact("reason", "deferred_resident_input_missing")
167                .with_fact("input_name", &activated.input_name)
168            })?;
169            activated.validate_facts(input)?;
170            activated.validate_binding(input, grant, self.grant_identity)?;
171        }
172        Ok(())
173    }
174}
175
176impl ActivatedDeferredResidentInput {
177    /// Binds an input whose facts already matched its admitted producer
178    /// occurrence to its allocation under `grant`.
179    pub(super) fn bind(
180        input_name: String,
181        facts: DeferredResidentFacts,
182        input: &GpuResultHandle,
183        grant: &MemoryGrant,
184    ) -> Result<Self> {
185        validate_grant_retention(&input_name, input, grant)?;
186        Ok(Self { input_name, facts, memory_identity: input.frame().memory_identity() })
187    }
188
189    fn validate_facts(&self, input: &GpuResultHandle) -> Result<()> {
190        let facts = DeferredResidentFacts::read(&self.input_name, input)?;
191        if facts != self.facts {
192            return Err(self
193                .identity_mismatch()
194                .with_fact("input_row_count", facts.row_count)
195                .with_fact("activated_row_count", self.facts.row_count));
196        }
197        Ok(())
198    }
199
200    fn validate_binding(&self, input: &GpuResultHandle, grant: &MemoryGrant, phase_grant: GrantIdentity) -> Result<()> {
201        validate_grant_retention(&self.input_name, input, grant)?;
202        if phase_grant != grant.identity() || input.frame().memory_identity() != self.memory_identity {
203            return Err(self.identity_mismatch());
204        }
205        Ok(())
206    }
207
208    fn identity_mismatch(&self) -> QueryError {
209        QueryError::new(
210            QueryErrorCode::InternalInvariantViolation,
211            "query_engine.runtime.context",
212            "the GPU-resident graph input differs from the frame that activated its admitted phase",
213        )
214        .with_fact("reason", "deferred_graph_input_identity_mismatch")
215        .with_fact("input_name", &self.input_name)
216    }
217}
218
219/// A deferred resident input that holds device storage must be retained by
220/// the attempt's grant, so the phase never reads storage another grant owns.
221fn validate_grant_retention(input_name: &str, input: &GpuResultHandle, grant: &MemoryGrant) -> Result<()> {
222    let physical_bytes = input.device_allocated_size_bytes()?;
223    if physical_bytes > 0 && input.retained_owner_identity_for(grant).is_none() {
224        return Err(QueryError::new(
225            QueryErrorCode::InternalInvariantViolation,
226            "query_engine.runtime.context",
227            "a deferred resident input is not retained by the admitted grant",
228        )
229        .with_fact("reason", "deferred_resident_grant_owner_mismatch")
230        .with_fact("input_name", input_name)
231        .with_fact("input_device_bytes", physical_bytes));
232    }
233    Ok(())
234}
235
236/// One native execution observation boundary selected from context ownership.
237///
238/// This type exposes plan recording and terminal completion without exposing
239/// the observation owner stored by the execution context.
240#[derive(Debug)]
241pub struct RuntimeExecutionObservationLifecycle {
242    inner: RuntimeExecutionObservationLifecycleInner,
243}
244
245#[derive(Debug)]
246enum RuntimeExecutionObservationLifecycleInner {
247    Query(RuntimeQueryLifecycle),
248    Plan(RuntimePlanExecutionLifecycle),
249}
250
251impl RuntimeExecutionObservationLifecycle {
252    fn for_context(context: &ExecutionContext, plan_execution_id: PlanExecutionId) -> Self {
253        // An admitted attempt seals once for the attempt, so each plan
254        // finishes only its own execution; a standalone owner seals the query.
255        let inner = match &context.query {
256            QueryScope::Admitted(admitted) => RuntimeExecutionObservationLifecycleInner::Plan(
257                RuntimePlanExecutionLifecycle::from_attempt(&admitted.attempt, plan_execution_id),
258            ),
259            QueryScope::Standalone(query) => match &query.observation_owner {
260                Some(owner) => RuntimeExecutionObservationLifecycleInner::Query(RuntimeQueryLifecycle::from_owner(
261                    owner.clone(),
262                    plan_execution_id,
263                )),
264                None => RuntimeExecutionObservationLifecycleInner::Plan(RuntimePlanExecutionLifecycle::from_handle(
265                    context.plan.runtime_observation.clone(),
266                    plan_execution_id,
267                )),
268            },
269        };
270        Self { inner }
271    }
272
273    /// Returns the record-only scope for one native plan invocation.
274    pub fn execution_scope(&self) -> RuntimeExecutionObservationScope {
275        match &self.inner {
276            RuntimeExecutionObservationLifecycleInner::Query(lifecycle) => lifecycle.execution_scope(),
277            RuntimeExecutionObservationLifecycleInner::Plan(lifecycle) => lifecycle.execution_scope(),
278        }
279    }
280
281    /// Completes this plan or standalone query according to context ownership.
282    /// A snapshot is returned only when a standalone owner sealed the query;
283    /// an admitted attempt's seal belongs to its release path.
284    pub fn finish(self, outcome: RuntimeTerminalOutcome) -> Option<SealedRuntimeObservationSnapshot> {
285        match self.inner {
286            RuntimeExecutionObservationLifecycleInner::Query(lifecycle) => lifecycle.finish(outcome),
287            RuntimeExecutionObservationLifecycleInner::Plan(lifecycle) => {
288                lifecycle.finish(outcome);
289                None
290            },
291        }
292    }
293}
294
295impl ExecutionContext {
296    /// Assembles the root context of an admitted attempt.
297    pub(in crate::runtime) fn admitted_root(
298        device: Arc<DeviceRuntime>,
299        attempt: AdmittedAttempt,
300        stream: StreamBinding,
301        plan: PlanScope,
302    ) -> Self {
303        Self { plan: Arc::new(plan), query: QueryScope::Admitted(Arc::new(attempt)), stream: Arc::new(stream), device }
304    }
305
306    fn admitted(&self) -> Option<&AdmittedAttempt> {
307        match &self.query {
308            QueryScope::Admitted(attempt) => Some(attempt),
309            QueryScope::Standalone(_) => None,
310        }
311    }
312
313    /// Returns a clone whose plan scope `update` changed.
314    fn with_plan(&self, update: impl FnOnce(&mut PlanScope)) -> Self {
315        let mut plan = PlanScope::clone(&self.plan);
316        update(&mut plan);
317        Self { plan: Arc::new(plan), ..self.clone() }
318    }
319
320    /// Returns a clone that runs on `stream`, keeping every other scope.
321    fn with_stream(&self, stream: StreamBinding) -> Self {
322        Self { stream: Arc::new(stream), ..self.clone() }
323    }
324
325    /// Attaches one counted query owner to a clone of the internal root template.
326    pub(super) fn with_query_resource_anchor(&self, anchor: Arc<QueryResourceAnchor>) -> Self {
327        debug_assert!(
328            self.admitted().is_some_and(|attempt| anchor.belongs_to(&attempt.query_epoch)),
329            "query resource anchor must belong to the context query epoch"
330        );
331        self.with_plan(|plan| plan.query_anchor = Some(anchor))
332    }
333
334    /// Takes a counted user of the service epoch, bound to the plan's query
335    /// anchor when the context carries one. `None` for a standalone context.
336    /// Fails with `Cancelled` and the reason `query_service_epoch_closed` or
337    /// `query_handle_released` once either lifetime is closed.
338    fn acquire_resource_epoch(&self, operation: &'static str) -> Result<Option<Arc<QueryServiceResourceEpoch>>> {
339        let Some(attempt) = self.admitted() else {
340            return Ok(None);
341        };
342        QueryServiceResourceEpoch::acquire(&attempt.service_epoch, self.plan.query_anchor.as_ref(), operation).map(Some)
343    }
344
345    /// Returns the cudf runtime used for all GPU operations.
346    pub fn runtime(&self) -> Result<CudfRuntime> {
347        let operation = "query_engine.runtime.context.runtime";
348        // Validate the query/service epoch at acquisition time, but do not
349        // embed it in GPU allocations. Escaping allocations are owned by a
350        // ResidentOwnerLease; unowned residual allocations must reach final
351        // validation so cleanup can fail closed. Embedding the epoch in one
352        // would keep the query epoch alive forever and prevent B-to-R handoff.
353        drop(self.acquire_resource_epoch(operation)?);
354        Ok(self.stream.runtime().as_ref().clone())
355    }
356
357    /// Acquire permission for one file-backed native I/O call.
358    ///
359    /// Standalone library contexts without a server lifecycle remain
360    /// unrestricted. Server contexts carry one generation gate and reject
361    /// acquisitions after graceful shutdown begins.
362    pub fn acquire_io_lease(&self, operation: &'static str) -> Result<Option<IoLifecycleLease>> {
363        self.plan.io_lifecycle.as_ref().map(|lifecycle| lifecycle.acquire(operation)).transpose()
364    }
365
366    /// Attach a server-generation I/O lifecycle to this context clone.
367    pub fn with_io_lifecycle(self, lifecycle: IoLifecycle) -> Self {
368        self.with_plan(|plan| plan.io_lifecycle = Some(lifecycle))
369    }
370
371    /// Host-fences this context's stream and returns any asynchronous device
372    /// fault that stream reports.
373    ///
374    /// Unlike [`runtime`](Self::runtime), the fence does not check the query
375    /// or service epoch. It enqueues no work and allocates nothing, so cleanup
376    /// that runs after cancellation released the query handle can still
377    /// complete the work it issued and attribute a fault to it. The caller
378    /// must hold the stream binding or the lease from `with_stream_lease`
379    /// that this context was created under; a borrowed context that outlives
380    /// its lease would fence a pooled stream another attempt may own.
381    #[doc(hidden)]
382    pub fn synchronize_stream(&self) -> std::result::Result<(), CudfError> {
383        self.stream.runtime().synchronize()
384    }
385
386    /// Returns a shared handle to the runtime.
387    ///
388    /// The caller receives the same runtime object. A new runtime with copied
389    /// configuration is not created.
390    pub fn shared_runtime(&self) -> Result<Arc<CudfRuntime>> {
391        self.runtime().map(Arc::new)
392    }
393
394    /// Returns an opaque lifecycle-bound executor for synchronous GPU work on
395    /// this context's device.
396    ///
397    /// The lease's query-execution job follows this context's own claim on
398    /// the attempt: a root context runs it as root-class work, while an
399    /// auxiliary fragment context, which is a second native island of the same
400    /// attempt, runs it as auxiliary work backed by the attempt auxiliary
401    /// permit that context already holds. Root capacity is one job per
402    /// admitted attempt, so a second island charging it would fail where it
403    /// has a reserved auxiliary slot of its own.
404    pub fn gpu_executor(&self) -> Result<GpuExecutorLease> {
405        self.gpu_execution_pool().map(if matches!(*self.stream, StreamBinding::AttemptWork { owned: Some(_), .. }) {
406            GpuExecutorLease::from_pool_for_auxiliary_fragment
407        } else {
408            GpuExecutorLease::from_pool
409        })
410    }
411
412    /// The device pool, bound to this context's resource epoch when admitted.
413    /// Jobs submitted through the bound clone fail once the service or the
414    /// query handle closes, and each spawned task retains the epoch; the
415    /// device pool itself outlives any one service.
416    pub(crate) fn gpu_execution_pool(&self) -> Result<GpuExecutionPool> {
417        let resource_epoch = self.acquire_resource_epoch("query_engine.runtime.context.gpu_execution_pool")?;
418        Ok(match resource_epoch {
419            Some(resource_epoch) => self.device.gpu_execution_pool.with_resource_epoch(resource_epoch),
420            None => self.device.gpu_execution_pool.clone(),
421        })
422    }
423
424    /// Returns the admitted attempt's fork-stream route, when this context
425    /// carries one. Each lease derived from it holds one auxiliary fork of the
426    /// route alone, up to [`AttemptStreamProviderRoute::lane_capacity`];
427    /// direct contexts have no route and run serially.
428    #[doc(hidden)]
429    pub fn attempt_stream_provider_route(&self) -> Option<Arc<AttemptStreamProviderRoute>> {
430        self.stream.route().cloned()
431    }
432
433    /// Returns the query-wide diagnostic correlation id.
434    pub fn query_id(&self) -> Option<&QueryId> {
435        self.plan.query_id.as_ref()
436    }
437
438    /// Returns the optional parent trace id.
439    pub fn trace_id(&self) -> Option<&TraceId> {
440        self.plan.trace_id.as_ref()
441    }
442
443    /// Returns the engine-minted attempt identity carried by an admitted context.
444    ///
445    /// This remains absent for direct contexts because they are not part of a
446    /// public execution-attempt lifecycle.
447    #[doc(hidden)]
448    pub fn attempt_id(&self) -> Option<AttemptId> {
449        self.admitted().map(|attempt| attempt.attempt_id)
450    }
451
452    /// Opens one exact allocator-event window for an exclusive resident graph
453    /// checkout. The caller must finish the returned guard after cache-runtime
454    /// temporaries are destroyed and before returning the checkout.
455    #[cfg(feature = "cugraph")]
456    #[doc(hidden)]
457    pub fn begin_cache_scope_allocator_correlation(
458        &self,
459        scope: Arc<cudf::CacheAllocationScope>,
460    ) -> Option<CacheScopeCorrelationGuard> {
461        self.admitted()?.cache_scope_allocator_correlation.begin(scope)
462    }
463
464    /// Returns the shared recording handle without terminal-seal authority.
465    pub fn runtime_observation(&self) -> &RuntimeObservationHandle {
466        &self.plan.runtime_observation
467    }
468
469    /// Opens one native execution boundary without exposing the context's
470    /// observation owner.
471    #[doc(hidden)]
472    pub fn begin_runtime_execution_observation(
473        &self,
474        plan_execution_id: PlanExecutionId,
475    ) -> RuntimeExecutionObservationLifecycle {
476        RuntimeExecutionObservationLifecycle::for_context(self, plan_execution_id)
477    }
478
479    /// Returns the active native plan-execution scope.
480    pub fn runtime_execution_scope(&self) -> Option<&RuntimeExecutionObservationScope> {
481        self.plan.runtime_execution_scope.as_ref()
482    }
483
484    /// Returns a clone carrying one invocation's recording handle.
485    ///
486    /// Seal authority remains with the execution lifecycle that supplied the
487    /// handle. Identity is validated before the clone can reach workers.
488    #[doc(hidden)]
489    pub fn with_runtime_observation_handle(&self, observation: RuntimeObservationHandle) -> Result<Self> {
490        self.validate_observation_identity(&observation)?;
491        Ok(self.with_plan(|plan| plan.runtime_observation = observation))
492    }
493
494    /// Returns a clone scoped to one native plan invocation.
495    #[doc(hidden)]
496    pub fn with_runtime_execution_scope(&self, scope: RuntimeExecutionObservationScope) -> Result<Self> {
497        self.validate_observation_identity(scope.observation())?;
498        Ok(self.with_plan(|plan| {
499            plan.runtime_observation = scope.observation().clone();
500            plan.runtime_stream_lease_scope.plan_execution_id = Some(scope.plan_execution_id().clone());
501            plan.runtime_execution_scope = Some(scope);
502        }))
503    }
504
505    fn validate_observation_identity(&self, observation: &RuntimeObservationHandle) -> Result<()> {
506        if let Some(query_id) = &self.plan.query_id {
507            validate_runtime_observation_query_id(observation, query_id)?;
508        }
509        if let Some(trace_id) = &self.plan.trace_id {
510            validate_runtime_observation_trace_id(observation, trace_id)?;
511        }
512        Ok(())
513    }
514
515    /// Returns a clone carrying explicit GPU job/worker correlation.
516    pub fn with_runtime_gpu_stream_scope(&self, scope: RuntimeStreamLeaseScope) -> Self {
517        self.with_plan(|plan| {
518            plan.runtime_stream_lease_scope.gpu_job_id = scope.gpu_job_id;
519            plan.runtime_stream_lease_scope.gpu_worker_id = scope.gpu_worker_id;
520        })
521    }
522
523    /// Returns a cloned context with a diagnostic query id consistent with observation identity.
524    pub fn with_query_id(&self, query_id: impl Into<QueryId>) -> Result<Self> {
525        let query_id = query_id.into();
526        validate_runtime_observation_query_id(&self.plan.runtime_observation, &query_id)?;
527        Ok(self.with_plan(|plan| plan.query_id = Some(query_id)))
528    }
529
530    /// Returns a cloned context with a trace id consistent with observation identity.
531    pub fn with_trace_id(&self, trace_id: impl Into<TraceId>) -> Result<Self> {
532        let trace_id = trace_id.into();
533        validate_runtime_observation_trace_id(&self.plan.runtime_observation, &trace_id)?;
534        Ok(self.with_plan(|plan| plan.trace_id = Some(trace_id)))
535    }
536
537    /// Returns whether this context is backed by a QueryService admission slot.
538    pub fn is_query_service_admitted(&self) -> bool {
539        self.admitted().is_some()
540    }
541
542    /// Returns the backend-owned local-Parquet cache for this context's
543    /// ordinal, if its device profile enabled resident caching.
544    #[doc(hidden)]
545    pub fn resident_source_cache(&self) -> Option<GpuLocalParquetSourceCache> {
546        self.device.resident_source_cache.clone()
547    }
548
549    /// Returns the backend-owned resident graph cache for this context's
550    /// ordinal, if its device profile enabled resident caching.
551    #[cfg(feature = "cugraph")]
552    #[doc(hidden)]
553    pub fn resident_graph_cache(&self) -> Option<GpuResidentGraphCache> {
554        self.device.resident_graph_cache.clone()
555    }
556
557    /// Installs the attempt's terminal cleanup on the root stream lease. The
558    /// release path installs it before dropping the root context, so the
559    /// callbacks run once the cleanup thread has synchronized the root stream.
560    /// Returns `false` for a binding other than the root, or when a completion
561    /// was already installed; the release path reports the latter as an
562    /// ownership fault.
563    pub(super) fn install_root_stream_cleanup_completion(
564        &self,
565        before_quiescence: impl FnOnce(StreamCleanupOutcome) + Send + 'static,
566        after_quiescence: impl FnOnce() + Send + 'static,
567    ) -> bool {
568        let StreamBinding::AttemptRoot { lease, .. } = &*self.stream else {
569            return false;
570        };
571        lease.install_cleanup_completion(before_quiescence, after_quiescence)
572    }
573
574    pub(super) fn has_root_stream_lease(&self) -> bool {
575        matches!(*self.stream, StreamBinding::AttemptRoot { .. })
576    }
577
578    /// Returns the cooperative cancellation flag for this execution.
579    pub fn cancellation(&self) -> &ExecutionCancellation {
580        match &self.query {
581            QueryScope::Standalone(query) => &query.cancellation,
582            QueryScope::Admitted(attempt) => &attempt.cancellation,
583        }
584    }
585
586    /// Fails with a structured cancellation error if the execution was cancelled.
587    ///
588    /// The service epoch, the query epoch, and the cancellation token are
589    /// checked in that order. All three fail with `QueryErrorCode::Cancelled`;
590    /// the `reason` fact names the closed epoch or the elapsed deadline, and a
591    /// caller cancellation carries none.
592    pub fn check_cancellation(&self, operation: &'static str) -> Result<()> {
593        self.check_service_epoch(operation)?;
594        self.cancellation().check(operation)
595    }
596
597    fn check_service_epoch(&self, operation: &'static str) -> Result<()> {
598        let Some(attempt) = self.admitted() else {
599            return Ok(());
600        };
601        attempt.service_epoch.check_open(operation)?;
602        attempt.query_epoch.check_open(operation)
603    }
604
605    /// CUDA device that runs this context's work.
606    pub fn device(&self) -> DeviceId {
607        self.stream.runtime().device()
608    }
609
610    /// Returns the engine-level memory control policy.
611    pub fn memory_control_policy(&self) -> MemoryControlPolicy {
612        match &self.query {
613            QueryScope::Standalone(query) => query.memory_control_policy,
614            QueryScope::Admitted(attempt) => attempt.memory_control_policy,
615        }
616    }
617
618    /// Returns the soft governor grant carried by this context, when present.
619    ///
620    /// The clone is bound to this context's resource epoch. Resident owner
621    /// leases derived from it keep only the service generation (see
622    /// `QueryServiceResourceEpoch::service_generation_only`), because frozen
623    /// residents outlive the attempt.
624    pub fn memory_grant(&self) -> Result<Option<MemoryGrant>> {
625        let Some(attempt) = self.admitted() else {
626            return Ok(None);
627        };
628        self.admitted_memory_grant(attempt, "query_engine.runtime.context.memory_grant").map(Some)
629    }
630
631    fn admitted_memory_grant(&self, attempt: &AdmittedAttempt, operation: &'static str) -> Result<MemoryGrant> {
632        let resource_epoch = self.acquire_resource_epoch(operation)?;
633        Ok(match resource_epoch {
634            Some(resource_epoch) => attempt.memory_grant.with_resource_epoch(resource_epoch),
635            None => attempt.memory_grant.clone(),
636        })
637    }
638
639    #[doc(hidden)]
640    pub fn retain_resident_output_owner(&self, bytes: u64) -> Result<Option<ResidentOwnerLease>> {
641        match self.memory_grant()? {
642            Some(grant) => grant.retain_resident_owner(bytes),
643            None => Ok(None),
644        }
645    }
646
647    /// Shares this stream's member for a budgeted execution stage.
648    ///
649    /// The stage request cannot change the immutable query-domain cap.
650    pub fn execution_budget_token(&self, request: cudf::ExecutionBudgetRequest) -> Result<cudf::ExecutionBudgetToken> {
651        let runtime = self.runtime()?;
652        Ok(cudf::ExecutionBudgetToken::from_shared_lease(&runtime, request, self.stream.member().clone()))
653    }
654
655    /// Returns the immutable domain id for this admitted attempt.
656    pub fn attempt_allocation_domain_id(&self) -> Option<u64> {
657        self.admitted().map(|attempt| attempt.allocation_domain.domain_id())
658    }
659
660    /// Immutable grant identity owned by this admitted attempt domain.
661    #[cfg(test)]
662    pub(crate) fn attempt_allocation_grant_identity(&self) -> Option<GrantIdentity> {
663        self.admitted().map(|attempt| attempt.allocation_domain.grant_identity())
664    }
665
666    /// Samples the allocation-domain current and peak usage.
667    pub fn attempt_allocation_domain_usage(&self) -> Result<Option<cudf::QueryMemoryDomainUsage>> {
668        self.admitted().map(|attempt| attempt.allocation_domain.usage()).transpose()
669    }
670
671    /// Returns the device budget used by reservation tracking.
672    pub fn device_budget_bytes(&self) -> u64 {
673        self.memory_control_policy().device_budget_bytes
674    }
675
676    /// Returns source scheduling controls for bounded source execution.
677    pub fn source_scheduler_policy(&self) -> SourceSchedulerPolicy {
678        self.device.source_scheduler_policy
679    }
680
681    /// Returns the source chunk planning mode.
682    pub fn source_chunk_planning_mode(&self) -> SourceChunkPlanningMode {
683        self.device.source_chunk_planning_mode
684    }
685    /// Returns the selected execution contract for floating statistical aggregates.
686    pub fn statistical_aggregate_execution_mode(&self) -> StatisticalAggregateExecutionMode {
687        self.device.statistical_aggregate_execution_mode
688    }
689
690    /// Returns whether this query preserves DataFusion input order for floating
691    /// statistical aggregates.
692    ///
693    /// Native execution derives this per query from the aggregate execution
694    /// mode and the plan shape, then carries it on every context clone so
695    /// source-read workers on spawned threads observe the same scope as the
696    /// caller thread.
697    pub fn datafusion_order_tracking(&self) -> bool {
698        self.plan.datafusion_order_tracking
699    }
700
701    /// Attaches the per-query DataFusion exact-order scope to this context clone.
702    pub fn with_datafusion_order_tracking(self, enabled: bool) -> Self {
703        self.with_plan(|plan| plan.datafusion_order_tracking = enabled)
704    }
705
706    /// Returns grouped aggregate strategy execution policy.
707    pub fn aggregate_strategy_execution_options(&self) -> AggregateStrategyExecutionOptions {
708        self.device.aggregate_strategy_execution_options
709    }
710
711    /// Join and dynamic-filter thresholds a plan executed without an
712    /// admission artifact is admitted under.
713    pub fn planning_policy(&self) -> PlanningPolicy {
714        self.device.planning_policy
715    }
716
717    /// Returns expert local-throughput source chunk planning policy.
718    pub fn source_chunk_planning_options(&self) -> SourceChunkPlanningOptions {
719        self.device.source_chunk_planning_options
720    }
721}
722
723/// The service memory policy an admitted attempt runs under: its device budget
724/// becomes the grant's.
725pub(in crate::runtime) fn memory_control_policy_for_grant(
726    policy: MemoryControlPolicy,
727    grant: &MemoryGrant,
728) -> MemoryControlPolicy {
729    MemoryControlPolicy { device_budget_bytes: grant.budget_bytes(), ..policy }
730}
731
732fn validate_runtime_observation_query_id(observation: &RuntimeObservationHandle, query_id: &QueryId) -> Result<()> {
733    let Some(observation_query_id) = observation.query_id() else {
734        return Ok(());
735    };
736    if observation_query_id.as_str() == query_id.as_str() {
737        return Ok(());
738    }
739    Err(QueryError::new(
740        QueryErrorCode::InvalidPlan,
741        "query_engine.runtime_observation.identity",
742        "execution context query id conflicts with its runtime observation session",
743    )
744    .with_fact("reason", "runtime_observation_query_id_mismatch")
745    .with_fact("context_query_id", query_id)
746    .with_fact("observation_query_id", observation_query_id))
747}
748
749fn validate_runtime_observation_trace_id(observation: &RuntimeObservationHandle, trace_id: &TraceId) -> Result<()> {
750    // A disabled handle records nothing and carries no trace to conflict with.
751    if !observation.is_enabled() {
752        return Ok(());
753    }
754    let observation_trace_id = observation.trace_id().map(TraceId::as_str);
755    if observation_trace_id == Some(trace_id.as_str()) {
756        return Ok(());
757    }
758    Err(QueryError::new(
759        QueryErrorCode::InvalidPlan,
760        "query_engine.runtime_observation.identity",
761        "execution context trace id conflicts with its runtime observation session",
762    )
763    .with_fact("reason", "runtime_observation_trace_id_mismatch")
764    .with_fact("context_trace_id", trace_id)
765    .with_fact("observation_trace_id", observation_trace_id.unwrap_or("none")))
766}
767
768pub(in crate::runtime) fn validate_service_stream_pool_device(
769    pool: &ServiceStreamPool,
770    runtime_device: DeviceId,
771) -> Result<()> {
772    if pool.device() != runtime_device {
773        return Err(QueryError::new(
774            QueryErrorCode::InvalidPlan,
775            "query_engine.service_stream_pool.device",
776            "service stream pool device must match the execution runtime device",
777        )
778        .with_fact("pool_device_ordinal", pool.device().ordinal())
779        .with_fact("runtime_device_ordinal", runtime_device.ordinal()));
780    }
781    Ok(())
782}