1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Shared accounting adapter, not a store/view admission or allocator owner.
use crate::lifecycle::stats::{AccountedCounter, Accounting, AllocationComponent};
use crate::lifecycle::{Store, StoreError};
use std::sync::Arc;
pub use crate::lifecycle::stats::{
AllocationInterval, AllocationIntervalError, AllocationIntervalSnapshot,
};
/// Hard aggregate graph ceiling; all participants share the store's allowance.
pub const MAX_GRAPH_RESIDENT_BYTES: u64 = 256 * 1024 * 1024;
/// A non-allocating clone of the existing store's shared accounting authority.
/// Construction does not admit a view or permit work after store close.
#[derive(Clone)]
pub struct GraphResources {
accounting: Arc<Accounting>,
}
impl GraphResources {
/// Reuses a store configured with an aggregate limit at most 256 MiB.
/// No second budget or independent accounting instance is created. The
/// retained-view owner separately enforces admission and close cancellation.
pub fn from_store(store: &Store) -> Result<Self, StoreError> {
Self::from_accounting(&store.accounting)
}
pub(crate) fn from_accounting(accounting: &Arc<Accounting>) -> Result<Self, StoreError> {
let limit = accounting.resident_limit();
if limit > MAX_GRAPH_RESIDENT_BYTES {
return Err(StoreError::BudgetExceeded {
needed: limit,
budget: MAX_GRAPH_RESIDENT_BYTES,
component: "graph aggregate configuration",
});
}
Ok(Self {
accounting: Arc::clone(accounting),
})
}
/// Current engine and application-retained capacities.
pub fn snapshot(&self) -> Result<GraphResourceSnapshot, StoreError> {
let (engine_bytes, engine_peak_bytes, application_bytes, application_peak_bytes) =
self.accounting.graph_resource_bytes()?;
Ok(GraphResourceSnapshot {
engine_bytes,
engine_peak_bytes,
application_bytes,
application_peak_bytes,
})
}
/// Records application-retained capacity without imposing an engine budget.
/// The backing must be freed before this owned reporting guard is dropped.
pub fn reserve_application(
&self,
bytes: usize,
) -> Result<GraphApplicationReservation, StoreError> {
Ok(GraphApplicationReservation {
_reservation: self.accounting.reserve_application(bytes)?,
})
}
/// Nonshipping snapshot of internal store-cumulative work for tests and benchmarks.
#[cfg(any(test, feature = "test-seams", feature = "benchmark-kernels"))]
pub fn work_ledger(&self) -> Result<crate::lifecycle::stats::GraphWorkLedger, StoreError> {
Ok(self.accounting.graph_work())
}
pub(crate) fn record_work(&self, kind: crate::lifecycle::stats::GraphWorkKind, units: u64) {
work_batch::record(&self.accounting, kind, units);
}
pub(crate) fn record_work_delta(&self, delta: crate::lifecycle::stats::GraphWorkLedger) {
work_batch::record_delta(&self.accounting, delta);
}
pub(crate) fn begin_work(&self) -> work_batch::WorkBatch {
work_batch::WorkBatch::new(&self.accounting)
}
/// Current exact shared reservations, including all other store participants.
pub fn reserved_bytes(&self) -> Result<u64, StoreError> {
Ok(self.accounting.audit()?.resident_owned_bytes)
}
/// Monotone resident reservation peak since this store Accounting was created.
/// This includes short-lived overlap and is not periodic process-RSS sampling.
pub fn peak_reserved_bytes(&self) -> Result<u64, StoreError> {
self.accounting.resident_peak_bytes()
}
/// Begins one exact shared-reservation observation on this store owner.
/// Existing live reservations are included. The guard owns no Store or
/// graph admission and a second observation returns a typed busy error.
pub fn begin_allocation_interval(&self) -> Result<AllocationInterval, AllocationIntervalError> {
self.accounting.begin_allocation_interval()
}
/// Reserves anonymous participant capacity before allocating or adopting it.
/// The participant owns the backing and must reconcile actual capacity;
/// reservation success alone proves no allocation or view ownership.
pub fn reserve(&self, bytes: usize) -> Result<GraphReservation, StoreError> {
let mut charge = AccountedCounter::new(&self.accounting, AllocationComponent::Temporary)?;
charge.set(bytes)?;
Ok(GraphReservation { charge })
}
}
/// Owned shared capacity reservation. Drop releases precisely the current charge.
pub struct GraphReservation {
charge: AccountedCounter,
}
impl GraphReservation {
pub(crate) fn release(&mut self, bytes: usize) {
self.charge.release(bytes as u64);
}
pub(crate) fn belongs_to(&self, resources: &GraphResources) -> bool {
self.charge.belongs_to(&resources.accounting)
}
/// Changes the charge atomically against the shared budgets; failed growth
/// preserves its prior value. Backing must be freed before shrinking.
pub fn resize(&mut self, bytes: usize) -> Result<(), StoreError> {
self.charge.set(bytes)
}
/// Exact currently reserved capacity, not a count of initialized elements.
pub const fn bytes(&self) -> u64 {
self.charge.bytes()
}
}
/// Allocation observations exclude mappings, process footprint and caller/model buffers.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GraphResourceSnapshot {
/// Current engine-owned working capacities and controls.
pub engine_bytes: u64,
/// Lifetime engine capacity high-water, including replacement overlap.
pub engine_peak_bytes: u64,
/// Current application-retained allocation bytes, outside engine admission.
pub application_bytes: u64,
/// Lifetime application-retained capacity high-water.
pub application_peak_bytes: u64,
}
pub(crate) mod work_batch;
/// Unbudgeted application-retention reporting, tied to the originating store.
/// Keeps accounting alive across store close without retaining a store/view.
pub struct GraphApplicationReservation {
_reservation: crate::lifecycle::stats::ApplicationReservation,
}