1use std::{
41 cell::Cell,
42 collections::BTreeMap,
43 fmt,
44 panic::{AssertUnwindSafe, catch_unwind},
45 sync::{Arc, Mutex, MutexGuard, PoisonError},
46};
47
48use cudf::runtime::{CudaEvent, CudaStream};
49use query_planning::error::{QueryError, QueryErrorCode, Result};
50use serde::Serialize;
51
52use super::DeviceResourceProfile;
53#[cfg(test)]
54use super::{DeviceCapabilities, DeviceResourceEnvelope};
55use crate::{
56 native_error::cudf_query_error,
57 observability::{AdmissionRequestId, AttemptFaultIsolation, AttemptId},
58 runtime::context::QueryServiceResourceEpoch,
59};
60
61const OPERATION: &str = "query_engine.gpu.device_ledger";
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65pub(crate) enum DeviceAdmissionHealth {
66 Open,
68 Closed { reason: String, isolation: AttemptFaultIsolation, generation: u64 },
74}
75
76impl DeviceAdmissionHealth {
77 fn as_str(&self) -> &'static str {
78 match self {
79 Self::Open => "open",
80 Self::Closed { .. } => "closed",
81 }
82 }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
87pub(crate) struct LedgerSnapshot {
88 pub(crate) device_ordinal: usize,
89 pub(crate) sequence: u64,
92 pub(crate) managed_capacity_bytes: u64,
93 pub(crate) cache_cap_bytes: u64,
95 pub(crate) query_share_bytes: u64,
97 pub(crate) default_query_limit_bytes: u64,
98 pub(crate) max_active_attempts: usize,
99 pub(crate) active_attempts: usize,
100 pub(crate) live_grants_bytes: u64,
101 pub(crate) frozen_resident_bytes: u64,
103 pub(crate) resident_owner_bytes: u64,
105 pub(crate) root_streams_available: usize,
112 pub(crate) auxiliary_streams_available: usize,
114 pub(crate) root_worker_slots_available: usize,
116 pub(crate) auxiliary_worker_slots_available: usize,
118 pub(crate) worker_depth: usize,
120 pub(crate) cache_evictable_resident_bytes: u64,
121 pub(crate) cache_pinned_in_use_bytes: u64,
122 pub(crate) health: DeviceAdmissionHealth,
123}
124
125impl LedgerSnapshot {
126 pub(crate) fn stream_slots_available(&self) -> usize {
128 self.root_streams_available.saturating_add(self.auxiliary_streams_available)
129 }
130
131 pub(crate) fn admissible_query_cap_bytes(&self, requested_cap_bytes: Option<u64>) -> Option<u64> {
138 self.active_attempts.checked_add(1).filter(|active| *active <= self.max_active_attempts)?;
139 let resident_capacity = self.query_share_bytes.checked_sub(self.frozen_resident_bytes)?;
140 let query_cap = requested_cap_bytes.unwrap_or_else(|| self.default_query_limit_bytes.min(resident_capacity));
141 if query_cap == 0 {
142 return None;
143 }
144 self.frozen_resident_bytes
145 .checked_add(self.live_grants_bytes)?
146 .checked_add(query_cap)
147 .filter(|total| *total <= self.query_share_bytes)
148 .map(|_| query_cap)
149 }
150
151 pub(crate) fn can_ever_fit_query_cap(&self, requested_cap_bytes: Option<u64>) -> bool {
154 let Some(resident_capacity) = self.query_share_bytes.checked_sub(self.frozen_resident_bytes) else {
155 return false;
156 };
157 let cap = requested_cap_bytes.unwrap_or_else(|| self.default_query_limit_bytes.min(resident_capacity));
158 cap > 0 && cap <= resident_capacity
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
164pub struct DeviceLedgerReport {
165 pub device_ordinal: usize,
167 pub sequence: u64,
169 pub managed_capacity_bytes: u64,
171 pub(crate) default_query_limit_bytes: u64,
172 pub max_active_attempts: usize,
174 pub active_attempts: usize,
176 pub live_grants_bytes: u64,
178 pub frozen_resident_bytes: u64,
180 pub resident_owner_bytes: u64,
182 pub root_streams_available: usize,
184 pub(crate) auxiliary_streams_available: usize,
186 pub root_worker_slots_available: usize,
188 pub(crate) auxiliary_worker_slots_available: usize,
190 pub(crate) worker_depth: usize,
192 pub available_bytes: u64,
195 pub(crate) free_bytes: u64,
198 pub evictable_bytes: u64,
201 pub cache_evictable_resident_bytes: u64,
203 pub cache_pinned_in_use_bytes: u64,
205 pub health: String,
207 pub grants: Vec<DeviceLedgerGrantRow>,
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
213pub struct DeviceLedgerGrantRow {
214 pub grant_id: u64,
216 pub admission_request_id: AdmissionRequestId,
218 pub grant_bytes: u64,
220 pub resident_owner_bytes: u64,
222 pub(crate) frozen_resident_bytes: Option<u64>,
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
227pub struct GrantIdentity {
228 pub(crate) attempt_id: AttemptId,
229 pub(crate) generation: u64,
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
239pub(crate) enum LedgerTransitionKind {
240 AdmissionClosed,
241 GrantAdmitted,
242 GrantFrozenResident,
243 GrantReleased,
244 EvictableCharged,
246 EvictableReleased,
248 CachePinned,
250 CacheUnpinned,
252 ResidentOwnerCharged,
253 ResidentOwnerReleased,
254}
255
256#[derive(Debug, Clone, PartialEq, Eq)]
258pub(crate) struct LedgerTransition {
259 pub(crate) kind: LedgerTransitionKind,
260 pub(crate) snapshot: LedgerSnapshot,
261}
262
263pub(crate) type LedgerTransitionObserver = Arc<dyn Fn(&LedgerTransition) + Send + Sync + 'static>;
265
266pub(crate) type LedgerAdmissionObserver = Arc<dyn Fn(&LedgerTransition) + Send + Sync + 'static>;
272
273#[derive(Clone)]
275pub struct DeviceLedger {
276 inner: Arc<DeviceLedgerInner>,
277}
278
279struct DeviceLedgerInner {
289 profile: DeviceResourceProfile,
290 state: Mutex<DeviceLedgerState>,
291 transition_observers: Mutex<Arc<[LedgerTransitionObserver]>>,
295 admission_observers: Mutex<Arc<[LedgerAdmissionObserver]>>,
298}
299
300const MAX_LEDGER_TRANSITION_OBSERVER_DEPTH: usize = 64;
301
302#[derive(Clone, Copy)]
308struct LedgerTransitionObserverStack {
309 ledgers: [*const DeviceLedgerInner; MAX_LEDGER_TRANSITION_OBSERVER_DEPTH],
310 len: usize,
311}
312
313impl LedgerTransitionObserverStack {
314 const EMPTY: Self = Self { ledgers: [std::ptr::null(); MAX_LEDGER_TRANSITION_OBSERVER_DEPTH], len: 0 };
315
316 fn contains(&self, ledger: *const DeviceLedgerInner) -> bool {
317 self.ledgers[..self.len].contains(&ledger)
318 }
319}
320
321thread_local! {
322 static LEDGER_TRANSITION_OBSERVER_STACK: Cell<LedgerTransitionObserverStack> = const { Cell::new(LedgerTransitionObserverStack::EMPTY) };
325}
326
327struct LedgerTransitionObserverGuard {
328 ledger: *const DeviceLedgerInner,
329}
330
331impl LedgerTransitionObserverGuard {
332 fn enter(ledger: &Arc<DeviceLedgerInner>) -> Self {
333 let ledger = Arc::as_ptr(ledger);
334 LEDGER_TRANSITION_OBSERVER_STACK.with(|stack| {
335 let mut active = stack.get();
336 assert!(
337 active.len < MAX_LEDGER_TRANSITION_OBSERVER_DEPTH,
338 "ledger transition observer nesting exceeds its fixed bound"
339 );
340 active.ledgers[active.len] = ledger;
341 active.len += 1;
342 stack.set(active);
343 });
344 Self { ledger }
345 }
346}
347
348impl Drop for LedgerTransitionObserverGuard {
349 fn drop(&mut self) {
350 LEDGER_TRANSITION_OBSERVER_STACK.with(|stack| {
351 let mut active = stack.get();
352 debug_assert!(active.len > 0);
353 active.len = active.len.saturating_sub(1);
354 debug_assert_eq!(active.ledgers[active.len], self.ledger);
355 active.ledgers[active.len] = std::ptr::null();
356 stack.set(active);
357 });
358 }
359}
360
361impl fmt::Debug for DeviceLedgerInner {
362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 f.debug_struct("DeviceLedgerInner")
364 .field("device_ordinal", &self.profile.device_ordinal)
365 .field("managed_capacity_bytes", &self.profile.managed_capacity_bytes)
366 .field("state", &self.state)
367 .finish_non_exhaustive()
368 }
369}
370
371#[derive(Debug)]
372struct DeviceLedgerState {
373 sequence: u64,
376 next_grant_generation: u64,
378 grants: BTreeMap<u64, ActiveGrant>,
382 frozen_resident_bytes: u64,
384 health: DeviceAdmissionHealth,
385 evictable_bytes: u64,
387 pinned_bytes: u64,
388}
389
390#[derive(Debug, Clone, Copy)]
391struct ActiveGrant {
392 admission_request_id: AdmissionRequestId,
393 grant_bytes: u64,
395 resident_owner_bytes: u64,
398 pending_frozen_resident_bytes: Option<u64>,
402 frozen_resident_bytes: Option<u64>,
406}
407
408impl DeviceLedgerState {
409 fn live_grants_bytes(&self) -> u64 {
410 self.grants.values().fold(0u64, |sum, grant| {
411 sum.saturating_add(if grant.frozen_resident_bytes.is_some() { 0 } else { grant.grant_bytes })
412 })
413 }
414
415 fn resident_owner_bytes(&self) -> u64 {
416 self.grants.values().fold(0u64, |sum, grant| sum.saturating_add(grant.resident_owner_bytes))
417 }
418}
419
420impl DeviceLedger {
421 pub(crate) fn new(profile: DeviceResourceProfile) -> Self {
423 Self {
424 inner: Arc::new(DeviceLedgerInner {
425 profile,
426 state: Mutex::new(DeviceLedgerState {
427 sequence: 0,
428 next_grant_generation: 1,
429 grants: BTreeMap::new(),
430 frozen_resident_bytes: 0,
431 health: DeviceAdmissionHealth::Open,
432 evictable_bytes: 0,
433 pinned_bytes: 0,
434 }),
435 transition_observers: Mutex::new(Arc::from([])),
436 admission_observers: Mutex::new(Arc::from([])),
437 }),
438 }
439 }
440
441 #[cfg(test)]
443 pub(crate) fn for_test(device_ordinal: usize, managed_capacity_bytes: u64, max_active_attempts: usize) -> Self {
444 let max_active_attempts = max_active_attempts.max(1);
445 let source_work = 1usize;
446 let slots = max_active_attempts.saturating_mul(2).max(max_active_attempts);
447 let default_query_limit_bytes = DeviceResourceProfile::derive_default_query_limit_bytes(
448 device_ordinal,
449 managed_capacity_bytes,
450 0,
451 max_active_attempts,
452 )
453 .expect("test ledger profile must resolve");
454 let profile = DeviceResourceProfile::resolve(DeviceResourceEnvelope {
455 device_ordinal,
456 device_capabilities: DeviceCapabilities::default(),
457 configured_limit_bytes: managed_capacity_bytes,
458 headroom_bytes: 0,
459 backend_reserve_bytes: 0,
460 default_query_limit_bytes,
461 max_active_attempts,
462 source_work_per_attempt: source_work,
463 stream_pool_size: slots,
464 worker_job_capacity: slots,
465 cache_cap_bytes: 0,
466 })
467 .expect("test ledger profile must resolve");
468 Self::new(profile)
469 }
470
471 #[cfg(test)]
473 pub(crate) fn admit_with_configured_floor_and_soft_working_set_hint_for_test(
474 &self,
475 waiter_id: AdmissionRequestId,
476 query_cap_bytes: u64,
477 _unused_soft_working_set_hint_bytes: u64,
478 ) -> Result<Option<MemoryGrant>> {
479 self.try_admit_for_attempt(waiter_id, AttemptId::mint(), Some(query_cap_bytes))
480 }
481
482 pub fn profile(&self) -> &DeviceResourceProfile {
484 &self.inner.profile
485 }
486
487 pub fn device_ordinal(&self) -> usize {
489 self.inner.profile.device_ordinal
490 }
491
492 pub(crate) fn managed_capacity_bytes(&self) -> u64 {
494 self.inner.profile.managed_capacity_bytes
495 }
496
497 fn in_transition_observer(&self) -> bool {
498 let ledger = Arc::as_ptr(&self.inner);
499 LEDGER_TRANSITION_OBSERVER_STACK.with(|stack| stack.get().contains(ledger))
500 }
501
502 fn transition_observer_reentrancy_error(&self, action: &'static str) -> QueryError {
503 QueryError::new(
504 QueryErrorCode::Internal,
505 OPERATION,
506 "device ledger transition observer synchronously re-entered the same ledger",
507 )
508 .with_fact("action", action)
509 .with_fact("reason", "ledger_observer_reentrancy")
510 }
511
512 #[cold]
513 fn panic_on_transition_observer_reentrancy(&self, action: &'static str) -> ! {
514 std::panic::panic_any(self.transition_observer_reentrancy_error(action))
515 }
516
517 fn lock_state(&self, action: &'static str) -> Result<MutexGuard<'_, DeviceLedgerState>> {
523 if self.in_transition_observer() {
524 return Err(self.transition_observer_reentrancy_error(action));
525 }
526 self.inner.state.lock().map_err(|_error| {
527 QueryError::new(QueryErrorCode::Internal, OPERATION, "device ledger mutex was poisoned")
528 .with_fact("action", action)
529 .with_fact("reason", "ledger_mutex_poisoned")
530 })
531 }
532
533 fn transition_observers(&self) -> Arc<[LedgerTransitionObserver]> {
536 Arc::clone(&self.inner.transition_observers.lock().unwrap_or_else(PoisonError::into_inner))
537 }
538
539 fn admission_observers(&self) -> Arc<[LedgerAdmissionObserver]> {
540 Arc::clone(&self.inner.admission_observers.lock().unwrap_or_else(PoisonError::into_inner))
541 }
542
543 fn notify_admission_observers(&self, transition: &LedgerTransition) {
546 for observer in self.admission_observers().iter() {
547 if catch_unwind(AssertUnwindSafe(|| observer(transition))).is_err() {
548 eprintln!("[query-engine] warning: device ledger admission observer panicked");
549 }
550 }
551 }
552
553 fn bump_sequence_and_notify_locked(
559 &self,
560 state: &mut DeviceLedgerState,
561 kind: LedgerTransitionKind,
562 ) -> LedgerTransition {
563 state.sequence = state.sequence.saturating_add(1);
564 let transition = LedgerTransition { kind, snapshot: self.snapshot_locked(state) };
565 for observer in self.transition_observers().iter() {
566 let _delivery = LedgerTransitionObserverGuard::enter(&self.inner);
567 if catch_unwind(AssertUnwindSafe(|| observer(&transition))).is_err() {
568 eprintln!("[query-engine] warning: device ledger transition observer panicked");
569 }
570 }
571 transition
572 }
573
574 pub(crate) fn register_transition_observer(&self, observer: LedgerTransitionObserver) -> Result<()> {
580 let mut registered = self.inner.transition_observers.lock().map_err(|_error| {
581 QueryError::new(QueryErrorCode::Internal, OPERATION, "device ledger transition-observer mutex was poisoned")
582 .with_fact("reason", "transition_observer_mutex_poisoned")
583 })?;
584 let mut next = Vec::with_capacity(registered.len().saturating_add(1));
585 next.extend(registered.iter().cloned());
586 next.push(observer);
587 *registered = Arc::from(next);
588 Ok(())
589 }
590
591 pub(crate) fn register_admission_observer(&self, observer: LedgerAdmissionObserver) -> Result<()> {
598 let mut registered = self.inner.admission_observers.lock().map_err(|_error| {
599 QueryError::new(QueryErrorCode::Internal, OPERATION, "device ledger admission-observer mutex was poisoned")
600 .with_fact("reason", "admission_observer_mutex_poisoned")
601 })?;
602 let mut next = Vec::with_capacity(registered.len().saturating_add(1));
603 next.extend(registered.iter().cloned());
604 next.push(observer);
605 *registered = Arc::from(next);
606 Ok(())
607 }
608
609 pub(crate) fn snapshot(&self) -> Result<LedgerSnapshot> {
611 let state = self.lock_state("snapshot")?;
612 Ok(self.snapshot_locked(&state))
613 }
614
615 fn snapshot_locked(&self, state: &DeviceLedgerState) -> LedgerSnapshot {
616 let profile = &self.inner.profile;
617 let active_attempts = state.grants.values().filter(|grant| grant.frozen_resident_bytes.is_none()).count();
618 let committed_auxiliary_slots = active_attempts.saturating_mul(profile.source_work_per_attempt);
623 LedgerSnapshot {
624 device_ordinal: profile.device_ordinal,
625 sequence: state.sequence,
626 managed_capacity_bytes: profile.managed_capacity_bytes,
627 cache_cap_bytes: profile.cache_cap_bytes,
628 query_share_bytes: profile.query_share_bytes(),
629 default_query_limit_bytes: profile.default_query_limit_bytes,
630 max_active_attempts: profile.max_active_attempts,
631 active_attempts,
632 live_grants_bytes: state.live_grants_bytes(),
633 frozen_resident_bytes: state.frozen_resident_bytes,
634 resident_owner_bytes: state.resident_owner_bytes(),
635 root_streams_available: profile.root_stream_count.saturating_sub(active_attempts),
636 auxiliary_streams_available: profile.aux_source_work_capacity.saturating_sub(committed_auxiliary_slots),
637 root_worker_slots_available: profile.root_lane_count.saturating_sub(active_attempts),
638 auxiliary_worker_slots_available: profile
639 .aux_source_work_capacity
640 .saturating_sub(committed_auxiliary_slots),
641 worker_depth: active_attempts.saturating_add(committed_auxiliary_slots),
642 cache_evictable_resident_bytes: state.evictable_bytes,
644 cache_pinned_in_use_bytes: state.pinned_bytes,
645 health: state.health.clone(),
646 }
647 }
648
649 pub(crate) fn report(&self) -> Result<DeviceLedgerReport> {
651 let state = self.lock_state("report")?;
652 let snapshot = self.snapshot_locked(&state);
653 let grants = state
654 .grants
655 .iter()
656 .map(|(grant_id, active)| DeviceLedgerGrantRow {
657 grant_id: *grant_id,
658 admission_request_id: active.admission_request_id,
659 grant_bytes: active.grant_bytes,
660 resident_owner_bytes: active.resident_owner_bytes,
661 frozen_resident_bytes: active.frozen_resident_bytes,
662 })
663 .collect();
664 Ok(DeviceLedgerReport {
665 device_ordinal: snapshot.device_ordinal,
666 sequence: snapshot.sequence,
667 managed_capacity_bytes: snapshot.managed_capacity_bytes,
668 default_query_limit_bytes: snapshot.default_query_limit_bytes,
669 max_active_attempts: snapshot.max_active_attempts,
670 active_attempts: snapshot.active_attempts,
671 live_grants_bytes: snapshot.live_grants_bytes,
672 frozen_resident_bytes: snapshot.frozen_resident_bytes,
673 resident_owner_bytes: snapshot.resident_owner_bytes,
674 root_streams_available: snapshot.root_streams_available,
675 auxiliary_streams_available: snapshot.auxiliary_streams_available,
676 root_worker_slots_available: snapshot.root_worker_slots_available,
677 auxiliary_worker_slots_available: snapshot.auxiliary_worker_slots_available,
678 worker_depth: snapshot.worker_depth,
679 available_bytes: snapshot
680 .query_share_bytes
681 .saturating_sub(snapshot.live_grants_bytes)
682 .saturating_sub(snapshot.frozen_resident_bytes),
683 free_bytes: snapshot
684 .query_share_bytes
685 .saturating_sub(snapshot.live_grants_bytes)
686 .saturating_sub(snapshot.frozen_resident_bytes),
687 evictable_bytes: state.evictable_bytes,
688 cache_evictable_resident_bytes: snapshot.cache_evictable_resident_bytes,
689 cache_pinned_in_use_bytes: snapshot.cache_pinned_in_use_bytes,
690 health: snapshot.health.as_str().to_string(),
691 grants,
692 })
693 }
694
695 pub(crate) fn health(&self) -> Result<DeviceAdmissionHealth> {
696 let state = self.lock_state("health")?;
697 Ok(state.health.clone())
698 }
699
700 pub(crate) fn close_admission(&self, reason: impl Into<String>) -> Result<u64> {
703 self.close_admission_with(AttemptFaultIsolation::DeviceLocal, reason.into())
704 }
705
706 pub(crate) fn close_admission_for_unknown_isolation(&self, detail: impl Into<String>) -> Result<u64> {
709 self.close_admission_with(AttemptFaultIsolation::Unknown, detail.into())
710 }
711
712 fn close_admission_with(&self, isolation: AttemptFaultIsolation, reason: String) -> Result<u64> {
713 let mut state = self.lock_state("close_admission")?;
714 let generation = match &state.health {
715 DeviceAdmissionHealth::Open => 1,
716 DeviceAdmissionHealth::Closed { generation, .. } => generation.saturating_add(1),
717 };
718 state.health = DeviceAdmissionHealth::Closed { reason, isolation, generation };
719 let transition = self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::AdmissionClosed);
720 drop(state);
722 self.notify_admission_observers(&transition);
723 Ok(generation)
724 }
725
726 pub(crate) fn close_admission_if_open(&self, reason: impl Into<String>) -> Result<bool> {
733 let mut state = self.lock_state("close_admission_if_open")?;
734 if !matches!(state.health, DeviceAdmissionHealth::Open) {
735 return Ok(false);
736 }
737 state.health = DeviceAdmissionHealth::Closed {
738 reason: reason.into(),
739 isolation: AttemptFaultIsolation::DeviceLocal,
740 generation: 1,
741 };
742 let transition = self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::AdmissionClosed);
743 drop(state);
745 self.notify_admission_observers(&transition);
746 Ok(true)
747 }
748
749 #[cfg(test)]
751 pub(crate) fn candidate_query_cap_bytes(&self, requested_cap_bytes: Option<u64>) -> Result<Option<u64>> {
752 let state = self.lock_state("candidate_max_new_grant_bytes")?;
753 let snapshot = self.snapshot_locked(&state);
754 Ok(snapshot.admissible_query_cap_bytes(requested_cap_bytes))
755 }
756
757 pub(crate) fn try_admit_for_attempt(
771 &self,
772 waiter_id: AdmissionRequestId,
773 attempt_id: AttemptId,
774 requested_cap_bytes: Option<u64>,
775 ) -> Result<Option<MemoryGrant>> {
776 if requested_cap_bytes == Some(0) {
777 return Err(QueryError::new(QueryErrorCode::InvalidPlan, OPERATION, "query cap must be greater than zero")
778 .with_fact("reason", "zero_query_cap")
779 .with_fact("admission_request_id", waiter_id));
780 }
781 let mut state = self.lock_state("try_admit")?;
782 if !matches!(state.health, DeviceAdmissionHealth::Open) {
783 return Ok(None);
784 }
785 let snapshot = self.snapshot_locked(&state);
786 let Some(query_cap_bytes) = snapshot.admissible_query_cap_bytes(requested_cap_bytes) else {
787 return Ok(None);
788 };
789 self.commit_grant_locked(&mut state, waiter_id, attempt_id, query_cap_bytes).map(Some)
790 }
791
792 #[cfg(test)]
793 pub(crate) fn try_admit(
794 &self,
795 waiter_id: AdmissionRequestId,
796 requested_cap_bytes: Option<u64>,
797 ) -> Result<Option<MemoryGrant>> {
798 self.try_admit_for_attempt(waiter_id, AttemptId::mint(), requested_cap_bytes)
799 }
800
801 fn commit_grant_locked(
802 &self,
803 state: &mut DeviceLedgerState,
804 waiter_id: AdmissionRequestId,
805 attempt_id: AttemptId,
806 grant_bytes: u64,
807 ) -> Result<MemoryGrant> {
808 let generation = state.next_grant_generation;
809 let next_grant_generation = generation.checked_add(1).ok_or_else(|| {
810 QueryError::new(QueryErrorCode::Internal, OPERATION, "device ledger grant generation exhausted")
811 .with_fact("reason", "grant_generation_exhausted")
812 .with_fact("device_ordinal", self.inner.profile.device_ordinal)
813 .with_fact("attempt_id", attempt_id)
814 })?;
815 let identity = GrantIdentity { attempt_id, generation };
816 state.next_grant_generation = next_grant_generation;
817 state.grants.insert(
818 generation,
819 ActiveGrant {
820 admission_request_id: waiter_id,
821 grant_bytes,
822 resident_owner_bytes: 0,
823 pending_frozen_resident_bytes: None,
824 frozen_resident_bytes: None,
825 },
826 );
827 self.bump_sequence_and_notify_locked(state, LedgerTransitionKind::GrantAdmitted);
828 Ok(MemoryGrant {
829 inner: Arc::new(MemoryGrantInner {
830 ledger: Arc::clone(&self.inner),
831 identity,
832 admission_request_id: waiter_id,
833 grant_bytes,
834 resident_owner_bytes: Default::default(),
835 next_resident_owner_id: std::sync::atomic::AtomicU64::new(1),
836 resident_owner_allocations: Mutex::new(BTreeMap::new()),
837 frozen: Default::default(),
838 frozen_domain: Mutex::new(None),
839 }),
840 resource_epoch: None,
841 })
842 }
843
844 fn release_grant(&self, grant_id: u64) {
850 if self.in_transition_observer() {
851 self.panic_on_transition_observer_reentrancy("release_grant");
852 }
853 let mut state = self.inner.state.lock().unwrap_or_else(PoisonError::into_inner);
855 let Some(active) = state.grants.remove(&grant_id) else {
856 return;
857 };
858 if let Some(resident_bytes) = active.frozen_resident_bytes {
859 state.frozen_resident_bytes = state.frozen_resident_bytes.saturating_sub(resident_bytes);
860 }
861 let transition = self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::GrantReleased);
862 drop(state);
864 self.notify_admission_observers(&transition);
865 }
866
867 pub(crate) fn try_reserve_evictable_bytes(&self, bytes: u64) -> Result<bool> {
875 if bytes == 0 {
876 return Ok(true);
877 }
878 let mut state = self.lock_state("try_reserve_evictable_bytes")?;
879 if state.evictable_bytes.saturating_add(state.pinned_bytes).saturating_add(bytes)
880 > self.inner.profile.cache_cap_bytes
881 {
882 return Ok(false);
883 }
884 state.evictable_bytes = state.evictable_bytes.saturating_add(bytes);
885 self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::EvictableCharged);
886 Ok(true)
887 }
888
889 pub(crate) fn pin_cache_residency_charge(&self, bytes: u64) -> Result<()> {
895 if bytes == 0 {
896 return Ok(());
897 }
898 let mut state = self.lock_state("pin_cache_residency_charge")?;
899 if state.evictable_bytes < bytes {
900 return Err(QueryError::new(
901 QueryErrorCode::Internal,
902 OPERATION,
903 "resident-cache pin is missing its evictable ledger charge",
904 )
905 .with_fact("reason", "cache_evictable_charge_missing")
906 .with_fact("requested_bytes", bytes)
907 .with_fact("evictable_bytes", state.evictable_bytes));
908 }
909 let pinned_bytes = state.pinned_bytes.checked_add(bytes).ok_or_else(|| {
910 QueryError::new(QueryErrorCode::Internal, OPERATION, "resident-cache pinned ledger charge overflowed")
911 .with_fact("reason", "cache_pinned_charge_overflow")
912 .with_fact("requested_bytes", bytes)
913 .with_fact("pinned_bytes", state.pinned_bytes)
914 })?;
915 state.evictable_bytes -= bytes;
916 state.pinned_bytes = pinned_bytes;
917 self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::CachePinned);
918 Ok(())
919 }
920
921 pub(crate) fn unpin_cache_residency_charge(&self, bytes: u64) -> Result<()> {
923 if bytes == 0 {
924 return Ok(());
925 }
926 let mut state = self.lock_state("unpin_cache_residency_charge")?;
927 if state.pinned_bytes < bytes {
928 return Err(QueryError::new(
929 QueryErrorCode::Internal,
930 OPERATION,
931 "resident-cache unpin is missing its pinned ledger charge",
932 )
933 .with_fact("reason", "cache_pinned_charge_missing")
934 .with_fact("requested_bytes", bytes)
935 .with_fact("pinned_bytes", state.pinned_bytes));
936 }
937 let evictable_bytes = state.evictable_bytes.checked_add(bytes).ok_or_else(|| {
938 QueryError::new(QueryErrorCode::Internal, OPERATION, "resident-cache evictable ledger charge overflowed")
939 .with_fact("reason", "cache_evictable_charge_overflow")
940 .with_fact("requested_bytes", bytes)
941 .with_fact("evictable_bytes", state.evictable_bytes)
942 })?;
943 state.pinned_bytes -= bytes;
944 state.evictable_bytes = evictable_bytes;
945 self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::CacheUnpinned);
946 Ok(())
947 }
948
949 pub(crate) fn release_cache_residency_charge_after_destroy(&self, bytes: u64) -> Result<()> {
955 self.release_exact_evictable_cache_charge(bytes, "release_cache_residency_charge_after_destroy")
956 }
957
958 pub(crate) fn release_cache_residency_precharge_excess(&self, bytes: u64) -> Result<()> {
961 self.release_exact_evictable_cache_charge(bytes, "release_cache_residency_precharge_excess")
962 }
963
964 fn release_exact_evictable_cache_charge(&self, bytes: u64, action: &'static str) -> Result<()> {
965 if bytes == 0 {
966 return Ok(());
967 }
968 let mut state = self.lock_state(action)?;
969 if state.evictable_bytes < bytes {
970 return Err(QueryError::new(
971 QueryErrorCode::Internal,
972 OPERATION,
973 "resident-cache exact release is missing its evictable ledger charge",
974 )
975 .with_fact("reason", "cache_exact_charge_missing")
976 .with_fact("action", action)
977 .with_fact("requested_bytes", bytes)
978 .with_fact("evictable_bytes", state.evictable_bytes));
979 }
980 state.evictable_bytes -= bytes;
981 self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::EvictableReleased);
982 Ok(())
983 }
984}
985
986#[derive(Clone)]
993pub struct MemoryGrant {
994 inner: Arc<MemoryGrantInner>,
995 resource_epoch: Option<Arc<QueryServiceResourceEpoch>>,
999}
1000
1001#[derive(Clone)]
1003pub struct ResidentOwnerLease {
1004 inner: Arc<ResidentOwnerLeaseInner>,
1005 _reader: Option<Arc<ResidentReader>>,
1008}
1009
1010struct ResidentOwnerLeaseInner {
1011 grant: MemoryGrant,
1014 bytes: u64,
1016 owner_id: u64,
1017 completion_fences: Mutex<ResidentCompletionFences>,
1018}
1019
1020#[derive(Default)]
1025struct ResidentCompletionFences {
1026 fences: Vec<ResidentCompletionFence>,
1027 record_failure: Option<String>,
1030}
1031
1032struct ResidentCompletionFence {
1033 stream_identity: usize,
1034 event: CudaEvent,
1035}
1036
1037struct ResidentReader {
1040 owner: Arc<ResidentOwnerLeaseInner>,
1041 stream: CudaStream,
1042}
1043
1044impl Drop for ResidentReader {
1045 fn drop(&mut self) {
1046 if let Err(error) = self.owner.record_completion(&self.stream) {
1047 let mut fences = self.owner.completion_fences.lock().unwrap_or_else(PoisonError::into_inner);
1048 fences.record_failure.get_or_insert_with(|| format!("resident reader completion failed: {error}"));
1049 }
1050 }
1051}
1052
1053struct MemoryGrantInner {
1054 ledger: Arc<DeviceLedgerInner>,
1055 identity: GrantIdentity,
1056 admission_request_id: AdmissionRequestId,
1057 grant_bytes: u64,
1058 resident_owner_bytes: std::sync::atomic::AtomicU64,
1061 next_resident_owner_id: std::sync::atomic::AtomicU64,
1062 resident_owner_allocations: Mutex<BTreeMap<u64, Arc<[usize]>>>,
1065 frozen: std::sync::atomic::AtomicBool,
1069 frozen_domain: Mutex<Option<Arc<cudf::QueryMemoryDomain>>>,
1073}
1074
1075impl Drop for MemoryGrantInner {
1076 fn drop(&mut self) {
1081 let ledger = DeviceLedger { inner: Arc::clone(&self.ledger) };
1082 let frozen_cleanup_failure = match self.frozen_domain.get_mut().unwrap_or_else(PoisonError::into_inner) {
1085 Some(domain) => match domain.usage() {
1086 Ok(usage)
1087 if usage.active_members == 0
1088 && usage.current_allocations == 0
1089 && usage.current_bytes == 0
1090 && usage.current_aligned_bytes == 0 =>
1091 {
1092 None
1093 },
1094 Ok(usage) => Some(format!(
1095 "frozen resident domain did not reach zero at final release (domain_id={}, active_members={}, current_allocations={}, current_bytes={}, current_aligned_bytes={})",
1096 usage.domain_id,
1097 usage.active_members,
1098 usage.current_allocations,
1099 usage.current_bytes,
1100 usage.current_aligned_bytes
1101 )),
1102 Err(error) => Some(format!("frozen resident domain final usage sample failed: {error}")),
1103 },
1104 None => None,
1105 };
1106 if let Some(reason) = frozen_cleanup_failure
1107 && let Err(error) = ledger.close_admission_if_open(reason)
1108 {
1109 eprintln!("[query-engine] warning: failed to close admission after frozen resident cleanup: {error}");
1110 }
1111 ledger.release_grant(self.identity.generation);
1112 }
1113}
1114
1115impl MemoryGrant {
1116 pub(crate) fn with_resource_epoch(&self, resource_epoch: Arc<QueryServiceResourceEpoch>) -> Self {
1117 Self { inner: Arc::clone(&self.inner), resource_epoch: Some(resource_epoch) }
1118 }
1119
1120 fn check_resource_epoch(&self, operation: &'static str) -> Result<()> {
1121 self.resource_epoch.as_ref().map_or(Ok(()), |epoch| epoch.check_open(operation))
1122 }
1123
1124 pub(crate) fn grant_id(&self) -> u64 {
1126 self.inner.identity.generation
1127 }
1128
1129 pub fn identity(&self) -> GrantIdentity {
1131 self.inner.identity
1132 }
1133
1134 pub fn admission_request_id(&self) -> AdmissionRequestId {
1136 self.inner.admission_request_id
1137 }
1138
1139 pub(crate) fn identity_key(&self) -> usize {
1140 Arc::as_ptr(&self.inner) as usize
1141 }
1142
1143 pub fn budget_bytes(&self) -> u64 {
1147 self.inner.grant_bytes
1148 }
1149
1150 #[cfg(test)]
1151 pub(crate) fn freeze_resident(&self, resident_bytes: u64) -> Result<()> {
1152 self.prepare_frozen_resident_charge(resident_bytes, None)?;
1153 self.publish_frozen_resident()
1154 }
1155
1156 #[cfg(test)]
1157 pub(super) fn prepare_frozen_resident_for_test(&self, resident_bytes: u64) -> Result<()> {
1158 self.prepare_frozen_resident_charge(resident_bytes, None)
1159 }
1160
1161 pub(crate) fn freeze_resident_with_domain(
1173 &self,
1174 resident_bytes: u64,
1175 domain: Arc<cudf::QueryMemoryDomain>,
1176 ) -> Result<()> {
1177 self.reconcile_resident_owner_bytes(resident_bytes)?;
1178 self.prepare_frozen_resident_charge(resident_bytes, Some(domain))
1179 }
1180
1181 fn reconcile_resident_owner_bytes(&self, measured_bytes: u64) -> Result<()> {
1183 if measured_bytes > self.inner.grant_bytes {
1184 return Err(QueryError::new(
1185 QueryErrorCode::InternalInvariantViolation,
1186 "query_engine.runtime.gpu.memory_grant.reconcile_resident_owner_bytes",
1187 "measured resident bytes exceed the immutable query cap",
1188 )
1189 .with_fact("measured_bytes", measured_bytes)
1190 .with_fact("query_cap_bytes", self.inner.grant_bytes));
1191 }
1192 let ledger = DeviceLedger { inner: Arc::clone(&self.inner.ledger) };
1193 let mut state = ledger.lock_state("reconcile_resident_owner_bytes")?;
1194 let Some(active) = state.grants.get_mut(&self.inner.identity.generation) else {
1195 return Err(QueryError::new(
1196 QueryErrorCode::InternalInvariantViolation,
1197 "query_engine.runtime.gpu.memory_grant.reconcile_resident_owner_bytes",
1198 "query grant is missing from its device ledger",
1199 )
1200 .with_fact("grant_id", self.grant_id()));
1201 };
1202 if active.resident_owner_bytes == measured_bytes {
1203 return Ok(());
1204 }
1205 active.resident_owner_bytes = measured_bytes;
1206 self.inner.resident_owner_bytes.store(measured_bytes, std::sync::atomic::Ordering::Release);
1207 ledger.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::ResidentOwnerCharged);
1208 Ok(())
1209 }
1210
1211 fn prepare_frozen_resident_charge(
1216 &self,
1217 resident_bytes: u64,
1218 domain: Option<Arc<cudf::QueryMemoryDomain>>,
1219 ) -> Result<()> {
1220 if resident_bytes > self.inner.grant_bytes {
1221 return Err(QueryError::new(
1222 QueryErrorCode::InternalInvariantViolation,
1223 "query_engine.runtime.gpu.memory_grant.freeze_resident",
1224 "frozen resident charge must fit the query cap",
1225 )
1226 .with_fact("resident_bytes", resident_bytes)
1227 .with_fact("query_cap_bytes", self.inner.grant_bytes));
1228 }
1229 let ledger = DeviceLedger { inner: Arc::clone(&self.inner.ledger) };
1230 let mut state = ledger.lock_state("freeze_resident")?;
1231 let active = state.grants.get(&self.inner.identity.generation).ok_or_else(|| {
1232 QueryError::new(
1233 QueryErrorCode::InternalInvariantViolation,
1234 "query_engine.runtime.gpu.memory_grant.freeze_resident",
1235 "query grant is missing from its device ledger",
1236 )
1237 .with_fact("grant_id", self.grant_id())
1238 })?;
1239 let existing = active.frozen_resident_bytes.or(active.pending_frozen_resident_bytes);
1240 if let Some(existing_bytes) = existing {
1241 let frozen_domain = self.inner.frozen_domain.lock().map_err(|_error| {
1242 QueryError::new(
1243 QueryErrorCode::InternalInvariantViolation,
1244 "query_engine.runtime.gpu.memory_grant.freeze_resident",
1245 "frozen resident domain owner mutex was poisoned",
1246 )
1247 })?;
1248 let same_domain = match (frozen_domain.as_ref(), domain.as_ref()) {
1249 (Some(existing), Some(domain)) => Arc::ptr_eq(existing, domain),
1250 (None, None) => true,
1251 _ => false,
1252 };
1253 return (existing_bytes == resident_bytes && same_domain).then_some(()).ok_or_else(|| {
1254 QueryError::new(
1255 QueryErrorCode::InternalInvariantViolation,
1256 "query_engine.runtime.gpu.memory_grant.freeze_resident",
1257 "query grant was frozen with a different resident charge",
1258 )
1259 .with_fact("grant_id", self.grant_id())
1260 .with_fact("existing_resident_bytes", existing_bytes)
1261 .with_fact("resident_bytes", resident_bytes)
1262 });
1263 }
1264 state
1265 .grants
1266 .get_mut(&self.inner.identity.generation)
1267 .expect("grant existence was checked under the same ledger lock")
1268 .pending_frozen_resident_bytes = Some(resident_bytes);
1269 if let Some(domain) = domain {
1270 *self.inner.frozen_domain.lock().map_err(|_error| {
1271 QueryError::new(
1272 QueryErrorCode::InternalInvariantViolation,
1273 "query_engine.runtime.gpu.memory_grant.freeze_resident",
1274 "frozen resident domain owner mutex was poisoned",
1275 )
1276 })? = Some(domain);
1277 }
1278 self.inner.frozen.store(true, std::sync::atomic::Ordering::Release);
1279 Ok(())
1280 }
1281
1282 pub(crate) fn validate_frozen_resident_publish(&self) -> Result<()> {
1290 let ledger = DeviceLedger { inner: Arc::clone(&self.inner.ledger) };
1291 let state = ledger.lock_state("validate_frozen_resident_publish")?;
1292 let active = state.grants.get(&self.inner.identity.generation).ok_or_else(|| {
1293 QueryError::new(
1294 QueryErrorCode::InternalInvariantViolation,
1295 "query_engine.runtime.gpu.memory_grant.validate_frozen_resident_publish",
1296 "prepared frozen resident grant is missing from its device ledger",
1297 )
1298 .with_fact("grant_id", self.grant_id())
1299 })?;
1300 if active.frozen_resident_bytes.is_some() {
1301 return Ok(());
1302 }
1303 let resident_bytes = active.pending_frozen_resident_bytes.ok_or_else(|| {
1304 QueryError::new(
1305 QueryErrorCode::InternalInvariantViolation,
1306 "query_engine.runtime.gpu.memory_grant.validate_frozen_resident_publish",
1307 "frozen resident transition was not prepared",
1308 )
1309 .with_fact("grant_id", self.grant_id())
1310 })?;
1311 state.frozen_resident_bytes.checked_add(resident_bytes).map(|_| ()).ok_or_else(|| {
1312 QueryError::new(
1313 QueryErrorCode::InternalInvariantViolation,
1314 "query_engine.runtime.gpu.memory_grant.validate_frozen_resident_publish",
1315 "frozen resident accounting would overflow",
1316 )
1317 .with_fact("grant_id", self.grant_id())
1318 .with_fact("resident_bytes", resident_bytes)
1319 })
1320 }
1321
1322 pub(crate) fn close_device_admission(&self, reason: &'static str) -> Result<bool> {
1325 DeviceLedger { inner: Arc::clone(&self.inner.ledger) }.close_admission_if_open(reason)
1326 }
1327
1328 pub(crate) fn publish_frozen_resident(&self) -> Result<()> {
1334 let ledger = DeviceLedger { inner: Arc::clone(&self.inner.ledger) };
1335 let mut state = ledger.lock_state("publish_frozen_resident")?;
1336 let active = state.grants.get_mut(&self.inner.identity.generation).ok_or_else(|| {
1337 QueryError::new(
1338 QueryErrorCode::InternalInvariantViolation,
1339 "query_engine.runtime.gpu.memory_grant.publish_frozen_resident",
1340 "prepared frozen resident grant is missing from its device ledger",
1341 )
1342 .with_fact("grant_id", self.grant_id())
1343 })?;
1344 if active.frozen_resident_bytes.is_some() {
1345 return Ok(());
1346 }
1347 let resident_bytes = active.pending_frozen_resident_bytes.take().ok_or_else(|| {
1348 QueryError::new(
1349 QueryErrorCode::InternalInvariantViolation,
1350 "query_engine.runtime.gpu.memory_grant.publish_frozen_resident",
1351 "frozen resident transition was not prepared",
1352 )
1353 .with_fact("grant_id", self.grant_id())
1354 })?;
1355 state.frozen_resident_bytes = state.frozen_resident_bytes.checked_add(resident_bytes).ok_or_else(|| {
1356 QueryError::new(
1357 QueryErrorCode::InternalInvariantViolation,
1358 "query_engine.runtime.gpu.memory_grant.publish_frozen_resident",
1359 "frozen resident accounting overflowed",
1360 )
1361 })?;
1362 state
1363 .grants
1364 .get_mut(&self.inner.identity.generation)
1365 .expect("grant existence was checked under the same ledger lock")
1366 .frozen_resident_bytes = Some(resident_bytes);
1367 let transition = ledger.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::GrantFrozenResident);
1368 drop(state);
1370 ledger.notify_admission_observers(&transition);
1371 Ok(())
1372 }
1373
1374 pub(crate) fn is_frozen_resident(&self) -> bool {
1375 self.inner.frozen.load(std::sync::atomic::Ordering::Acquire)
1376 }
1377
1378 pub fn device_ordinal(&self) -> usize {
1380 self.inner.ledger.profile.device_ordinal
1381 }
1382
1383 pub(crate) fn resident_owner_bytes(&self) -> u64 {
1385 self.inner.resident_owner_bytes.load(std::sync::atomic::Ordering::Acquire)
1386 }
1387
1388 fn add_resident_owner_bytes(&self, bytes: u64) -> Result<()> {
1390 self.check_resource_epoch("query_engine.runtime.gpu.memory_grant.add_resident_owner_bytes")?;
1391 if bytes == 0 {
1392 return Ok(());
1393 }
1394 let ledger = DeviceLedger { inner: Arc::clone(&self.inner.ledger) };
1395 let mut state = ledger.lock_state("add_resident_owner_bytes")?;
1396 let Some(active) = state.grants.get_mut(&self.inner.identity.generation) else {
1397 return Err(QueryError::new(
1398 QueryErrorCode::InternalInvariantViolation,
1399 "query_engine.runtime.gpu.memory_grant.add_resident_owner_bytes",
1400 "resident owner was published after its grant left the device ledger",
1401 ));
1402 };
1403 if active.frozen_resident_bytes.is_some() {
1404 return Err(QueryError::new(
1405 QueryErrorCode::InternalInvariantViolation,
1406 "query_engine.runtime.gpu.memory_grant.add_resident_owner_bytes",
1407 "resident owner was published after the grant was frozen",
1408 ));
1409 }
1410 let next_resident_owner_bytes = active.resident_owner_bytes.checked_add(bytes).ok_or_else(|| {
1411 QueryError::new(
1412 QueryErrorCode::InternalInvariantViolation,
1413 "query_engine.runtime.gpu.memory_grant.add_resident_owner_bytes",
1414 "resident owner byte accounting overflowed",
1415 )
1416 })?;
1417 active.resident_owner_bytes = next_resident_owner_bytes;
1418 self.inner.resident_owner_bytes.store(next_resident_owner_bytes, std::sync::atomic::Ordering::Release);
1419 ledger.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::ResidentOwnerCharged);
1420 Ok(())
1421 }
1422
1423 fn release_resident_owner_bytes(&self, bytes: u64) {
1424 if bytes == 0 {
1425 return;
1426 }
1427 let ledger = DeviceLedger { inner: Arc::clone(&self.inner.ledger) };
1428 if ledger.in_transition_observer() {
1429 ledger.panic_on_transition_observer_reentrancy("release_resident_owner_bytes");
1430 }
1431 let mut state = ledger.inner.state.lock().unwrap_or_else(PoisonError::into_inner);
1435 self.release_locked(&ledger, &mut state, bytes);
1436 }
1437
1438 fn release_locked(&self, ledger: &DeviceLedger, state: &mut DeviceLedgerState, bytes: u64) {
1440 let Some(active) = state.grants.get_mut(&self.inner.identity.generation) else {
1441 return;
1442 };
1443 let next_resident_owner_bytes = match active.resident_owner_bytes.checked_sub(bytes) {
1444 Some(next) => next,
1445 None if active.frozen_resident_bytes.is_some() || active.pending_frozen_resident_bytes.is_some() => 0,
1448 None => {
1449 eprintln!("[query-engine] warning: resident owner release exceeded its active charge");
1450 return;
1451 },
1452 };
1453 active.resident_owner_bytes = next_resident_owner_bytes;
1454 self.inner.resident_owner_bytes.store(next_resident_owner_bytes, std::sync::atomic::Ordering::Release);
1455 ledger.bump_sequence_and_notify_locked(state, LedgerTransitionKind::ResidentOwnerReleased);
1456 }
1457
1458 pub(crate) fn retain_resident_owner(&self, bytes: u64) -> Result<Option<ResidentOwnerLease>> {
1460 if bytes == 0 {
1461 return Ok(None);
1462 }
1463 self.add_resident_owner_bytes(bytes)?;
1464 Ok(Some(self.new_resident_owner_lease(bytes)?))
1465 }
1466
1467 #[cfg(any(test, feature = "test-hooks"))]
1469 #[doc(hidden)]
1470 pub fn retain_resident_owner_for_test(&self, bytes: u64) -> Result<Option<ResidentOwnerLease>> {
1471 self.retain_resident_owner(bytes)
1472 }
1473
1474 fn new_resident_owner_lease(&self, bytes: u64) -> Result<ResidentOwnerLease> {
1475 let owner_id = self.inner.next_resident_owner_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1476 self.inner
1477 .resident_owner_allocations
1478 .lock()
1479 .unwrap_or_else(std::sync::PoisonError::into_inner)
1480 .insert(owner_id, Arc::from([]));
1481 let resource_epoch = self
1488 .resource_epoch
1489 .as_ref()
1490 .map(|epoch| {
1491 epoch.service_generation_only("query_engine.runtime.gpu.memory_grant.new_resident_owner_lease")
1492 })
1493 .transpose()?;
1494 Ok(ResidentOwnerLease {
1495 inner: Arc::new(ResidentOwnerLeaseInner {
1496 grant: MemoryGrant { inner: Arc::clone(&self.inner), resource_epoch },
1497 bytes,
1498 owner_id,
1499 completion_fences: Mutex::new(ResidentCompletionFences::default()),
1500 }),
1501 _reader: None,
1502 })
1503 }
1504
1505 pub(crate) fn published_resident_owner_count(&self) -> usize {
1506 self.inner.resident_owner_allocations.lock().unwrap_or_else(std::sync::PoisonError::into_inner).len()
1507 }
1508
1509 pub(crate) fn published_resident_allocation_pointers(&self) -> std::collections::BTreeSet<usize> {
1510 self.inner
1511 .resident_owner_allocations
1512 .lock()
1513 .unwrap_or_else(std::sync::PoisonError::into_inner)
1514 .values()
1515 .flat_map(|pointers| pointers.iter().copied())
1516 .collect()
1517 }
1518}
1519
1520impl ResidentOwnerLease {
1521 pub(crate) fn record_completion(&self, stream: &CudaStream) -> Result<()> {
1525 self.inner.record_completion(stream)
1526 }
1527
1528 pub(crate) fn for_reader(&self, stream: CudaStream) -> Self {
1532 Self {
1533 inner: Arc::clone(&self.inner),
1534 _reader: Some(Arc::new(ResidentReader { owner: Arc::clone(&self.inner), stream })),
1535 }
1536 }
1537
1538 pub(crate) fn set_backing_allocation_pointers(&self, mut pointers: Vec<usize>) {
1539 pointers.retain(|pointer| *pointer != 0);
1540 pointers.sort_unstable();
1541 pointers.dedup();
1542 self.inner
1543 .grant
1544 .inner
1545 .resident_owner_allocations
1546 .lock()
1547 .unwrap_or_else(std::sync::PoisonError::into_inner)
1548 .insert(self.inner.owner_id, Arc::from(pointers));
1549 }
1550
1551 pub(crate) fn is_frozen_resident(&self) -> bool {
1552 self.inner.grant.is_frozen_resident()
1553 }
1554
1555 pub(crate) fn bytes(&self) -> u64 {
1557 self.inner.bytes
1558 }
1559
1560 pub(crate) fn belongs_to(&self, grant: &MemoryGrant) -> bool {
1562 self.inner.grant.identity_key() == grant.identity_key()
1563 }
1564
1565 #[cfg(test)]
1571 pub(crate) fn has_resource_epoch(&self) -> bool {
1572 self.inner.grant.resource_epoch.is_some()
1573 }
1574
1575 #[cfg(test)]
1580 pub(crate) fn retains_query_anchor(&self) -> bool {
1581 self.inner.grant.resource_epoch.as_ref().is_some_and(|epoch| epoch.has_query_anchor())
1582 }
1583
1584 pub(crate) fn identity_key(&self) -> usize {
1585 Arc::as_ptr(&self.inner) as usize
1586 }
1587
1588 pub(crate) fn retain_derived_owner(&self, bytes: u64) -> Result<Option<Self>> {
1590 self.inner.grant.retain_resident_owner(bytes)
1591 }
1592}
1593
1594impl ResidentOwnerLeaseInner {
1595 fn record_completion(&self, stream: &CudaStream) -> Result<()> {
1596 let operation = "query_engine.runtime.gpu.frozen_resident_lease.completion_fence";
1597 let mut completion_fences = self.completion_fences.lock().map_err(|_error| {
1598 QueryError::new(
1599 QueryErrorCode::InternalInvariantViolation,
1600 operation,
1601 "frozen resident completion fence mutex was poisoned",
1602 )
1603 })?;
1604 let event = stream.record_completion_event().map_err(|error| {
1607 cudf_query_error(operation, "failed to record the resident output's completion event", error)
1608 })?;
1609 let stream_identity = stream.identity();
1610 match completion_fences.fences.iter_mut().find(|fence| fence.stream_identity == stream_identity) {
1611 Some(fence) => fence.event = event,
1612 None => completion_fences.fences.push(ResidentCompletionFence { stream_identity, event }),
1613 }
1614 Ok(())
1615 }
1616
1617 fn wait_for_completions(&mut self) -> Option<String> {
1625 let completion_fences = match self.completion_fences.get_mut() {
1626 Ok(completion_fences) => completion_fences,
1627 Err(_error) => return Some("frozen resident completion fence mutex was poisoned".to_string()),
1628 };
1629 if completion_fences.fences.is_empty() {
1630 return Some("frozen resident owner reached final release without a completion fence".to_string());
1631 }
1632 let mut failure = completion_fences.record_failure.take();
1633 for fence in &completion_fences.fences {
1634 if let Err(error) =
1635 fence.event.synchronize_for_teardown("query_engine.runtime.gpu.frozen_resident_lease.drop")
1636 {
1637 failure.get_or_insert_with(|| format!("frozen resident completion fence failed: {error}"));
1638 }
1639 }
1640 failure
1641 }
1642}
1643
1644impl Drop for ResidentOwnerLeaseInner {
1645 fn drop(&mut self) {
1652 let cleanup_failure = if self.grant.is_frozen_resident() { self.wait_for_completions() } else { None };
1653 if let Some(reason) = cleanup_failure {
1654 let ledger = DeviceLedger { inner: Arc::clone(&self.grant.inner.ledger) };
1655 if let Err(error) = ledger.close_admission_if_open(reason) {
1656 eprintln!(
1657 "[query-engine] warning: failed to close admission after resident completion failure: {error}"
1658 );
1659 }
1660 }
1661 self.grant
1662 .inner
1663 .resident_owner_allocations
1664 .lock()
1665 .unwrap_or_else(std::sync::PoisonError::into_inner)
1666 .remove(&self.owner_id);
1667 self.grant.release_resident_owner_bytes(self.bytes);
1668 }
1669}
1670
1671impl fmt::Debug for MemoryGrant {
1672 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1673 f.debug_struct("MemoryGrant")
1674 .field("identity", &self.inner.identity)
1675 .field("admission_request_id", &self.inner.admission_request_id)
1676 .field("grant_bytes", &self.inner.grant_bytes)
1677 .field("resident_owner_bytes", &self.resident_owner_bytes())
1678 .finish_non_exhaustive()
1679 }
1680}
1681
1682impl fmt::Debug for DeviceLedger {
1683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1684 f.debug_struct("DeviceLedger")
1685 .field("device_ordinal", &self.inner.profile.device_ordinal)
1686 .field("managed_capacity_bytes", &self.inner.profile.managed_capacity_bytes)
1687 .finish_non_exhaustive()
1688 }
1689}
1690
1691#[cfg(test)]
1692mod tests;