linkrs_query/executor/streaming/
memory_pool.rs1use std::sync::atomic::{AtomicUsize, Ordering};
18use std::sync::Arc;
19
20use linkrs_core::error::QueryError;
21
22use super::chunk::DataChunk;
23
24#[derive(Debug, Clone)]
26pub enum MemoryPoolError {
27 ExceededLimit {
29 requested: usize,
30 available: usize,
31 pool_name: &'static str,
32 },
33 ShutDown(&'static str),
35}
36
37impl std::fmt::Display for MemoryPoolError {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 Self::ExceededLimit {
41 requested,
42 available,
43 pool_name,
44 } => {
45 write!(
46 f,
47 "{}: requested {} bytes, {} bytes available",
48 pool_name, requested, available
49 )
50 }
51 Self::ShutDown(name) => write!(f, "{}: pool is shut down", name),
52 }
53 }
54}
55
56impl std::error::Error for MemoryPoolError {}
57
58impl From<MemoryPoolError> for QueryError {
59 fn from(e: MemoryPoolError) -> Self {
60 QueryError::execution(e.to_string())
61 }
62}
63
64#[derive(Debug)]
68struct PoolInner {
69 max_bytes: usize,
71 used: AtomicUsize,
73 shut_down: std::sync::atomic::AtomicBool,
75}
76
77impl PoolInner {
78 fn new(max_bytes: usize) -> Self {
79 Self {
80 max_bytes,
81 used: AtomicUsize::new(0),
82 shut_down: std::sync::atomic::AtomicBool::new(false),
83 }
84 }
85
86 fn try_reserve(&self, bytes: usize) -> Result<(), MemoryPoolError> {
87 if self.shut_down.load(Ordering::Relaxed) {
88 return Err(MemoryPoolError::ShutDown("pool"));
89 }
90 if self.max_bytes == 0 {
91 self.used.fetch_add(bytes, Ordering::Relaxed);
93 return Ok(());
94 }
95 loop {
96 let current = self.used.load(Ordering::Relaxed);
97 let new_total =
98 current
99 .checked_add(bytes)
100 .ok_or_else(|| MemoryPoolError::ExceededLimit {
101 requested: bytes,
102 available: self.max_bytes.saturating_sub(current),
103 pool_name: "pool (overflow)",
104 })?;
105 if new_total > self.max_bytes {
106 return Err(MemoryPoolError::ExceededLimit {
107 requested: bytes,
108 available: self.max_bytes.saturating_sub(current),
109 pool_name: "pool",
110 });
111 }
112 if self
113 .used
114 .compare_exchange_weak(current, new_total, Ordering::Relaxed, Ordering::Relaxed)
115 .is_ok()
116 {
117 return Ok(());
118 }
119 }
120 }
121
122 fn release(&self, bytes: usize) {
123 self.used.fetch_sub(bytes, Ordering::Relaxed);
124 }
125
126 fn current(&self) -> usize {
127 self.used.load(Ordering::Relaxed)
128 }
129
130 fn shut_down(&self) {
131 self.shut_down.store(true, Ordering::Relaxed);
132 }
133}
134
135#[derive(Debug)]
139pub struct MemoryPoolReservation {
140 inner: Option<Arc<PoolInner>>,
141 bytes: usize,
142}
143
144impl MemoryPoolReservation {
145 fn new(inner: Arc<PoolInner>, bytes: usize) -> Self {
146 Self {
147 inner: Some(inner),
148 bytes,
149 }
150 }
151
152 pub fn forget(mut self) {
154 self.bytes = 0;
155 self.inner = None;
156 }
157
158 pub fn bytes(&self) -> usize {
160 self.bytes
161 }
162}
163
164impl Drop for MemoryPoolReservation {
165 fn drop(&mut self) {
166 if let Some(ref inner) = self.inner {
167 if self.bytes > 0 {
168 inner.release(self.bytes);
169 }
170 }
171 }
172}
173
174impl Clone for MemoryPoolReservation {
175 fn clone(&self) -> Self {
176 if let Some(ref inner) = self.inner {
186 Self {
190 inner: Some(Arc::clone(inner)),
191 bytes: self.bytes,
192 }
193 } else {
194 Self {
195 inner: None,
196 bytes: 0,
197 }
198 }
199 }
200}
201
202#[derive(Debug, Clone)]
204pub struct PoolHandle {
205 inner: Arc<PoolInner>,
206}
207
208impl PoolHandle {
209 fn new(max_bytes: usize) -> Self {
210 Self {
211 inner: Arc::new(PoolInner::new(max_bytes)),
212 }
213 }
214
215 pub fn reserve(&self, bytes: usize) -> Result<MemoryPoolReservation, MemoryPoolError> {
217 self.inner.try_reserve(bytes)?;
218 Ok(MemoryPoolReservation::new(Arc::clone(&self.inner), bytes))
219 }
220
221 pub fn current(&self) -> usize {
223 self.inner.current()
224 }
225
226 pub fn max(&self) -> usize {
228 self.inner.max_bytes
229 }
230
231 pub fn shut_down(&self) {
233 self.inner.shut_down();
234 }
235}
236
237#[derive(Debug, Clone)]
245pub struct DatabaseMemoryPool {
246 handle: PoolHandle,
247 max_query_bytes: usize,
250}
251
252impl DatabaseMemoryPool {
253 pub fn new(max_bytes: usize, max_query_bytes: usize) -> Self {
258 Self {
259 handle: PoolHandle::new(max_bytes),
260 max_query_bytes,
261 }
262 }
263
264 pub fn default_pool() -> Self {
266 Self::new(8 * 1024 * 1024 * 1024, 512 * 1024 * 1024)
267 }
268
269 pub fn unlimited() -> Self {
271 Self::new(0, usize::MAX)
272 }
273
274 pub fn new_query_pool(&self, requested_bytes: usize) -> Result<QueryPool, MemoryPoolError> {
280 let effective = requested_bytes.min(self.max_query_bytes);
281 let reservation = self.handle.reserve(effective)?;
282 Ok(QueryPool {
283 handle: PoolHandle::new(effective),
284 _reservation: reservation,
285 })
286 }
287
288 pub fn current(&self) -> usize {
290 self.handle.current()
291 }
292
293 pub fn max(&self) -> usize {
295 self.handle.max()
296 }
297
298 pub fn shut_down(&self) {
300 self.handle.shut_down();
301 }
302
303 pub fn handle(&self) -> PoolHandle {
305 self.handle.clone()
306 }
307}
308
309#[derive(Debug, Clone)]
316pub struct QueryPool {
317 handle: PoolHandle,
318 _reservation: MemoryPoolReservation,
319}
320
321impl QueryPool {
322 pub fn new_fragment_pool(
324 &self,
325 requested_bytes: usize,
326 ) -> Result<FragmentPool, MemoryPoolError> {
327 let reservation = self.handle.reserve(requested_bytes)?;
328 Ok(FragmentPool {
329 handle: PoolHandle::new(requested_bytes),
330 _reservation: reservation,
331 })
332 }
333
334 pub fn reserve(&self, bytes: usize) -> Result<MemoryPoolReservation, MemoryPoolError> {
336 self.handle.reserve(bytes)
337 }
338
339 pub fn current(&self) -> usize {
341 self.handle.current()
342 }
343
344 pub fn max(&self) -> usize {
346 self.handle.max()
347 }
348}
349
350#[derive(Debug, Clone)]
354pub struct FragmentPool {
355 handle: PoolHandle,
356 _reservation: MemoryPoolReservation,
357}
358
359impl FragmentPool {
360 pub fn new_operator_pool(
362 &self,
363 requested_bytes: usize,
364 ) -> Result<OperatorPool, MemoryPoolError> {
365 let reservation = self.handle.reserve(requested_bytes)?;
366 Ok(OperatorPool {
367 handle: PoolHandle::new(requested_bytes),
368 _reservation: reservation,
369 })
370 }
371
372 pub fn reserve(&self, bytes: usize) -> Result<MemoryPoolReservation, MemoryPoolError> {
374 self.handle.reserve(bytes)
375 }
376
377 pub fn current(&self) -> usize {
379 self.handle.current()
380 }
381}
382
383#[derive(Debug, Clone)]
387pub struct OperatorPool {
388 handle: PoolHandle,
389 _reservation: MemoryPoolReservation,
390}
391
392impl OperatorPool {
393 pub fn new_task_pool(&self, requested_bytes: usize) -> Result<TaskPool, MemoryPoolError> {
395 let reservation = self.handle.reserve(requested_bytes)?;
396 Ok(TaskPool {
397 handle: PoolHandle::new(requested_bytes),
398 _reservation: reservation,
399 })
400 }
401
402 pub fn reserve(&self, bytes: usize) -> Result<MemoryPoolReservation, MemoryPoolError> {
404 self.handle.reserve(bytes)
405 }
406
407 pub fn current(&self) -> usize {
409 self.handle.current()
410 }
411}
412
413#[derive(Debug, Clone)]
421pub struct TaskPool {
422 handle: PoolHandle,
423 _reservation: MemoryPoolReservation,
424}
425
426impl TaskPool {
427 pub fn reserve(&self, bytes: usize) -> Result<MemoryPoolReservation, MemoryPoolError> {
429 self.handle.reserve(bytes)
430 }
431
432 pub fn current(&self) -> usize {
434 self.handle.current()
435 }
436}
437
438#[derive(Debug)]
444pub struct PooledChunk {
445 pub chunk: DataChunk,
446 pub reservation: MemoryPoolReservation,
447}
448
449impl PooledChunk {
450 pub fn new(chunk: DataChunk, reservation: MemoryPoolReservation) -> Self {
451 Self { chunk, reservation }
452 }
453
454 pub fn into_parts(self) -> (DataChunk, MemoryPoolReservation) {
456 (self.chunk, self.reservation)
457 }
458}
459
460impl PoolHandle {
461 pub fn copy_chunk(&self, chunk: &DataChunk) -> Result<PooledChunk, QueryError> {
466 let estimated = chunk.estimated_bytes();
470 let reservation = self
471 .reserve(estimated)
472 .map_err(|e| QueryError::execution(e.to_string()))?;
473 let copied = DataChunk {
474 rows: chunk.rows.clone(),
475 typed_columns: None,
476 columnar_build_deferred: false,
477 selection: chunk.selection.clone(),
478 multiplicity: chunk.multiplicity,
479 schema: std::sync::Arc::clone(&chunk.schema),
480 layout: std::sync::Arc::clone(&chunk.layout),
481 memory_reservation: None,
482 columnar_stats: chunk.columnar_stats.clone(),
483 };
484 Ok(PooledChunk {
485 chunk: copied,
486 reservation,
487 })
488 }
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 #[test]
496 fn test_database_pool_admission() {
497 let db = DatabaseMemoryPool::new(1024, 400);
498 let _qp = db
499 .new_query_pool(500)
500 .expect("first query admitted (capped to 400)");
501 let _qp2 = db
504 .new_query_pool(700)
505 .expect("second query admitted (capped to 400, 800 <= 1024)");
506 let result = db.new_query_pool(400);
508 assert!(
509 result.is_err(),
510 "third query should exceed global limit (1200 > 1024)"
511 );
512 }
513
514 #[test]
515 fn test_query_pool_reserve_release() {
516 let db = DatabaseMemoryPool::unlimited();
517 let qp = db.new_query_pool(1024).expect("query pool");
518 let reservation = qp.reserve(128).expect("reserve");
519 assert_eq!(qp.current(), 128);
520 drop(reservation);
521 assert_eq!(qp.current(), 0);
522 }
523
524 #[test]
525 fn test_hierarchical_reservation() {
526 let db = DatabaseMemoryPool::new(4096, 2048);
527 let qp = db.new_query_pool(2048).expect("query");
528 let fp = qp.new_fragment_pool(1024).expect("fragment");
529 let op = fp.new_operator_pool(512).expect("operator");
530 let _tp = op.new_task_pool(256).expect("task");
531
532 assert_eq!(op.current(), 256);
534 assert_eq!(fp.current(), 512);
536 assert_eq!(qp.current(), 1024);
538 assert!(db.current() >= 2048);
540 }
541
542 #[test]
543 fn test_unlimited_pool() {
544 let db = DatabaseMemoryPool::unlimited();
545 let qp = db.new_query_pool(usize::MAX).expect("unlimited query");
546 let r = qp.reserve(1_000_000).expect("large reserve");
547 assert!(qp.current() >= 1_000_000);
548 drop(r);
549 }
550
551 #[test]
552 fn test_shut_down_rejects() {
553 let db = DatabaseMemoryPool::new(1024, 512);
554 db.shut_down();
555 let result = db.new_query_pool(64);
556 assert!(result.is_err());
557 match result {
558 Err(MemoryPoolError::ShutDown(_)) => {} _ => panic!("expected ShutDown error"),
560 }
561 }
562
563 #[test]
564 fn test_reservation_forget() {
565 let db = DatabaseMemoryPool::unlimited();
566 let qp = db.new_query_pool(1024).expect("query");
567 let r = qp.reserve(256).expect("reserve");
568 r.forget();
569 assert!(qp.current() >= 256);
571 }
572}