linkrs_query/executor/base/
memory_budget.rs1use linkrs_core::error::QueryError;
7use linkrs_core::Value;
8use std::sync::Arc;
9
10static BUDGET_COUNTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
11
12#[derive(Clone)]
18pub struct MemoryBudget {
19 pub max_bytes: usize,
21 allocated: Arc<std::sync::atomic::AtomicUsize>,
23 pub(crate) id: usize,
25}
26
27impl std::fmt::Debug for MemoryBudget {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.debug_struct("MemoryBudget")
30 .field("max_bytes", &self.max_bytes)
31 .field(
32 "allocated",
33 &self.allocated.load(std::sync::atomic::Ordering::Relaxed),
34 )
35 .field("id", &self.id)
36 .finish()
37 }
38}
39
40impl MemoryBudget {
41 pub const DEFAULT_MAX: usize = 512 * 1024 * 1024;
43
44 pub fn new(max_bytes: usize) -> Self {
46 Self {
47 max_bytes,
48 allocated: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
49 id: BUDGET_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
50 }
51 }
52
53 pub fn default_budget() -> Self {
55 Self::new(Self::DEFAULT_MAX)
56 }
57
58 pub fn try_reserve(&self, bytes: usize) -> Result<bool, QueryError> {
67 let mut prev = self.allocated.load(std::sync::atomic::Ordering::Relaxed);
68 loop {
69 let total = prev.checked_add(bytes).ok_or_else(|| {
70 QueryError::execution(format!(
71 "Memory budget overflow: request {} bytes overflows usize",
72 bytes,
73 ))
74 })?;
75 if total > self.max_bytes {
76 return Err(QueryError::execution(format!(
77 "Memory budget exceeded: request {} bytes, total {} > budget {} bytes",
78 bytes, total, self.max_bytes,
79 )));
80 }
81 match self.allocated.compare_exchange_weak(
82 prev,
83 total,
84 std::sync::atomic::Ordering::Relaxed,
85 std::sync::atomic::Ordering::Relaxed,
86 ) {
87 Ok(_) => return Ok(true),
88 Err(current) => prev = current,
89 }
90 }
91 }
92
93 pub fn release(&self, bytes: usize) {
95 self.allocated
96 .fetch_sub(bytes, std::sync::atomic::Ordering::Relaxed);
97 }
98
99 pub fn current(&self) -> usize {
101 self.allocated.load(std::sync::atomic::Ordering::Relaxed)
102 }
103
104 pub fn estimate_row_memory(row: &[Value]) -> usize {
106 row.iter().map(|v| v.estimated_size()).sum()
107 }
108
109 pub fn estimate_rows_memory(rows: &[Vec<Value>]) -> usize {
111 rows.iter().map(|row| Self::estimate_row_memory(row)).sum()
112 }
113}
114
115#[derive(Debug, Clone)]
121pub struct MemoryTracker {
122 budget: MemoryBudget,
123 peak_bytes: usize,
124 current_bytes: usize,
125}
126
127impl MemoryTracker {
128 pub fn new(budget: MemoryBudget) -> Self {
130 Self {
131 budget,
132 peak_bytes: 0,
133 current_bytes: 0,
134 }
135 }
136
137 pub fn try_reserve(&mut self, bytes: usize) -> Result<(), QueryError> {
142 self.budget.try_reserve(bytes)?;
143 self.current_bytes += bytes;
144 self.peak_bytes = self.peak_bytes.max(self.current_bytes);
145 Ok(())
146 }
147
148 pub fn release(&mut self, bytes: usize) {
150 self.budget.release(bytes);
151 self.current_bytes = self.current_bytes.saturating_sub(bytes);
152 }
153
154 pub fn peak(&self) -> usize {
156 self.peak_bytes
157 }
158
159 pub fn current(&self) -> usize {
161 self.current_bytes
162 }
163
164 pub fn try_reserve_row(&mut self, row: &[Value]) -> Result<(), QueryError> {
166 let mem = MemoryBudget::estimate_row_memory(row);
167 self.try_reserve(mem)
168 }
169
170 pub fn try_reserve_rows(&mut self, rows: &[Vec<Value>]) -> Result<(), QueryError> {
172 let mem = MemoryBudget::estimate_rows_memory(rows);
173 self.try_reserve(mem)
174 }
175
176 pub fn reset(&mut self) {
180 self.budget.release(self.current_bytes);
181 self.current_bytes = 0;
182 }
184}
185
186#[derive(Debug)]
191pub struct MemoryReservation {
192 budget: MemoryBudget,
193 bytes: usize,
194}
195
196#[derive(Debug)]
202pub struct MemoryTrackerReservation<'a> {
203 tracker: &'a mut MemoryTracker,
204 bytes: usize,
205}
206
207impl MemoryReservation {
208 fn new(budget: MemoryBudget, bytes: usize) -> Self {
209 Self { budget, bytes }
210 }
211
212 pub fn forget(mut self) {
217 self.bytes = 0;
218 }
219
220 pub fn bytes(&self) -> usize {
222 self.bytes
223 }
224}
225
226impl Drop for MemoryReservation {
227 fn drop(&mut self) {
228 if self.bytes > 0 {
229 self.budget.release(self.bytes);
230 }
231 }
232}
233
234impl Drop for MemoryTrackerReservation<'_> {
235 fn drop(&mut self) {
236 if self.bytes > 0 {
237 self.tracker.release(self.bytes);
238 }
239 }
240}
241
242impl MemoryBudget {
243 pub fn reserve(&self, bytes: usize) -> Result<MemoryReservation, QueryError> {
248 self.try_reserve(bytes)?;
249 Ok(MemoryReservation::new(self.clone(), bytes))
250 }
251}
252
253impl MemoryTracker {
254 pub fn reserve_guarded(
258 &mut self,
259 bytes: usize,
260 ) -> Result<MemoryTrackerReservation<'_>, QueryError> {
261 self.try_reserve(bytes)?;
262 Ok(MemoryTrackerReservation {
263 tracker: self,
264 bytes,
265 })
266 }
267}
268
269pub trait Spillable {
274 fn spill_to_disk(&mut self) -> Result<(), QueryError>;
276
277 fn spilled_size(&self) -> u64;
279
280 fn spill_count(&self) -> u64 {
282 0
283 }
284
285 fn spilled_rows(&self) -> u64 {
287 0
288 }
289
290 fn has_spilled(&self) -> bool {
292 self.spilled_size() > 0
293 }
294
295 fn try_spill(&mut self) -> Result<bool, QueryError> {
297 if !self.has_spilled() {
298 self.spill_to_disk()?;
299 }
300 Ok(self.has_spilled())
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 #[test]
309 fn tracked_reservation_releases_budget_and_tracker() {
310 let budget = MemoryBudget::new(64);
311 let mut tracker = MemoryTracker::new(budget.clone());
312
313 {
314 let reservation = tracker.reserve_guarded(32).expect("reserve memory");
315 assert_eq!(reservation.bytes, 32);
316 }
317
318 assert_eq!(budget.current(), 0);
319 assert_eq!(tracker.current(), 0);
320 assert_eq!(tracker.peak(), 32);
321 }
322}