use std::{
cell::Cell,
collections::BTreeMap,
fmt,
panic::{AssertUnwindSafe, catch_unwind},
sync::{Arc, Mutex, MutexGuard, PoisonError},
};
use cudf::runtime::{CudaEvent, CudaStream};
use query_planning::error::{QueryError, QueryErrorCode, Result};
use serde::Serialize;
use super::DeviceResourceProfile;
#[cfg(test)]
use super::{DeviceCapabilities, DeviceResourceEnvelope};
use crate::{
native_error::cudf_query_error,
observability::{AdmissionRequestId, AttemptFaultIsolation, AttemptId},
runtime::context::QueryServiceResourceEpoch,
};
const OPERATION: &str = "query_engine.gpu.device_ledger";
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum DeviceAdmissionHealth {
Open,
Closed { reason: String, isolation: AttemptFaultIsolation, generation: u64 },
}
impl DeviceAdmissionHealth {
fn as_str(&self) -> &'static str {
match self {
Self::Open => "open",
Self::Closed { .. } => "closed",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct LedgerSnapshot {
pub(crate) device_ordinal: usize,
pub(crate) sequence: u64,
pub(crate) managed_capacity_bytes: u64,
pub(crate) cache_cap_bytes: u64,
pub(crate) query_share_bytes: u64,
pub(crate) default_query_limit_bytes: u64,
pub(crate) max_active_attempts: usize,
pub(crate) active_attempts: usize,
pub(crate) live_grants_bytes: u64,
pub(crate) frozen_resident_bytes: u64,
pub(crate) resident_owner_bytes: u64,
pub(crate) root_streams_available: usize,
pub(crate) auxiliary_streams_available: usize,
pub(crate) root_worker_slots_available: usize,
pub(crate) auxiliary_worker_slots_available: usize,
pub(crate) worker_depth: usize,
pub(crate) cache_evictable_resident_bytes: u64,
pub(crate) cache_pinned_in_use_bytes: u64,
pub(crate) health: DeviceAdmissionHealth,
}
impl LedgerSnapshot {
pub(crate) fn stream_slots_available(&self) -> usize {
self.root_streams_available.saturating_add(self.auxiliary_streams_available)
}
pub(crate) fn admissible_query_cap_bytes(&self, requested_cap_bytes: Option<u64>) -> Option<u64> {
self.active_attempts.checked_add(1).filter(|active| *active <= self.max_active_attempts)?;
let resident_capacity = self.query_share_bytes.checked_sub(self.frozen_resident_bytes)?;
let query_cap = requested_cap_bytes.unwrap_or_else(|| self.default_query_limit_bytes.min(resident_capacity));
if query_cap == 0 {
return None;
}
self.frozen_resident_bytes
.checked_add(self.live_grants_bytes)?
.checked_add(query_cap)
.filter(|total| *total <= self.query_share_bytes)
.map(|_| query_cap)
}
pub(crate) fn can_ever_fit_query_cap(&self, requested_cap_bytes: Option<u64>) -> bool {
let Some(resident_capacity) = self.query_share_bytes.checked_sub(self.frozen_resident_bytes) else {
return false;
};
let cap = requested_cap_bytes.unwrap_or_else(|| self.default_query_limit_bytes.min(resident_capacity));
cap > 0 && cap <= resident_capacity
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct DeviceLedgerReport {
pub device_ordinal: usize,
pub sequence: u64,
pub managed_capacity_bytes: u64,
pub(crate) default_query_limit_bytes: u64,
pub max_active_attempts: usize,
pub active_attempts: usize,
pub live_grants_bytes: u64,
pub frozen_resident_bytes: u64,
pub resident_owner_bytes: u64,
pub root_streams_available: usize,
pub(crate) auxiliary_streams_available: usize,
pub root_worker_slots_available: usize,
pub(crate) auxiliary_worker_slots_available: usize,
pub(crate) worker_depth: usize,
pub available_bytes: u64,
pub(crate) free_bytes: u64,
pub evictable_bytes: u64,
pub cache_evictable_resident_bytes: u64,
pub cache_pinned_in_use_bytes: u64,
pub health: String,
pub grants: Vec<DeviceLedgerGrantRow>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct DeviceLedgerGrantRow {
pub grant_id: u64,
pub admission_request_id: AdmissionRequestId,
pub grant_bytes: u64,
pub resident_owner_bytes: u64,
pub(crate) frozen_resident_bytes: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GrantIdentity {
pub(crate) attempt_id: AttemptId,
pub(crate) generation: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum LedgerTransitionKind {
AdmissionClosed,
GrantAdmitted,
GrantFrozenResident,
GrantReleased,
EvictableCharged,
EvictableReleased,
CachePinned,
CacheUnpinned,
ResidentOwnerCharged,
ResidentOwnerReleased,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct LedgerTransition {
pub(crate) kind: LedgerTransitionKind,
pub(crate) snapshot: LedgerSnapshot,
}
pub(crate) type LedgerTransitionObserver = Arc<dyn Fn(&LedgerTransition) + Send + Sync + 'static>;
pub(crate) type LedgerAdmissionObserver = Arc<dyn Fn(&LedgerTransition) + Send + Sync + 'static>;
#[derive(Clone)]
pub struct DeviceLedger {
inner: Arc<DeviceLedgerInner>,
}
struct DeviceLedgerInner {
profile: DeviceResourceProfile,
state: Mutex<DeviceLedgerState>,
transition_observers: Mutex<Arc<[LedgerTransitionObserver]>>,
admission_observers: Mutex<Arc<[LedgerAdmissionObserver]>>,
}
const MAX_LEDGER_TRANSITION_OBSERVER_DEPTH: usize = 64;
#[derive(Clone, Copy)]
struct LedgerTransitionObserverStack {
ledgers: [*const DeviceLedgerInner; MAX_LEDGER_TRANSITION_OBSERVER_DEPTH],
len: usize,
}
impl LedgerTransitionObserverStack {
const EMPTY: Self = Self { ledgers: [std::ptr::null(); MAX_LEDGER_TRANSITION_OBSERVER_DEPTH], len: 0 };
fn contains(&self, ledger: *const DeviceLedgerInner) -> bool {
self.ledgers[..self.len].contains(&ledger)
}
}
thread_local! {
static LEDGER_TRANSITION_OBSERVER_STACK: Cell<LedgerTransitionObserverStack> = const { Cell::new(LedgerTransitionObserverStack::EMPTY) };
}
struct LedgerTransitionObserverGuard {
ledger: *const DeviceLedgerInner,
}
impl LedgerTransitionObserverGuard {
fn enter(ledger: &Arc<DeviceLedgerInner>) -> Self {
let ledger = Arc::as_ptr(ledger);
LEDGER_TRANSITION_OBSERVER_STACK.with(|stack| {
let mut active = stack.get();
assert!(
active.len < MAX_LEDGER_TRANSITION_OBSERVER_DEPTH,
"ledger transition observer nesting exceeds its fixed bound"
);
active.ledgers[active.len] = ledger;
active.len += 1;
stack.set(active);
});
Self { ledger }
}
}
impl Drop for LedgerTransitionObserverGuard {
fn drop(&mut self) {
LEDGER_TRANSITION_OBSERVER_STACK.with(|stack| {
let mut active = stack.get();
debug_assert!(active.len > 0);
active.len = active.len.saturating_sub(1);
debug_assert_eq!(active.ledgers[active.len], self.ledger);
active.ledgers[active.len] = std::ptr::null();
stack.set(active);
});
}
}
impl fmt::Debug for DeviceLedgerInner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DeviceLedgerInner")
.field("device_ordinal", &self.profile.device_ordinal)
.field("managed_capacity_bytes", &self.profile.managed_capacity_bytes)
.field("state", &self.state)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
struct DeviceLedgerState {
sequence: u64,
next_grant_generation: u64,
grants: BTreeMap<u64, ActiveGrant>,
frozen_resident_bytes: u64,
health: DeviceAdmissionHealth,
evictable_bytes: u64,
pinned_bytes: u64,
}
#[derive(Debug, Clone, Copy)]
struct ActiveGrant {
admission_request_id: AdmissionRequestId,
grant_bytes: u64,
resident_owner_bytes: u64,
pending_frozen_resident_bytes: Option<u64>,
frozen_resident_bytes: Option<u64>,
}
impl DeviceLedgerState {
fn live_grants_bytes(&self) -> u64 {
self.grants.values().fold(0u64, |sum, grant| {
sum.saturating_add(if grant.frozen_resident_bytes.is_some() { 0 } else { grant.grant_bytes })
})
}
fn resident_owner_bytes(&self) -> u64 {
self.grants.values().fold(0u64, |sum, grant| sum.saturating_add(grant.resident_owner_bytes))
}
}
impl DeviceLedger {
pub(crate) fn new(profile: DeviceResourceProfile) -> Self {
Self {
inner: Arc::new(DeviceLedgerInner {
profile,
state: Mutex::new(DeviceLedgerState {
sequence: 0,
next_grant_generation: 1,
grants: BTreeMap::new(),
frozen_resident_bytes: 0,
health: DeviceAdmissionHealth::Open,
evictable_bytes: 0,
pinned_bytes: 0,
}),
transition_observers: Mutex::new(Arc::from([])),
admission_observers: Mutex::new(Arc::from([])),
}),
}
}
#[cfg(test)]
pub(crate) fn for_test(device_ordinal: usize, managed_capacity_bytes: u64, max_active_attempts: usize) -> Self {
let max_active_attempts = max_active_attempts.max(1);
let source_work = 1usize;
let slots = max_active_attempts.saturating_mul(2).max(max_active_attempts);
let default_query_limit_bytes = DeviceResourceProfile::derive_default_query_limit_bytes(
device_ordinal,
managed_capacity_bytes,
0,
max_active_attempts,
)
.expect("test ledger profile must resolve");
let profile = DeviceResourceProfile::resolve(DeviceResourceEnvelope {
device_ordinal,
device_capabilities: DeviceCapabilities::default(),
configured_limit_bytes: managed_capacity_bytes,
headroom_bytes: 0,
backend_reserve_bytes: 0,
default_query_limit_bytes,
max_active_attempts,
source_work_per_attempt: source_work,
stream_pool_size: slots,
worker_job_capacity: slots,
cache_cap_bytes: 0,
})
.expect("test ledger profile must resolve");
Self::new(profile)
}
#[cfg(test)]
pub(crate) fn admit_with_configured_floor_and_soft_working_set_hint_for_test(
&self,
waiter_id: AdmissionRequestId,
query_cap_bytes: u64,
_unused_soft_working_set_hint_bytes: u64,
) -> Result<Option<MemoryGrant>> {
self.try_admit_for_attempt(waiter_id, AttemptId::mint(), Some(query_cap_bytes))
}
pub fn profile(&self) -> &DeviceResourceProfile {
&self.inner.profile
}
pub fn device_ordinal(&self) -> usize {
self.inner.profile.device_ordinal
}
pub(crate) fn managed_capacity_bytes(&self) -> u64 {
self.inner.profile.managed_capacity_bytes
}
fn in_transition_observer(&self) -> bool {
let ledger = Arc::as_ptr(&self.inner);
LEDGER_TRANSITION_OBSERVER_STACK.with(|stack| stack.get().contains(ledger))
}
fn transition_observer_reentrancy_error(&self, action: &'static str) -> QueryError {
QueryError::new(
QueryErrorCode::Internal,
OPERATION,
"device ledger transition observer synchronously re-entered the same ledger",
)
.with_fact("action", action)
.with_fact("reason", "ledger_observer_reentrancy")
}
#[cold]
fn panic_on_transition_observer_reentrancy(&self, action: &'static str) -> ! {
std::panic::panic_any(self.transition_observer_reentrancy_error(action))
}
fn lock_state(&self, action: &'static str) -> Result<MutexGuard<'_, DeviceLedgerState>> {
if self.in_transition_observer() {
return Err(self.transition_observer_reentrancy_error(action));
}
self.inner.state.lock().map_err(|_error| {
QueryError::new(QueryErrorCode::Internal, OPERATION, "device ledger mutex was poisoned")
.with_fact("action", action)
.with_fact("reason", "ledger_mutex_poisoned")
})
}
fn transition_observers(&self) -> Arc<[LedgerTransitionObserver]> {
Arc::clone(&self.inner.transition_observers.lock().unwrap_or_else(PoisonError::into_inner))
}
fn admission_observers(&self) -> Arc<[LedgerAdmissionObserver]> {
Arc::clone(&self.inner.admission_observers.lock().unwrap_or_else(PoisonError::into_inner))
}
fn notify_admission_observers(&self, transition: &LedgerTransition) {
for observer in self.admission_observers().iter() {
if catch_unwind(AssertUnwindSafe(|| observer(transition))).is_err() {
eprintln!("[query-engine] warning: device ledger admission observer panicked");
}
}
}
fn bump_sequence_and_notify_locked(
&self,
state: &mut DeviceLedgerState,
kind: LedgerTransitionKind,
) -> LedgerTransition {
state.sequence = state.sequence.saturating_add(1);
let transition = LedgerTransition { kind, snapshot: self.snapshot_locked(state) };
for observer in self.transition_observers().iter() {
let _delivery = LedgerTransitionObserverGuard::enter(&self.inner);
if catch_unwind(AssertUnwindSafe(|| observer(&transition))).is_err() {
eprintln!("[query-engine] warning: device ledger transition observer panicked");
}
}
transition
}
pub(crate) fn register_transition_observer(&self, observer: LedgerTransitionObserver) -> Result<()> {
let mut registered = self.inner.transition_observers.lock().map_err(|_error| {
QueryError::new(QueryErrorCode::Internal, OPERATION, "device ledger transition-observer mutex was poisoned")
.with_fact("reason", "transition_observer_mutex_poisoned")
})?;
let mut next = Vec::with_capacity(registered.len().saturating_add(1));
next.extend(registered.iter().cloned());
next.push(observer);
*registered = Arc::from(next);
Ok(())
}
pub(crate) fn register_admission_observer(&self, observer: LedgerAdmissionObserver) -> Result<()> {
let mut registered = self.inner.admission_observers.lock().map_err(|_error| {
QueryError::new(QueryErrorCode::Internal, OPERATION, "device ledger admission-observer mutex was poisoned")
.with_fact("reason", "admission_observer_mutex_poisoned")
})?;
let mut next = Vec::with_capacity(registered.len().saturating_add(1));
next.extend(registered.iter().cloned());
next.push(observer);
*registered = Arc::from(next);
Ok(())
}
pub(crate) fn snapshot(&self) -> Result<LedgerSnapshot> {
let state = self.lock_state("snapshot")?;
Ok(self.snapshot_locked(&state))
}
fn snapshot_locked(&self, state: &DeviceLedgerState) -> LedgerSnapshot {
let profile = &self.inner.profile;
let active_attempts = state.grants.values().filter(|grant| grant.frozen_resident_bytes.is_none()).count();
let committed_auxiliary_slots = active_attempts.saturating_mul(profile.source_work_per_attempt);
LedgerSnapshot {
device_ordinal: profile.device_ordinal,
sequence: state.sequence,
managed_capacity_bytes: profile.managed_capacity_bytes,
cache_cap_bytes: profile.cache_cap_bytes,
query_share_bytes: profile.query_share_bytes(),
default_query_limit_bytes: profile.default_query_limit_bytes,
max_active_attempts: profile.max_active_attempts,
active_attempts,
live_grants_bytes: state.live_grants_bytes(),
frozen_resident_bytes: state.frozen_resident_bytes,
resident_owner_bytes: state.resident_owner_bytes(),
root_streams_available: profile.root_stream_count.saturating_sub(active_attempts),
auxiliary_streams_available: profile.aux_source_work_capacity.saturating_sub(committed_auxiliary_slots),
root_worker_slots_available: profile.root_lane_count.saturating_sub(active_attempts),
auxiliary_worker_slots_available: profile
.aux_source_work_capacity
.saturating_sub(committed_auxiliary_slots),
worker_depth: active_attempts.saturating_add(committed_auxiliary_slots),
cache_evictable_resident_bytes: state.evictable_bytes,
cache_pinned_in_use_bytes: state.pinned_bytes,
health: state.health.clone(),
}
}
pub(crate) fn report(&self) -> Result<DeviceLedgerReport> {
let state = self.lock_state("report")?;
let snapshot = self.snapshot_locked(&state);
let grants = state
.grants
.iter()
.map(|(grant_id, active)| DeviceLedgerGrantRow {
grant_id: *grant_id,
admission_request_id: active.admission_request_id,
grant_bytes: active.grant_bytes,
resident_owner_bytes: active.resident_owner_bytes,
frozen_resident_bytes: active.frozen_resident_bytes,
})
.collect();
Ok(DeviceLedgerReport {
device_ordinal: snapshot.device_ordinal,
sequence: snapshot.sequence,
managed_capacity_bytes: snapshot.managed_capacity_bytes,
default_query_limit_bytes: snapshot.default_query_limit_bytes,
max_active_attempts: snapshot.max_active_attempts,
active_attempts: snapshot.active_attempts,
live_grants_bytes: snapshot.live_grants_bytes,
frozen_resident_bytes: snapshot.frozen_resident_bytes,
resident_owner_bytes: snapshot.resident_owner_bytes,
root_streams_available: snapshot.root_streams_available,
auxiliary_streams_available: snapshot.auxiliary_streams_available,
root_worker_slots_available: snapshot.root_worker_slots_available,
auxiliary_worker_slots_available: snapshot.auxiliary_worker_slots_available,
worker_depth: snapshot.worker_depth,
available_bytes: snapshot
.query_share_bytes
.saturating_sub(snapshot.live_grants_bytes)
.saturating_sub(snapshot.frozen_resident_bytes),
free_bytes: snapshot
.query_share_bytes
.saturating_sub(snapshot.live_grants_bytes)
.saturating_sub(snapshot.frozen_resident_bytes),
evictable_bytes: state.evictable_bytes,
cache_evictable_resident_bytes: snapshot.cache_evictable_resident_bytes,
cache_pinned_in_use_bytes: snapshot.cache_pinned_in_use_bytes,
health: snapshot.health.as_str().to_string(),
grants,
})
}
pub(crate) fn health(&self) -> Result<DeviceAdmissionHealth> {
let state = self.lock_state("health")?;
Ok(state.health.clone())
}
pub(crate) fn close_admission(&self, reason: impl Into<String>) -> Result<u64> {
self.close_admission_with(AttemptFaultIsolation::DeviceLocal, reason.into())
}
pub(crate) fn close_admission_for_unknown_isolation(&self, detail: impl Into<String>) -> Result<u64> {
self.close_admission_with(AttemptFaultIsolation::Unknown, detail.into())
}
fn close_admission_with(&self, isolation: AttemptFaultIsolation, reason: String) -> Result<u64> {
let mut state = self.lock_state("close_admission")?;
let generation = match &state.health {
DeviceAdmissionHealth::Open => 1,
DeviceAdmissionHealth::Closed { generation, .. } => generation.saturating_add(1),
};
state.health = DeviceAdmissionHealth::Closed { reason, isolation, generation };
let transition = self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::AdmissionClosed);
drop(state);
self.notify_admission_observers(&transition);
Ok(generation)
}
pub(crate) fn close_admission_if_open(&self, reason: impl Into<String>) -> Result<bool> {
let mut state = self.lock_state("close_admission_if_open")?;
if !matches!(state.health, DeviceAdmissionHealth::Open) {
return Ok(false);
}
state.health = DeviceAdmissionHealth::Closed {
reason: reason.into(),
isolation: AttemptFaultIsolation::DeviceLocal,
generation: 1,
};
let transition = self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::AdmissionClosed);
drop(state);
self.notify_admission_observers(&transition);
Ok(true)
}
#[cfg(test)]
pub(crate) fn candidate_query_cap_bytes(&self, requested_cap_bytes: Option<u64>) -> Result<Option<u64>> {
let state = self.lock_state("candidate_max_new_grant_bytes")?;
let snapshot = self.snapshot_locked(&state);
Ok(snapshot.admissible_query_cap_bytes(requested_cap_bytes))
}
pub(crate) fn try_admit_for_attempt(
&self,
waiter_id: AdmissionRequestId,
attempt_id: AttemptId,
requested_cap_bytes: Option<u64>,
) -> Result<Option<MemoryGrant>> {
if requested_cap_bytes == Some(0) {
return Err(QueryError::new(QueryErrorCode::InvalidPlan, OPERATION, "query cap must be greater than zero")
.with_fact("reason", "zero_query_cap")
.with_fact("admission_request_id", waiter_id));
}
let mut state = self.lock_state("try_admit")?;
if !matches!(state.health, DeviceAdmissionHealth::Open) {
return Ok(None);
}
let snapshot = self.snapshot_locked(&state);
let Some(query_cap_bytes) = snapshot.admissible_query_cap_bytes(requested_cap_bytes) else {
return Ok(None);
};
self.commit_grant_locked(&mut state, waiter_id, attempt_id, query_cap_bytes).map(Some)
}
#[cfg(test)]
pub(crate) fn try_admit(
&self,
waiter_id: AdmissionRequestId,
requested_cap_bytes: Option<u64>,
) -> Result<Option<MemoryGrant>> {
self.try_admit_for_attempt(waiter_id, AttemptId::mint(), requested_cap_bytes)
}
fn commit_grant_locked(
&self,
state: &mut DeviceLedgerState,
waiter_id: AdmissionRequestId,
attempt_id: AttemptId,
grant_bytes: u64,
) -> Result<MemoryGrant> {
let generation = state.next_grant_generation;
let next_grant_generation = generation.checked_add(1).ok_or_else(|| {
QueryError::new(QueryErrorCode::Internal, OPERATION, "device ledger grant generation exhausted")
.with_fact("reason", "grant_generation_exhausted")
.with_fact("device_ordinal", self.inner.profile.device_ordinal)
.with_fact("attempt_id", attempt_id)
})?;
let identity = GrantIdentity { attempt_id, generation };
state.next_grant_generation = next_grant_generation;
state.grants.insert(
generation,
ActiveGrant {
admission_request_id: waiter_id,
grant_bytes,
resident_owner_bytes: 0,
pending_frozen_resident_bytes: None,
frozen_resident_bytes: None,
},
);
self.bump_sequence_and_notify_locked(state, LedgerTransitionKind::GrantAdmitted);
Ok(MemoryGrant {
inner: Arc::new(MemoryGrantInner {
ledger: Arc::clone(&self.inner),
identity,
admission_request_id: waiter_id,
grant_bytes,
resident_owner_bytes: Default::default(),
next_resident_owner_id: std::sync::atomic::AtomicU64::new(1),
resident_owner_allocations: Mutex::new(BTreeMap::new()),
frozen: Default::default(),
frozen_domain: Mutex::new(None),
}),
resource_epoch: None,
})
}
fn release_grant(&self, grant_id: u64) {
if self.in_transition_observer() {
self.panic_on_transition_observer_reentrancy("release_grant");
}
let mut state = self.inner.state.lock().unwrap_or_else(PoisonError::into_inner);
let Some(active) = state.grants.remove(&grant_id) else {
return;
};
if let Some(resident_bytes) = active.frozen_resident_bytes {
state.frozen_resident_bytes = state.frozen_resident_bytes.saturating_sub(resident_bytes);
}
let transition = self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::GrantReleased);
drop(state);
self.notify_admission_observers(&transition);
}
pub(crate) fn try_reserve_evictable_bytes(&self, bytes: u64) -> Result<bool> {
if bytes == 0 {
return Ok(true);
}
let mut state = self.lock_state("try_reserve_evictable_bytes")?;
if state.evictable_bytes.saturating_add(state.pinned_bytes).saturating_add(bytes)
> self.inner.profile.cache_cap_bytes
{
return Ok(false);
}
state.evictable_bytes = state.evictable_bytes.saturating_add(bytes);
self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::EvictableCharged);
Ok(true)
}
pub(crate) fn pin_cache_residency_charge(&self, bytes: u64) -> Result<()> {
if bytes == 0 {
return Ok(());
}
let mut state = self.lock_state("pin_cache_residency_charge")?;
if state.evictable_bytes < bytes {
return Err(QueryError::new(
QueryErrorCode::Internal,
OPERATION,
"resident-cache pin is missing its evictable ledger charge",
)
.with_fact("reason", "cache_evictable_charge_missing")
.with_fact("requested_bytes", bytes)
.with_fact("evictable_bytes", state.evictable_bytes));
}
let pinned_bytes = state.pinned_bytes.checked_add(bytes).ok_or_else(|| {
QueryError::new(QueryErrorCode::Internal, OPERATION, "resident-cache pinned ledger charge overflowed")
.with_fact("reason", "cache_pinned_charge_overflow")
.with_fact("requested_bytes", bytes)
.with_fact("pinned_bytes", state.pinned_bytes)
})?;
state.evictable_bytes -= bytes;
state.pinned_bytes = pinned_bytes;
self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::CachePinned);
Ok(())
}
pub(crate) fn unpin_cache_residency_charge(&self, bytes: u64) -> Result<()> {
if bytes == 0 {
return Ok(());
}
let mut state = self.lock_state("unpin_cache_residency_charge")?;
if state.pinned_bytes < bytes {
return Err(QueryError::new(
QueryErrorCode::Internal,
OPERATION,
"resident-cache unpin is missing its pinned ledger charge",
)
.with_fact("reason", "cache_pinned_charge_missing")
.with_fact("requested_bytes", bytes)
.with_fact("pinned_bytes", state.pinned_bytes));
}
let evictable_bytes = state.evictable_bytes.checked_add(bytes).ok_or_else(|| {
QueryError::new(QueryErrorCode::Internal, OPERATION, "resident-cache evictable ledger charge overflowed")
.with_fact("reason", "cache_evictable_charge_overflow")
.with_fact("requested_bytes", bytes)
.with_fact("evictable_bytes", state.evictable_bytes)
})?;
state.pinned_bytes -= bytes;
state.evictable_bytes = evictable_bytes;
self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::CacheUnpinned);
Ok(())
}
pub(crate) fn release_cache_residency_charge_after_destroy(&self, bytes: u64) -> Result<()> {
self.release_exact_evictable_cache_charge(bytes, "release_cache_residency_charge_after_destroy")
}
pub(crate) fn release_cache_residency_precharge_excess(&self, bytes: u64) -> Result<()> {
self.release_exact_evictable_cache_charge(bytes, "release_cache_residency_precharge_excess")
}
fn release_exact_evictable_cache_charge(&self, bytes: u64, action: &'static str) -> Result<()> {
if bytes == 0 {
return Ok(());
}
let mut state = self.lock_state(action)?;
if state.evictable_bytes < bytes {
return Err(QueryError::new(
QueryErrorCode::Internal,
OPERATION,
"resident-cache exact release is missing its evictable ledger charge",
)
.with_fact("reason", "cache_exact_charge_missing")
.with_fact("action", action)
.with_fact("requested_bytes", bytes)
.with_fact("evictable_bytes", state.evictable_bytes));
}
state.evictable_bytes -= bytes;
self.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::EvictableReleased);
Ok(())
}
}
#[derive(Clone)]
pub struct MemoryGrant {
inner: Arc<MemoryGrantInner>,
resource_epoch: Option<Arc<QueryServiceResourceEpoch>>,
}
#[derive(Clone)]
pub struct ResidentOwnerLease {
inner: Arc<ResidentOwnerLeaseInner>,
_reader: Option<Arc<ResidentReader>>,
}
struct ResidentOwnerLeaseInner {
grant: MemoryGrant,
bytes: u64,
owner_id: u64,
completion_fences: Mutex<ResidentCompletionFences>,
}
#[derive(Default)]
struct ResidentCompletionFences {
fences: Vec<ResidentCompletionFence>,
record_failure: Option<String>,
}
struct ResidentCompletionFence {
stream_identity: usize,
event: CudaEvent,
}
struct ResidentReader {
owner: Arc<ResidentOwnerLeaseInner>,
stream: CudaStream,
}
impl Drop for ResidentReader {
fn drop(&mut self) {
if let Err(error) = self.owner.record_completion(&self.stream) {
let mut fences = self.owner.completion_fences.lock().unwrap_or_else(PoisonError::into_inner);
fences.record_failure.get_or_insert_with(|| format!("resident reader completion failed: {error}"));
}
}
}
struct MemoryGrantInner {
ledger: Arc<DeviceLedgerInner>,
identity: GrantIdentity,
admission_request_id: AdmissionRequestId,
grant_bytes: u64,
resident_owner_bytes: std::sync::atomic::AtomicU64,
next_resident_owner_id: std::sync::atomic::AtomicU64,
resident_owner_allocations: Mutex<BTreeMap<u64, Arc<[usize]>>>,
frozen: std::sync::atomic::AtomicBool,
frozen_domain: Mutex<Option<Arc<cudf::QueryMemoryDomain>>>,
}
impl Drop for MemoryGrantInner {
fn drop(&mut self) {
let ledger = DeviceLedger { inner: Arc::clone(&self.ledger) };
let frozen_cleanup_failure = match self.frozen_domain.get_mut().unwrap_or_else(PoisonError::into_inner) {
Some(domain) => match domain.usage() {
Ok(usage)
if usage.active_members == 0
&& usage.current_allocations == 0
&& usage.current_bytes == 0
&& usage.current_aligned_bytes == 0 =>
{
None
},
Ok(usage) => Some(format!(
"frozen resident domain did not reach zero at final release (domain_id={}, active_members={}, current_allocations={}, current_bytes={}, current_aligned_bytes={})",
usage.domain_id,
usage.active_members,
usage.current_allocations,
usage.current_bytes,
usage.current_aligned_bytes
)),
Err(error) => Some(format!("frozen resident domain final usage sample failed: {error}")),
},
None => None,
};
if let Some(reason) = frozen_cleanup_failure
&& let Err(error) = ledger.close_admission_if_open(reason)
{
eprintln!("[query-engine] warning: failed to close admission after frozen resident cleanup: {error}");
}
ledger.release_grant(self.identity.generation);
}
}
impl MemoryGrant {
pub(crate) fn with_resource_epoch(&self, resource_epoch: Arc<QueryServiceResourceEpoch>) -> Self {
Self { inner: Arc::clone(&self.inner), resource_epoch: Some(resource_epoch) }
}
fn check_resource_epoch(&self, operation: &'static str) -> Result<()> {
self.resource_epoch.as_ref().map_or(Ok(()), |epoch| epoch.check_open(operation))
}
pub(crate) fn grant_id(&self) -> u64 {
self.inner.identity.generation
}
pub fn identity(&self) -> GrantIdentity {
self.inner.identity
}
pub fn admission_request_id(&self) -> AdmissionRequestId {
self.inner.admission_request_id
}
pub(crate) fn identity_key(&self) -> usize {
Arc::as_ptr(&self.inner) as usize
}
pub fn budget_bytes(&self) -> u64 {
self.inner.grant_bytes
}
#[cfg(test)]
pub(crate) fn freeze_resident(&self, resident_bytes: u64) -> Result<()> {
self.prepare_frozen_resident_charge(resident_bytes, None)?;
self.publish_frozen_resident()
}
#[cfg(test)]
pub(super) fn prepare_frozen_resident_for_test(&self, resident_bytes: u64) -> Result<()> {
self.prepare_frozen_resident_charge(resident_bytes, None)
}
pub(crate) fn freeze_resident_with_domain(
&self,
resident_bytes: u64,
domain: Arc<cudf::QueryMemoryDomain>,
) -> Result<()> {
self.reconcile_resident_owner_bytes(resident_bytes)?;
self.prepare_frozen_resident_charge(resident_bytes, Some(domain))
}
fn reconcile_resident_owner_bytes(&self, measured_bytes: u64) -> Result<()> {
if measured_bytes > self.inner.grant_bytes {
return Err(QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.reconcile_resident_owner_bytes",
"measured resident bytes exceed the immutable query cap",
)
.with_fact("measured_bytes", measured_bytes)
.with_fact("query_cap_bytes", self.inner.grant_bytes));
}
let ledger = DeviceLedger { inner: Arc::clone(&self.inner.ledger) };
let mut state = ledger.lock_state("reconcile_resident_owner_bytes")?;
let Some(active) = state.grants.get_mut(&self.inner.identity.generation) else {
return Err(QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.reconcile_resident_owner_bytes",
"query grant is missing from its device ledger",
)
.with_fact("grant_id", self.grant_id()));
};
if active.resident_owner_bytes == measured_bytes {
return Ok(());
}
active.resident_owner_bytes = measured_bytes;
self.inner.resident_owner_bytes.store(measured_bytes, std::sync::atomic::Ordering::Release);
ledger.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::ResidentOwnerCharged);
Ok(())
}
fn prepare_frozen_resident_charge(
&self,
resident_bytes: u64,
domain: Option<Arc<cudf::QueryMemoryDomain>>,
) -> Result<()> {
if resident_bytes > self.inner.grant_bytes {
return Err(QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.freeze_resident",
"frozen resident charge must fit the query cap",
)
.with_fact("resident_bytes", resident_bytes)
.with_fact("query_cap_bytes", self.inner.grant_bytes));
}
let ledger = DeviceLedger { inner: Arc::clone(&self.inner.ledger) };
let mut state = ledger.lock_state("freeze_resident")?;
let active = state.grants.get(&self.inner.identity.generation).ok_or_else(|| {
QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.freeze_resident",
"query grant is missing from its device ledger",
)
.with_fact("grant_id", self.grant_id())
})?;
let existing = active.frozen_resident_bytes.or(active.pending_frozen_resident_bytes);
if let Some(existing_bytes) = existing {
let frozen_domain = self.inner.frozen_domain.lock().map_err(|_error| {
QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.freeze_resident",
"frozen resident domain owner mutex was poisoned",
)
})?;
let same_domain = match (frozen_domain.as_ref(), domain.as_ref()) {
(Some(existing), Some(domain)) => Arc::ptr_eq(existing, domain),
(None, None) => true,
_ => false,
};
return (existing_bytes == resident_bytes && same_domain).then_some(()).ok_or_else(|| {
QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.freeze_resident",
"query grant was frozen with a different resident charge",
)
.with_fact("grant_id", self.grant_id())
.with_fact("existing_resident_bytes", existing_bytes)
.with_fact("resident_bytes", resident_bytes)
});
}
state
.grants
.get_mut(&self.inner.identity.generation)
.expect("grant existence was checked under the same ledger lock")
.pending_frozen_resident_bytes = Some(resident_bytes);
if let Some(domain) = domain {
*self.inner.frozen_domain.lock().map_err(|_error| {
QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.freeze_resident",
"frozen resident domain owner mutex was poisoned",
)
})? = Some(domain);
}
self.inner.frozen.store(true, std::sync::atomic::Ordering::Release);
Ok(())
}
pub(crate) fn validate_frozen_resident_publish(&self) -> Result<()> {
let ledger = DeviceLedger { inner: Arc::clone(&self.inner.ledger) };
let state = ledger.lock_state("validate_frozen_resident_publish")?;
let active = state.grants.get(&self.inner.identity.generation).ok_or_else(|| {
QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.validate_frozen_resident_publish",
"prepared frozen resident grant is missing from its device ledger",
)
.with_fact("grant_id", self.grant_id())
})?;
if active.frozen_resident_bytes.is_some() {
return Ok(());
}
let resident_bytes = active.pending_frozen_resident_bytes.ok_or_else(|| {
QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.validate_frozen_resident_publish",
"frozen resident transition was not prepared",
)
.with_fact("grant_id", self.grant_id())
})?;
state.frozen_resident_bytes.checked_add(resident_bytes).map(|_| ()).ok_or_else(|| {
QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.validate_frozen_resident_publish",
"frozen resident accounting would overflow",
)
.with_fact("grant_id", self.grant_id())
.with_fact("resident_bytes", resident_bytes)
})
}
pub(crate) fn close_device_admission(&self, reason: &'static str) -> Result<bool> {
DeviceLedger { inner: Arc::clone(&self.inner.ledger) }.close_admission_if_open(reason)
}
pub(crate) fn publish_frozen_resident(&self) -> Result<()> {
let ledger = DeviceLedger { inner: Arc::clone(&self.inner.ledger) };
let mut state = ledger.lock_state("publish_frozen_resident")?;
let active = state.grants.get_mut(&self.inner.identity.generation).ok_or_else(|| {
QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.publish_frozen_resident",
"prepared frozen resident grant is missing from its device ledger",
)
.with_fact("grant_id", self.grant_id())
})?;
if active.frozen_resident_bytes.is_some() {
return Ok(());
}
let resident_bytes = active.pending_frozen_resident_bytes.take().ok_or_else(|| {
QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.publish_frozen_resident",
"frozen resident transition was not prepared",
)
.with_fact("grant_id", self.grant_id())
})?;
state.frozen_resident_bytes = state.frozen_resident_bytes.checked_add(resident_bytes).ok_or_else(|| {
QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.publish_frozen_resident",
"frozen resident accounting overflowed",
)
})?;
state
.grants
.get_mut(&self.inner.identity.generation)
.expect("grant existence was checked under the same ledger lock")
.frozen_resident_bytes = Some(resident_bytes);
let transition = ledger.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::GrantFrozenResident);
drop(state);
ledger.notify_admission_observers(&transition);
Ok(())
}
pub(crate) fn is_frozen_resident(&self) -> bool {
self.inner.frozen.load(std::sync::atomic::Ordering::Acquire)
}
pub fn device_ordinal(&self) -> usize {
self.inner.ledger.profile.device_ordinal
}
pub(crate) fn resident_owner_bytes(&self) -> u64 {
self.inner.resident_owner_bytes.load(std::sync::atomic::Ordering::Acquire)
}
fn add_resident_owner_bytes(&self, bytes: u64) -> Result<()> {
self.check_resource_epoch("query_engine.runtime.gpu.memory_grant.add_resident_owner_bytes")?;
if bytes == 0 {
return Ok(());
}
let ledger = DeviceLedger { inner: Arc::clone(&self.inner.ledger) };
let mut state = ledger.lock_state("add_resident_owner_bytes")?;
let Some(active) = state.grants.get_mut(&self.inner.identity.generation) else {
return Err(QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.add_resident_owner_bytes",
"resident owner was published after its grant left the device ledger",
));
};
if active.frozen_resident_bytes.is_some() {
return Err(QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.add_resident_owner_bytes",
"resident owner was published after the grant was frozen",
));
}
let next_resident_owner_bytes = active.resident_owner_bytes.checked_add(bytes).ok_or_else(|| {
QueryError::new(
QueryErrorCode::InternalInvariantViolation,
"query_engine.runtime.gpu.memory_grant.add_resident_owner_bytes",
"resident owner byte accounting overflowed",
)
})?;
active.resident_owner_bytes = next_resident_owner_bytes;
self.inner.resident_owner_bytes.store(next_resident_owner_bytes, std::sync::atomic::Ordering::Release);
ledger.bump_sequence_and_notify_locked(&mut state, LedgerTransitionKind::ResidentOwnerCharged);
Ok(())
}
fn release_resident_owner_bytes(&self, bytes: u64) {
if bytes == 0 {
return;
}
let ledger = DeviceLedger { inner: Arc::clone(&self.inner.ledger) };
if ledger.in_transition_observer() {
ledger.panic_on_transition_observer_reentrancy("release_resident_owner_bytes");
}
let mut state = ledger.inner.state.lock().unwrap_or_else(PoisonError::into_inner);
self.release_locked(&ledger, &mut state, bytes);
}
fn release_locked(&self, ledger: &DeviceLedger, state: &mut DeviceLedgerState, bytes: u64) {
let Some(active) = state.grants.get_mut(&self.inner.identity.generation) else {
return;
};
let next_resident_owner_bytes = match active.resident_owner_bytes.checked_sub(bytes) {
Some(next) => next,
None if active.frozen_resident_bytes.is_some() || active.pending_frozen_resident_bytes.is_some() => 0,
None => {
eprintln!("[query-engine] warning: resident owner release exceeded its active charge");
return;
},
};
active.resident_owner_bytes = next_resident_owner_bytes;
self.inner.resident_owner_bytes.store(next_resident_owner_bytes, std::sync::atomic::Ordering::Release);
ledger.bump_sequence_and_notify_locked(state, LedgerTransitionKind::ResidentOwnerReleased);
}
pub(crate) fn retain_resident_owner(&self, bytes: u64) -> Result<Option<ResidentOwnerLease>> {
if bytes == 0 {
return Ok(None);
}
self.add_resident_owner_bytes(bytes)?;
Ok(Some(self.new_resident_owner_lease(bytes)?))
}
#[cfg(any(test, feature = "test-hooks"))]
#[doc(hidden)]
pub fn retain_resident_owner_for_test(&self, bytes: u64) -> Result<Option<ResidentOwnerLease>> {
self.retain_resident_owner(bytes)
}
fn new_resident_owner_lease(&self, bytes: u64) -> Result<ResidentOwnerLease> {
let owner_id = self.inner.next_resident_owner_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
self.inner
.resident_owner_allocations
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.insert(owner_id, Arc::from([]));
let resource_epoch = self
.resource_epoch
.as_ref()
.map(|epoch| {
epoch.service_generation_only("query_engine.runtime.gpu.memory_grant.new_resident_owner_lease")
})
.transpose()?;
Ok(ResidentOwnerLease {
inner: Arc::new(ResidentOwnerLeaseInner {
grant: MemoryGrant { inner: Arc::clone(&self.inner), resource_epoch },
bytes,
owner_id,
completion_fences: Mutex::new(ResidentCompletionFences::default()),
}),
_reader: None,
})
}
pub(crate) fn published_resident_owner_count(&self) -> usize {
self.inner.resident_owner_allocations.lock().unwrap_or_else(std::sync::PoisonError::into_inner).len()
}
pub(crate) fn published_resident_allocation_pointers(&self) -> std::collections::BTreeSet<usize> {
self.inner
.resident_owner_allocations
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.values()
.flat_map(|pointers| pointers.iter().copied())
.collect()
}
}
impl ResidentOwnerLease {
pub(crate) fn record_completion(&self, stream: &CudaStream) -> Result<()> {
self.inner.record_completion(stream)
}
pub(crate) fn for_reader(&self, stream: CudaStream) -> Self {
Self {
inner: Arc::clone(&self.inner),
_reader: Some(Arc::new(ResidentReader { owner: Arc::clone(&self.inner), stream })),
}
}
pub(crate) fn set_backing_allocation_pointers(&self, mut pointers: Vec<usize>) {
pointers.retain(|pointer| *pointer != 0);
pointers.sort_unstable();
pointers.dedup();
self.inner
.grant
.inner
.resident_owner_allocations
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.insert(self.inner.owner_id, Arc::from(pointers));
}
pub(crate) fn is_frozen_resident(&self) -> bool {
self.inner.grant.is_frozen_resident()
}
pub(crate) fn bytes(&self) -> u64 {
self.inner.bytes
}
pub(crate) fn belongs_to(&self, grant: &MemoryGrant) -> bool {
self.inner.grant.identity_key() == grant.identity_key()
}
#[cfg(test)]
pub(crate) fn has_resource_epoch(&self) -> bool {
self.inner.grant.resource_epoch.is_some()
}
#[cfg(test)]
pub(crate) fn retains_query_anchor(&self) -> bool {
self.inner.grant.resource_epoch.as_ref().is_some_and(|epoch| epoch.has_query_anchor())
}
pub(crate) fn identity_key(&self) -> usize {
Arc::as_ptr(&self.inner) as usize
}
pub(crate) fn retain_derived_owner(&self, bytes: u64) -> Result<Option<Self>> {
self.inner.grant.retain_resident_owner(bytes)
}
}
impl ResidentOwnerLeaseInner {
fn record_completion(&self, stream: &CudaStream) -> Result<()> {
let operation = "query_engine.runtime.gpu.frozen_resident_lease.completion_fence";
let mut completion_fences = self.completion_fences.lock().map_err(|_error| {
QueryError::new(
QueryErrorCode::InternalInvariantViolation,
operation,
"frozen resident completion fence mutex was poisoned",
)
})?;
let event = stream.record_completion_event().map_err(|error| {
cudf_query_error(operation, "failed to record the resident output's completion event", error)
})?;
let stream_identity = stream.identity();
match completion_fences.fences.iter_mut().find(|fence| fence.stream_identity == stream_identity) {
Some(fence) => fence.event = event,
None => completion_fences.fences.push(ResidentCompletionFence { stream_identity, event }),
}
Ok(())
}
fn wait_for_completions(&mut self) -> Option<String> {
let completion_fences = match self.completion_fences.get_mut() {
Ok(completion_fences) => completion_fences,
Err(_error) => return Some("frozen resident completion fence mutex was poisoned".to_string()),
};
if completion_fences.fences.is_empty() {
return Some("frozen resident owner reached final release without a completion fence".to_string());
}
let mut failure = completion_fences.record_failure.take();
for fence in &completion_fences.fences {
if let Err(error) =
fence.event.synchronize_for_teardown("query_engine.runtime.gpu.frozen_resident_lease.drop")
{
failure.get_or_insert_with(|| format!("frozen resident completion fence failed: {error}"));
}
}
failure
}
}
impl Drop for ResidentOwnerLeaseInner {
fn drop(&mut self) {
let cleanup_failure = if self.grant.is_frozen_resident() { self.wait_for_completions() } else { None };
if let Some(reason) = cleanup_failure {
let ledger = DeviceLedger { inner: Arc::clone(&self.grant.inner.ledger) };
if let Err(error) = ledger.close_admission_if_open(reason) {
eprintln!(
"[query-engine] warning: failed to close admission after resident completion failure: {error}"
);
}
}
self.grant
.inner
.resident_owner_allocations
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.remove(&self.owner_id);
self.grant.release_resident_owner_bytes(self.bytes);
}
}
impl fmt::Debug for MemoryGrant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MemoryGrant")
.field("identity", &self.inner.identity)
.field("admission_request_id", &self.inner.admission_request_id)
.field("grant_bytes", &self.inner.grant_bytes)
.field("resident_owner_bytes", &self.resident_owner_bytes())
.finish_non_exhaustive()
}
}
impl fmt::Debug for DeviceLedger {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DeviceLedger")
.field("device_ordinal", &self.inner.profile.device_ordinal)
.field("managed_capacity_bytes", &self.inner.profile.managed_capacity_bytes)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests;