1use super::{
2 InstallMemoryPoolsError, ManagedMemoryBinding, ManagedMemoryHandle, ManagedMemoryId,
3 MemoryAllocationMode, MemoryConfiguration, MemoryReport, MemoryUsage, PERSISTENT_POOL_POS,
4 PoolType,
5 memory_pool::{
6 DirectPool, ExclusiveMemoryPool, MemoryPool, PageMapping, PersistentPool, SlicedPool,
7 },
8};
9use crate::{
10 config::{
11 CubeClRuntimeConfig, RuntimeConfig,
12 memory::{MemoryLogLevel, PersistentMemory},
13 },
14 logging::ServerLogger,
15 memory_management::{BytesFormat, ErrorGraph, FailureId, memory_pool::Slice},
16 server::IoError,
17 storage::{ComputeStorage, StorageHandle},
18};
19
20use alloc::format;
21use alloc::string::{String, ToString};
22use alloc::vec::Vec;
23use core::ops::Range;
24use cubecl_environment::backtrace::BackTrace;
25use cubecl_environment::collections::HashSet;
26use cubecl_environment::sync::Arc;
27use cubecl_ir::MemoryDeviceProperties;
28
29#[allow(clippy::large_enum_variant)]
32enum DynamicPool {
33 Sliced(SlicedPool),
34 Exclusive(ExclusiveMemoryPool),
35 Direct(DirectPool),
36}
37
38impl MemoryPool for DynamicPool {
39 fn accept(&self, size: u64) -> bool {
40 match self {
41 DynamicPool::Sliced(pool) => pool.accept(size),
42 DynamicPool::Exclusive(pool) => pool.accept(size),
43 DynamicPool::Direct(pool) => pool.accept(size),
44 }
45 }
46
47 fn find(&self, binding: &ManagedMemoryBinding) -> Result<&Slice, IoError> {
48 match self {
49 DynamicPool::Sliced(m) => m.find(binding),
50 DynamicPool::Exclusive(m) => m.find(binding),
51 DynamicPool::Direct(m) => m.find(binding),
52 }
53 }
54
55 fn find_mut(&mut self, binding: &ManagedMemoryBinding) -> Result<&mut Slice, IoError> {
56 match self {
57 DynamicPool::Sliced(m) => m.find_mut(binding),
58 DynamicPool::Exclusive(m) => m.find_mut(binding),
59 DynamicPool::Direct(m) => m.find_mut(binding),
60 }
61 }
62
63 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
64 fn try_reserve(&mut self, size: u64, failures: &mut ErrorGraph) -> Option<ManagedMemoryHandle> {
65 match self {
66 DynamicPool::Sliced(m) => m.try_reserve(size, failures),
67 DynamicPool::Exclusive(m) => m.try_reserve(size, failures),
68 DynamicPool::Direct(m) => m.try_reserve(size, failures),
69 }
70 }
71
72 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
73 fn alloc<Storage: ComputeStorage>(
74 &mut self,
75 storage: &mut Storage,
76 size: u64,
77 mapping: PageMapping,
78 failures: &mut ErrorGraph,
79 ) -> Result<ManagedMemoryHandle, IoError> {
80 match self {
81 DynamicPool::Sliced(m) => m.alloc(storage, size, mapping, failures),
82 DynamicPool::Exclusive(m) => m.alloc(storage, size, mapping, failures),
83 DynamicPool::Direct(m) => m.alloc(storage, size, mapping, failures),
84 }
85 }
86
87 fn materialize<Storage: ComputeStorage>(
88 &mut self,
89 storage: &mut Storage,
90 binding: &ManagedMemoryBinding,
91 ) -> Result<(), IoError> {
92 match self {
93 DynamicPool::Sliced(m) => m.materialize(storage, binding),
94 DynamicPool::Exclusive(m) => m.materialize(storage, binding),
95 DynamicPool::Direct(m) => m.materialize(storage, binding),
96 }
97 }
98
99 fn get_memory_usage(&self) -> MemoryUsage {
100 match self {
101 DynamicPool::Sliced(m) => m.get_memory_usage(),
102 DynamicPool::Exclusive(m) => m.get_memory_usage(),
103 DynamicPool::Direct(m) => m.get_memory_usage(),
104 }
105 }
106
107 fn cleanup<Storage: ComputeStorage>(
108 &mut self,
109 storage: &mut Storage,
110 alloc_nr: u64,
111 explicit: bool,
112 failures: &mut ErrorGraph,
113 ) {
114 match self {
115 DynamicPool::Sliced(m) => m.cleanup(storage, alloc_nr, explicit, failures),
116 DynamicPool::Exclusive(m) => m.cleanup(storage, alloc_nr, explicit, failures),
117 DynamicPool::Direct(m) => m.cleanup(storage, alloc_nr, explicit, failures),
118 };
119 storage.flush();
120 }
121
122 fn bind(
123 &mut self,
124 reserved: ManagedMemoryHandle,
125 assigned: ManagedMemoryHandle,
126 cursor: u64,
127 failures: &mut ErrorGraph,
128 ) -> Result<(), IoError> {
129 match self {
130 DynamicPool::Sliced(m) => m.bind(reserved, assigned, cursor, failures),
131 DynamicPool::Exclusive(m) => m.bind(reserved, assigned, cursor, failures),
132 DynamicPool::Direct(m) => m.bind(reserved, assigned, cursor, failures),
133 }
134 }
135}
136
137impl core::fmt::Display for DynamicPool {
138 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
139 match self {
140 DynamicPool::Sliced(pool) => write!(f, "{pool}"),
141 DynamicPool::Exclusive(pool) => write!(f, "{pool}"),
142 DynamicPool::Direct(pool) => write!(f, "{pool}"),
143 }
144 }
145}
146
147impl DynamicPool {
148 fn report(&self) -> super::MemoryPoolReport {
149 match self {
150 DynamicPool::Sliced(m) => m.report(),
151 DynamicPool::Exclusive(m) => m.report(),
152 DynamicPool::Direct(m) => m.report(),
153 }
154 }
155}
156
157pub struct MemoryManagement<Storage> {
159 name: String,
160 persistent: PersistentPool,
161 pools: Vec<DynamicPool>,
162 capacity_warned: HashSet<usize>,
166 storage: Storage,
167 alloc_reserve_count: u64,
168 mode: MemoryAllocationMode,
169 persistent_windows: u64,
172 config: PersistentMemory,
173 logger: Arc<ServerLogger>,
174 capture: Option<CaptureState>,
176}
177
178struct CaptureState {
183 restore_mode: MemoryAllocationMode,
187 touched: HashSet<ManagedMemoryId>,
193 priming: bool,
196 primed: Vec<ManagedMemoryHandle>,
207}
208
209#[derive(Debug)]
211pub struct MemoryManagementOptions {
212 name: String,
214 memory: MemoryAllocationOption,
216}
217
218impl MemoryManagementOptions {
219 pub fn new<S: Into<String>>(name: S) -> Self {
221 Self {
222 name: name.into(),
223 memory: MemoryAllocationOption::FromConfig,
224 }
225 }
226
227 pub fn mode(mut self, mode: MemoryAllocationMode) -> Self {
229 self.memory = MemoryAllocationOption::Provided(mode);
230 self
231 }
232}
233
234#[derive(Default, Debug)]
235enum MemoryAllocationOption {
237 #[default]
238 FromConfig,
240 Provided(MemoryAllocationMode),
242}
243
244impl<Storage: ComputeStorage> MemoryManagement<Storage> {
245 pub fn from_configuration(
247 storage: Storage,
248 properties: &MemoryDeviceProperties,
249 config: MemoryConfiguration,
250 logger: Arc<ServerLogger>,
251 options: MemoryManagementOptions,
252 ) -> Self {
253 let pools = build_pools(properties, config, &logger, &options.name);
254
255 let config = CubeClRuntimeConfig::get().memory.persistent_memory.clone();
256
257 let mode = match options.memory {
258 MemoryAllocationOption::Provided(mode) => mode,
259 MemoryAllocationOption::FromConfig => match config {
260 PersistentMemory::Enabled | PersistentMemory::SizeMatch => {
261 MemoryAllocationMode::Auto
262 }
263 PersistentMemory::Disabled => MemoryAllocationMode::Auto,
264 PersistentMemory::Enforced => MemoryAllocationMode::Persistent,
265 },
266 };
267
268 Self {
269 name: options.name,
270 persistent: PersistentPool::new(
271 properties.max_page_size,
272 properties.alignment,
273 PERSISTENT_POOL_POS,
274 ),
275 pools,
276 capacity_warned: HashSet::new(),
277 storage,
278 alloc_reserve_count: 0,
279 mode,
280 persistent_windows: 0,
281 config,
282 logger,
283 capture: None,
284 }
285 }
286
287 pub fn install_pools(
311 &mut self,
312 config: MemoryConfiguration,
313 properties: &MemoryDeviceProperties,
314 failures: &mut ErrorGraph,
315 ) -> Result<(), InstallMemoryPoolsError> {
316 self.cleanup(true, failures);
317
318 let dynamic_in_use: u64 = self
321 .pools
322 .iter()
323 .map(|pool| pool.get_memory_usage().bytes_in_use)
324 .sum();
325 if dynamic_in_use > 0 {
326 return Err(InstallMemoryPoolsError::PoolsInUse {
327 bytes_in_use: dynamic_in_use,
328 });
329 }
330
331 self.pools = build_pools(properties, config, &self.logger, &self.name);
332 self.capacity_warned.clear();
335 Ok(())
336 }
337
338 pub fn capture_begin(&mut self) {
351 if self.capture.is_none() {
352 self.capture = Some(CaptureState {
353 restore_mode: self.mode,
354 touched: HashSet::new(),
355 priming: true,
356 primed: Vec::new(),
357 });
358 }
359 self.mode = MemoryAllocationMode::Persistent;
360 }
361
362 pub fn capture_priming_end(&mut self) {
369 if let Some(capture) = &mut self.capture {
370 capture.priming = false;
371 capture.primed.clear();
374 }
375 }
376
377 pub fn capture_end(&mut self) -> Vec<ManagedMemoryHandle> {
386 match self.capture.take() {
387 Some(capture) => {
388 self.mode = capture.restore_mode;
389 self.persistent.retain_touched(&capture.touched)
390 }
391 None => Vec::new(),
392 }
393 }
394
395 pub fn mode(&mut self, mode: MemoryAllocationMode) {
407 let mode = match self.config {
409 PersistentMemory::Enabled | PersistentMemory::SizeMatch => mode,
410 PersistentMemory::Disabled | PersistentMemory::Enforced => return,
411 };
412
413 match mode {
414 MemoryAllocationMode::Persistent => self.persistent_windows += 1,
415 MemoryAllocationMode::Auto => {
416 self.persistent_windows = self.persistent_windows.saturating_sub(1)
417 }
418 }
419 let mode = match self.persistent_windows > 0 {
420 true => MemoryAllocationMode::Persistent,
421 false => MemoryAllocationMode::Auto,
422 };
423
424 self.logger.log_memory(
425 |level| !matches!(level, MemoryLogLevel::Disabled),
426 || {
427 format!(
428 "[{}] Setting memory allocation mode: from {:?} => {mode:?}",
429 self.name, self.mode
430 )
431 },
432 );
433
434 match &mut self.capture {
438 Some(capture) => capture.restore_mode = mode,
439 None => self.mode = mode,
440 }
441 }
442
443 pub fn cleanup(&mut self, explicit: bool, failures: &mut ErrorGraph) {
445 self.logger.log_memory(
446 |level| !matches!(level, MemoryLogLevel::Disabled) && explicit,
447 || "Manual memory cleanup ...".to_string(),
448 );
449
450 if self.capture.is_some() {
457 return;
458 }
459
460 self.persistent.cleanup(
461 &mut self.storage,
462 self.alloc_reserve_count,
463 explicit,
464 failures,
465 );
466
467 for pool in self.pools.iter_mut() {
468 pool.cleanup(
469 &mut self.storage,
470 self.alloc_reserve_count,
471 explicit,
472 failures,
473 );
474 }
475
476 if explicit {
480 self.storage.flush();
481 }
482 }
483
484 pub fn get_cursor(&self, binding: ManagedMemoryBinding) -> Result<u64, IoError> {
486 let slice = self.find(&binding)?;
487 Ok(slice.cursor)
488 }
489
490 fn find(&self, binding: &ManagedMemoryBinding) -> Result<&Slice, IoError> {
492 let id = binding.descriptor();
493
494 if !id.is_allocated() {
495 return Err(IoError::NotFound {
496 backtrace: BackTrace::capture(),
497 reason: "Memory location was never initialized".into(),
498 });
499 }
500
501 let slice = if id.location().pool == PERSISTENT_POOL_POS {
502 self.persistent.find(binding)?
503 } else {
504 let pool =
505 self.pools
506 .get(id.location().pool as usize)
507 .ok_or_else(|| IoError::NotFound {
508 backtrace: BackTrace::capture(),
509 reason: format!("Pool {} doesn't exist", id.location().pool).into(),
510 })?;
511
512 pool.find(binding)?
513 };
514
515 if slice.handle.descriptor() != binding.descriptor() {
519 return Err(IoError::NotFound {
520 backtrace: BackTrace::capture(),
521 reason: "Memory location points to a different allocation".into(),
522 });
523 }
524
525 Ok(slice)
526 }
527
528 fn find_mut(&mut self, binding: &ManagedMemoryBinding) -> Result<&mut Slice, IoError> {
531 let id = binding.descriptor();
532
533 if !id.is_allocated() {
534 return Err(IoError::NotFound {
535 backtrace: BackTrace::capture(),
536 reason: "Memory location was never initialized".into(),
537 });
538 }
539
540 let slice = if id.location().pool == PERSISTENT_POOL_POS {
541 self.persistent.find_mut(binding)?
542 } else {
543 let pool = self
544 .pools
545 .get_mut(id.location().pool as usize)
546 .ok_or_else(|| IoError::NotFound {
547 backtrace: BackTrace::capture(),
548 reason: format!("Pool {} doesn't exist", id.location().pool).into(),
549 })?;
550
551 pool.find_mut(binding)?
552 };
553
554 if slice.handle.descriptor() != binding.descriptor() {
556 return Err(IoError::NotFound {
557 backtrace: BackTrace::capture(),
558 reason: "Memory location points to a different allocation".into(),
559 });
560 }
561
562 Ok(slice)
563 }
564
565 pub fn get_storage(&mut self, binding: ManagedMemoryBinding) -> Result<StorageHandle, IoError> {
572 self.materialize(&binding)?;
573 let slice = self.find(&binding)?;
574 Ok(slice.storage.clone())
575 }
576
577 fn materialize(&mut self, binding: &ManagedMemoryBinding) -> Result<(), IoError> {
581 let location = binding.descriptor().location();
582 if location.init == 0 {
583 return Ok(());
584 }
585 match location.pool {
586 PERSISTENT_POOL_POS => self.persistent.materialize(&mut self.storage, binding),
587 pool => match self.pools.get_mut(pool as usize) {
588 Some(pool) => pool.materialize(&mut self.storage, binding),
589 None => Ok(()),
590 },
591 }
592 }
593
594 pub fn get_resource(
596 &mut self,
597 binding: ManagedMemoryBinding,
598 offset_start: Option<u64>,
599 offset_end: Option<u64>,
600 ) -> Result<Storage::Resource, IoError> {
601 let handle = self.get_storage(binding)?;
602
603 let handle = match offset_start {
604 Some(offset) => handle.offset_start(offset),
605 None => handle,
606 };
607 let handle = match offset_end {
608 Some(offset) => handle.offset_end(offset),
609 None => handle,
610 };
611 self.storage().get(&handle)
612 }
613
614 fn capture_touch(&mut self, handle: &ManagedMemoryHandle) {
624 if let Some(capture) = &mut self.capture {
625 capture.touched.insert(handle.descriptor().id);
626 if capture.priming {
627 capture.primed.push(handle.clone());
630 }
631 }
632 }
633
634 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
636 pub fn reserve(
637 &mut self,
638 size: u64,
639 failures: &mut ErrorGraph,
640 ) -> Result<ManagedMemoryHandle, IoError> {
641 self.alloc_reserve_count += 1;
644
645 self.cleanup(false, failures);
651
652 let mapping = PageMapping::current();
653
654 let persistent_mode = matches!(self.mode, MemoryAllocationMode::Persistent);
661 let size_match = matches!(self.config, PersistentMemory::SizeMatch);
662
663 if (persistent_mode || size_match)
664 && let Some(val) = self.persistent.try_reserve(size, failures)
665 {
666 self.logger.log_memory(
667 |level| matches!(level, MemoryLogLevel::Full),
668 || {
669 format!(
670 "[{}] Reserved memory {size} using persistent memory",
671 self.name
672 )
673 },
674 );
675 self.capture_touch(&val);
676 return Ok(val);
677 }
678
679 if persistent_mode || (size_match && self.persistent.has_size(size)) {
680 let allocated = self
681 .persistent
682 .alloc(&mut self.storage, size, mapping, failures);
683
684 self.logger.log_memory(
685 |level| !matches!(level, MemoryLogLevel::Disabled),
686 || {
687 format!(
688 "[{}] Allocated a new memory page using persistent memory, \n{}",
689 self.name, self,
690 )
691 },
692 );
693 if let Ok(handle) = &allocated {
694 self.capture_touch(handle);
695 }
696 return allocated;
697 }
698
699 self.logger.log_memory(
700 |level| matches!(level, MemoryLogLevel::Full),
701 || {
702 format!(
703 "[{}] Reserved memory {} using dynamic pool",
704 self.name,
705 BytesFormat::new(size)
706 )
707 },
708 );
709
710 let mut capacity_exceeded = None;
719 let mut reserved = None;
720
721 for (index, pool) in self
722 .pools
723 .iter_mut()
724 .enumerate()
725 .filter(|(_, pool)| pool.accept(size))
726 {
727 if let Some(slice) = pool.try_reserve(size, failures) {
728 return Ok(slice);
729 }
730
731 match pool.alloc(&mut self.storage, size, mapping, failures) {
732 Ok(handle) => {
733 reserved = Some(handle);
734 break;
735 }
736 Err(err @ IoError::PoolCapacityExceeded { .. }) => {
737 if self.capacity_warned.insert(index) {
745 log::warn!(
746 "[{}] memory pool {index} is at capacity (first hit at an \
747 allocation of {size} B); spilling to the next accepting pool. \
748 The measured plan is short for this workload.",
749 self.name
750 );
751 }
752 capacity_exceeded = Some(err);
753 }
754 Err(err) => return Err(capacity_exceeded.unwrap_or(err)),
758 }
759 }
760
761 let Some(reserved) = reserved else {
762 return Err(capacity_exceeded.unwrap_or_else(|| IoError::BufferTooBig {
763 size,
764 backtrace: BackTrace::capture(),
765 }));
766 };
767
768 self.logger.log_memory(
769 |level| matches!(level, MemoryLogLevel::Full),
770 || {
771 format!(
772 "[{}], Allocated a new memory page, current usage: \n{}",
773 self.name, self
774 )
775 },
776 );
777
778 Ok(reserved)
779 }
780
781 pub fn storage(&mut self) -> &mut Storage {
792 &mut self.storage
793 }
794
795 pub fn memory_usage(&self) -> MemoryUsage {
797 let memory_usage = self.pools.iter().map(|x| x.get_memory_usage()).fold(
798 MemoryUsage {
799 number_allocs: 0,
800 bytes_in_use: 0,
801 bytes_padding: 0,
802 bytes_reserved: 0,
803 },
804 |m1, m2| m1.combine(m2),
805 );
806 memory_usage.combine(self.persistent.get_memory_usage())
807 }
808
809 pub fn memory_report(&self) -> MemoryReport {
815 MemoryReport {
816 dynamic: self.pools.iter().map(|pool| pool.report()).collect(),
817 persistent: self.persistent.report(),
818 }
819 }
820
821 pub fn print_memory_usage(&self) {
823 #[cfg(feature = "std")]
824 log::info!("{}", self.memory_usage());
825 }
826
827 pub fn bind(
829 &mut self,
830 reserved: ManagedMemoryHandle,
831 assigned: ManagedMemoryHandle,
832 cursor: u64,
833 failures: &mut ErrorGraph,
834 ) -> Result<(), IoError> {
835 let descriptor = reserved.descriptor();
836
837 if !descriptor.is_allocated() {
838 return Err(IoError::NotFound {
839 backtrace: BackTrace::capture(),
840 reason: "Reserved memory isn't initialized".into(),
841 });
842 }
843
844 let pool_index = descriptor.location().pool as usize;
845 if pool_index == PERSISTENT_POOL_POS as usize {
846 self.capture_touch(&assigned);
850 return self.persistent.bind(reserved, assigned, cursor, failures);
851 }
852
853 self.pools
854 .get_mut(pool_index)
855 .map(|p| p.bind(reserved, assigned, cursor, failures))
856 .ok_or_else(|| IoError::NotFound {
857 backtrace: BackTrace::capture(),
858 reason: format!("Memory pool {} doesn't exist", pool_index).into(),
859 })?
860 }
861
862 pub fn failure(&self, binding: &ManagedMemoryBinding, range: Range<u64>) -> Option<FailureId> {
870 self.find(binding)
871 .ok()
872 .and_then(|slice| slice.tainted.failure(&range))
873 }
874
875 pub fn taint(
880 &mut self,
881 binding: &ManagedMemoryBinding,
882 range: Range<u64>,
883 failure: FailureId,
884 failures: &mut ErrorGraph,
885 ) {
886 if let Ok(slice) = self.find_mut(binding) {
887 slice.tainted.taint(range, failure, failures);
888 }
889 }
890
891 pub fn written(
895 &mut self,
896 binding: &ManagedMemoryBinding,
897 range: Range<u64>,
898 failures: &mut ErrorGraph,
899 ) {
900 if let Ok(slice) = self.find_mut(binding) {
901 slice.tainted.written(range, failures);
902 }
903 }
904}
905
906impl<Storage: ComputeStorage> core::fmt::Display for MemoryManagement<Storage> {
907 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
908 f.write_str("\n# MemoryManagement\n\n")?;
909 f.write_fmt(format_args!(" - name: {:?}\n", self.name))?;
910 f.write_fmt(format_args!("\n## Persistent\n\n{}", self.persistent))?;
911 f.write_str("\n## Dynamic\n\n")?;
912
913 for pool in self.pools.iter() {
914 f.write_fmt(format_args!("{pool}\n"))?;
915 }
916 let memory_usage = self.memory_usage();
917 f.write_fmt(format_args!("\n## Summary\n\n{memory_usage}"))?;
918
919 Ok(())
920 }
921}
922
923impl<Storage> core::fmt::Debug for MemoryManagement<Storage> {
924 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
925 f.write_str(
926 alloc::format!(
927 "DynamicMemoryManagement {:?}",
928 core::any::type_name::<Storage>(),
929 )
930 .as_str(),
931 )
932 }
933}
934
935fn build_pools(
939 properties: &MemoryDeviceProperties,
940 config: MemoryConfiguration,
941 logger: &Arc<ServerLogger>,
942 name: &str,
943) -> Vec<DynamicPool> {
944 let pool_options = config.pool_options(properties);
945
946 logger.log_memory(
947 |level| !matches!(level, MemoryLogLevel::Disabled),
948 || {
949 let mut msg = String::new();
950 for pool in pool_options.iter() {
951 msg += &format!("[{name}] Using memory pool: \n {pool:?}\n");
952 }
953 msg
954 },
955 );
956
957 assert!(
958 pool_options.len() < PERSISTENT_POOL_POS as usize,
959 "at most {} dynamic pools are supported",
960 PERSISTENT_POOL_POS - 1
961 );
962
963 pool_options
964 .iter()
965 .enumerate()
966 .map(|(pool_pos, pool)| {
967 let pool_pos = pool_pos as u8;
968
969 match pool.pool_type {
970 PoolType::SlicedPages {
971 page_size,
972 max_slice_size,
973 max_pool_size,
974 } => DynamicPool::Sliced(SlicedPool::new(
975 page_size,
976 max_slice_size,
977 properties.alignment,
978 pool_pos,
979 max_pool_size,
980 )),
981 PoolType::ExclusivePages { max_alloc_size } => {
982 DynamicPool::Exclusive(ExclusiveMemoryPool::new(
983 max_alloc_size,
984 properties.alignment,
985 pool.dealloc_period.unwrap_or(u64::MAX),
986 pool_pos,
987 ))
988 }
989 PoolType::Direct { reclaim_at } => {
990 DynamicPool::Direct(DirectPool::new(properties.alignment, pool_pos, reclaim_at))
991 }
992 }
993 })
994 .collect()
995}
996
997#[cfg(test)]
998mod tests {
999 use super::*;
1000 use crate::memory_management::MemoryPoolOptions;
1001 use crate::{
1002 config::memory::{MemoryPoolConfig, MemoryPoolsConfig, MemoryPoolsPreset},
1003 memory_management::{MemoryManagement, PoolConfigError},
1004 storage::BytesStorage,
1005 };
1006 use alloc::vec;
1007
1008 const DUMMY_MEM_PROPS: MemoryDeviceProperties =
1009 MemoryDeviceProperties::new(128 * 1024 * 1024, 32);
1010
1011 fn options() -> MemoryManagementOptions {
1012 MemoryManagementOptions {
1013 name: "test".into(),
1014 memory: MemoryAllocationOption::FromConfig,
1015 }
1016 }
1017
1018 #[test_log::test]
1020 #[cfg(not(exclusive_memory_only))]
1021 fn test_handle_mutability() {
1022 let mut memory_management = MemoryManagement::from_configuration(
1023 BytesStorage::default(),
1024 &DUMMY_MEM_PROPS,
1025 MemoryConfiguration::SubSlices,
1026 Arc::new(ServerLogger::default()),
1027 options(),
1028 );
1029 let handle = memory_management
1030 .reserve(10, &mut ErrorGraph::default())
1031 .unwrap();
1032 let other_ref = handle.clone();
1033 assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
1034 drop(other_ref);
1035 assert!(handle.can_mut(), "Handle should be mut when only one ref.");
1036 }
1037
1038 #[test_log::test]
1040 #[cfg(not(exclusive_memory_only))]
1041 fn test_memory_usage() {
1042 let max_page_size = 512;
1043
1044 let mut memory_management = MemoryManagement::from_configuration(
1045 BytesStorage::default(),
1046 &DUMMY_MEM_PROPS,
1047 MemoryConfiguration::Custom {
1048 pool_options: vec![MemoryPoolOptions {
1049 pool_type: PoolType::ExclusivePages {
1050 max_alloc_size: max_page_size,
1051 },
1052 dealloc_period: None,
1053 }],
1054 },
1055 Arc::new(ServerLogger::default()),
1056 options(),
1057 );
1058 let handle = memory_management.reserve(100, &mut ErrorGraph::default());
1059 let usage = memory_management.memory_usage();
1060
1061 assert_eq!(usage.bytes_in_use, 100);
1062 assert!(usage.bytes_reserved >= 100 && usage.bytes_reserved <= max_page_size);
1063
1064 drop(handle);
1066 let _handle = memory_management.reserve(100, &mut ErrorGraph::default());
1067 let usage_new = memory_management.memory_usage();
1068 assert_eq!(usage, usage_new);
1069 }
1070
1071 #[test_log::test]
1072 fn find_uninit_binding_returns_not_found() {
1073 let mut memory_management = MemoryManagement::from_configuration(
1074 BytesStorage::default(),
1075 &DUMMY_MEM_PROPS,
1076 MemoryConfiguration::Custom {
1077 pool_options: vec![MemoryPoolOptions {
1078 pool_type: PoolType::SlicedPages {
1079 page_size: 2048,
1080 max_slice_size: 2048,
1081 max_pool_size: None,
1082 },
1083 dealloc_period: None,
1084 }],
1085 },
1086 Arc::new(ServerLogger::default()),
1087 options(),
1088 );
1089
1090 let _live = memory_management
1093 .reserve(512, &mut ErrorGraph::default())
1094 .unwrap();
1095
1096 let binding = ManagedMemoryHandle::new().binding();
1097 assert!(matches!(
1098 memory_management.get_cursor(binding),
1099 Err(IoError::NotFound { .. })
1100 ));
1101 }
1102
1103 #[test_log::test]
1104 fn find_stale_descriptor_returns_not_found() {
1105 let mut memory_management = MemoryManagement::from_configuration(
1106 BytesStorage::default(),
1107 &DUMMY_MEM_PROPS,
1108 MemoryConfiguration::Custom {
1109 pool_options: vec![MemoryPoolOptions {
1110 pool_type: PoolType::SlicedPages {
1111 page_size: 2048,
1112 max_slice_size: 2048,
1113 max_pool_size: None,
1114 },
1115 dealloc_period: None,
1116 }],
1117 },
1118 Arc::new(ServerLogger::default()),
1119 options(),
1120 );
1121
1122 let reserved = memory_management
1123 .reserve(512, &mut ErrorGraph::default())
1124 .unwrap();
1125 let stale = reserved.clone();
1126 let assigned = ManagedMemoryHandle::new();
1127 let assigned_binding = assigned.clone().binding();
1128
1129 memory_management
1130 .bind(reserved, assigned, 0, &mut ErrorGraph::default())
1131 .unwrap();
1132
1133 assert!(matches!(
1136 memory_management.get_cursor(stale.binding()),
1137 Err(IoError::NotFound { .. })
1138 ));
1139 assert!(memory_management.get_cursor(assigned_binding).is_ok());
1140 }
1141
1142 #[test_log::test]
1143 fn held_binding_survives_explicit_cleanup_renumber() {
1144 let mut memory_management = MemoryManagement::from_configuration(
1145 BytesStorage::default(),
1146 &DUMMY_MEM_PROPS,
1147 MemoryConfiguration::Custom {
1148 pool_options: vec![MemoryPoolOptions {
1149 pool_type: PoolType::ExclusivePages {
1150 max_alloc_size: 1024,
1151 },
1152 dealloc_period: None,
1153 }],
1154 },
1155 Arc::new(ServerLogger::default()),
1156 options(),
1157 );
1158
1159 let handle_a = memory_management
1160 .reserve(1024, &mut ErrorGraph::default())
1161 .unwrap();
1162 let handle_b = memory_management
1163 .reserve(1024, &mut ErrorGraph::default())
1164 .unwrap();
1165 let handle_c = memory_management
1166 .reserve(1024, &mut ErrorGraph::default())
1167 .unwrap();
1168
1169 let binding_b = handle_b.binding();
1170 drop(handle_a);
1171 drop(handle_c);
1172
1173 memory_management.cleanup(true, &mut ErrorGraph::default());
1175
1176 assert!(memory_management.get_cursor(binding_b.clone()).is_ok());
1177 assert!(memory_management.get_storage(binding_b).is_ok());
1178 assert_eq!(memory_management.memory_usage().bytes_reserved, 1024);
1179 }
1180
1181 fn capped_sliced_config(page_size: u64, max_pool_size: Option<u64>) -> MemoryConfiguration {
1182 MemoryConfiguration::Custom {
1183 pool_options: vec![MemoryPoolOptions {
1184 pool_type: PoolType::SlicedPages {
1185 page_size,
1186 max_slice_size: page_size,
1187 max_pool_size,
1188 },
1189 dealloc_period: None,
1190 }],
1191 }
1192 }
1193
1194 #[test_log::test]
1195 fn capped_sliced_pool_errors_instead_of_growing() {
1196 let mut memory_management = MemoryManagement::from_configuration(
1197 BytesStorage::default(),
1198 &DUMMY_MEM_PROPS,
1199 capped_sliced_config(1024, Some(2048)),
1200 Arc::new(ServerLogger::default()),
1201 options(),
1202 );
1203
1204 let _a = memory_management
1205 .reserve(1024, &mut ErrorGraph::default())
1206 .unwrap();
1207 let _b = memory_management
1208 .reserve(1024, &mut ErrorGraph::default())
1209 .unwrap();
1210
1211 let result = memory_management.reserve(1024, &mut ErrorGraph::default());
1212 assert!(matches!(
1213 result,
1214 Err(IoError::PoolCapacityExceeded { capacity: 2048, .. })
1215 ));
1216 assert_eq!(
1217 memory_management.memory_usage().bytes_reserved,
1218 2048,
1219 "a failed reservation must not grow the pool"
1220 );
1221 }
1222
1223 #[test_log::test]
1227 fn persistent_window_serves_what_a_capped_layout_refuses() {
1228 let mut memory_management = MemoryManagement::from_configuration(
1229 BytesStorage::default(),
1230 &DUMMY_MEM_PROPS,
1231 capped_sliced_config(1024, Some(1024)),
1232 Arc::new(ServerLogger::default()),
1233 options(),
1234 );
1235
1236 let refused = memory_management.reserve(4096, &mut ErrorGraph::default());
1237 assert!(matches!(refused, Err(IoError::BufferTooBig { .. })));
1238
1239 memory_management.mode(MemoryAllocationMode::Persistent);
1240 let _input = memory_management
1241 .reserve(4096, &mut ErrorGraph::default())
1242 .unwrap();
1243 let _output = memory_management
1244 .reserve(4096, &mut ErrorGraph::default())
1245 .unwrap();
1246 let _line = memory_management
1247 .reserve(16, &mut ErrorGraph::default())
1248 .unwrap();
1249 memory_management.mode(MemoryAllocationMode::Auto);
1250
1251 let report = memory_management.memory_report();
1252 assert_eq!(report.persistent.usage.bytes_in_use, 2 * 4096 + 16);
1253 assert_eq!(report.dynamic[0].pages_peak, 0);
1254 }
1255
1256 #[test_log::test]
1257 fn capped_sliced_pool_reuses_freed_memory() {
1258 let mut memory_management = MemoryManagement::from_configuration(
1259 BytesStorage::default(),
1260 &DUMMY_MEM_PROPS,
1261 capped_sliced_config(1024, Some(2048)),
1262 Arc::new(ServerLogger::default()),
1263 options(),
1264 );
1265
1266 let handle_a = memory_management
1267 .reserve(1024, &mut ErrorGraph::default())
1268 .unwrap();
1269 let _b = memory_management
1270 .reserve(1024, &mut ErrorGraph::default())
1271 .unwrap();
1272 drop(handle_a);
1273
1274 let _c = memory_management
1277 .reserve(1024, &mut ErrorGraph::default())
1278 .unwrap();
1279 assert_eq!(memory_management.memory_usage().bytes_reserved, 2048);
1280 }
1281
1282 #[test_log::test]
1283 fn capped_lazy_pool_cleanup_still_frees() {
1284 let mut memory_management = MemoryManagement::from_configuration(
1285 BytesStorage::default(),
1286 &DUMMY_MEM_PROPS,
1287 capped_sliced_config(1024, Some(2048)),
1288 Arc::new(ServerLogger::default()),
1289 options(),
1290 );
1291
1292 let handle_a = memory_management
1293 .reserve(1024, &mut ErrorGraph::default())
1294 .unwrap();
1295 let handle_b = memory_management
1296 .reserve(1024, &mut ErrorGraph::default())
1297 .unwrap();
1298 drop(handle_a);
1299 drop(handle_b);
1300 memory_management.cleanup(true, &mut ErrorGraph::default());
1301 assert_eq!(memory_management.memory_usage().bytes_reserved, 0);
1302
1303 let _a = memory_management
1305 .reserve(1024, &mut ErrorGraph::default())
1306 .unwrap();
1307 let _b = memory_management
1308 .reserve(1024, &mut ErrorGraph::default())
1309 .unwrap();
1310 assert!(matches!(
1311 memory_management.reserve(1024, &mut ErrorGraph::default()),
1312 Err(IoError::PoolCapacityExceeded { .. })
1313 ));
1314 }
1315
1316 #[test_log::test]
1317 fn max_pool_size_smaller_than_page_shrinks_page() {
1318 let mut memory_management = MemoryManagement::from_configuration(
1319 BytesStorage::default(),
1320 &DUMMY_MEM_PROPS,
1321 capped_sliced_config(2048, Some(512)),
1322 Arc::new(ServerLogger::default()),
1323 options(),
1324 );
1325
1326 let _small = memory_management
1327 .reserve(256, &mut ErrorGraph::default())
1328 .unwrap();
1329 assert!(memory_management.memory_usage().bytes_reserved <= 512);
1330
1331 assert!(
1333 memory_management
1334 .reserve(1024, &mut ErrorGraph::default())
1335 .is_err()
1336 );
1337 assert!(memory_management.memory_usage().bytes_reserved <= 512);
1338 }
1339
1340 #[test_log::test]
1341 fn max_pool_size_below_alignment_never_overshoots() {
1342 let mut memory_management = MemoryManagement::from_configuration(
1346 BytesStorage::default(),
1347 &DUMMY_MEM_PROPS,
1348 capped_sliced_config(1024, Some(16)),
1349 Arc::new(ServerLogger::default()),
1350 options(),
1351 );
1352
1353 assert!(matches!(
1354 memory_management.reserve(8, &mut ErrorGraph::default()),
1355 Err(IoError::PoolCapacityExceeded { .. })
1356 ));
1357 assert_eq!(memory_management.memory_usage().bytes_reserved, 0);
1358 }
1359
1360 #[test_log::test]
1366 fn persistent_windows_nest() {
1367 let mut memory_management = MemoryManagement::from_configuration(
1368 BytesStorage::default(),
1369 &DUMMY_MEM_PROPS,
1370 MemoryConfiguration::Custom {
1371 pool_options: vec![MemoryPoolOptions {
1372 pool_type: PoolType::SlicedPages {
1373 page_size: 4096,
1374 max_slice_size: 4096,
1375 max_pool_size: None,
1376 },
1377 dealloc_period: None,
1378 }],
1379 },
1380 Arc::new(ServerLogger::default()),
1381 options(),
1382 );
1383
1384 memory_management.mode(MemoryAllocationMode::Persistent); memory_management.mode(MemoryAllocationMode::Persistent); memory_management.mode(MemoryAllocationMode::Auto); let weight = memory_management
1390 .reserve(1024, &mut ErrorGraph::default())
1391 .unwrap();
1392 let report = memory_management.memory_report();
1393 assert_eq!(
1394 report.persistent.usage.bytes_in_use, 1024,
1395 "an allocation inside the outer window is persistent"
1396 );
1397 assert_eq!(
1398 report.dynamic[0].pages_peak, 0,
1399 "nothing leaked into the dynamic pools"
1400 );
1401
1402 memory_management.mode(MemoryAllocationMode::Auto);
1404 let _transient = memory_management
1405 .reserve(1024, &mut ErrorGraph::default())
1406 .unwrap();
1407 assert_eq!(memory_management.memory_report().dynamic[0].pages_peak, 1);
1408
1409 drop(weight);
1410 }
1411
1412 #[test_log::test]
1421 fn capacity_overflow_falls_through_to_later_accepting_pool() {
1422 let mut memory_management = MemoryManagement::from_configuration(
1423 BytesStorage::default(),
1424 &DUMMY_MEM_PROPS,
1425 MemoryConfiguration::Custom {
1426 pool_options: vec![
1427 MemoryPoolOptions {
1428 pool_type: PoolType::SlicedPages {
1429 page_size: 1024,
1430 max_slice_size: 1024,
1431 max_pool_size: Some(1024),
1432 },
1433 dealloc_period: None,
1434 },
1435 MemoryPoolOptions {
1436 pool_type: PoolType::SlicedPages {
1437 page_size: 1024,
1438 max_slice_size: 1024,
1439 max_pool_size: None,
1440 },
1441 dealloc_period: None,
1442 },
1443 ],
1444 },
1445 Arc::new(ServerLogger::default()),
1446 options(),
1447 );
1448
1449 let _fill = memory_management
1450 .reserve(1024, &mut ErrorGraph::default())
1451 .unwrap();
1452 let _overflow = memory_management
1453 .reserve(1024, &mut ErrorGraph::default())
1454 .unwrap();
1455 assert_eq!(
1456 memory_management.memory_usage().bytes_reserved,
1457 2048,
1458 "the overflow landed in the later pool, not a second arena page"
1459 );
1460
1461 let report = memory_management.memory_report();
1462 assert_eq!(report.dynamic[0].pages_peak, 1, "the cap held");
1463 assert_eq!(report.dynamic[1].pages_peak, 1, "the tail caught the spill");
1464 }
1465
1466 #[test_log::test]
1467 fn alloc_two_chunks_on_one_page() {
1468 let page_size = 2048;
1469
1470 let mut memory_management = MemoryManagement::from_configuration(
1471 BytesStorage::default(),
1472 &DUMMY_MEM_PROPS,
1473 MemoryConfiguration::Custom {
1474 pool_options: vec![MemoryPoolOptions {
1475 pool_type: PoolType::SlicedPages {
1476 page_size,
1477 max_slice_size: page_size,
1478 max_pool_size: None,
1479 },
1480 dealloc_period: None,
1481 }],
1482 },
1483 Arc::new(ServerLogger::default()),
1484 options(),
1485 );
1486
1487 let alloc_size = 512;
1488 let _handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1489 let _new_handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1490
1491 let usage = memory_management.memory_usage();
1492 assert_eq!(usage.number_allocs, 2);
1493 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1494 assert_eq!(usage.bytes_reserved, page_size);
1495 }
1496
1497 #[test_log::test]
1498 fn alloc_reuses_storage() {
1499 let page_size = 512;
1501
1502 let mut memory_management = MemoryManagement::from_configuration(
1503 BytesStorage::default(),
1504 &DUMMY_MEM_PROPS,
1505 MemoryConfiguration::Custom {
1506 pool_options: vec![MemoryPoolOptions {
1507 pool_type: PoolType::SlicedPages {
1508 page_size,
1509 max_slice_size: page_size,
1510 max_pool_size: None,
1511 },
1512 dealloc_period: None,
1513 }],
1514 },
1515 Arc::new(ServerLogger::default()),
1516 options(),
1517 );
1518
1519 let alloc_size = 512;
1520 let _handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1521 drop(_handle);
1522 let _new_handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1523
1524 let usage = memory_management.memory_usage();
1525 assert_eq!(usage.number_allocs, 1);
1526 assert_eq!(usage.bytes_in_use, alloc_size);
1527 assert_eq!(usage.bytes_reserved, page_size);
1528 }
1529
1530 #[test_log::test]
1531 fn alloc_allocs_new_storage() {
1532 let page_size = 1024;
1533
1534 let mut memory_management = MemoryManagement::from_configuration(
1535 BytesStorage::default(),
1536 &DUMMY_MEM_PROPS,
1537 MemoryConfiguration::Custom {
1538 pool_options: vec![MemoryPoolOptions {
1539 pool_type: PoolType::SlicedPages {
1540 page_size,
1541 max_slice_size: page_size,
1542 max_pool_size: None,
1543 },
1544 dealloc_period: None,
1545 }],
1546 },
1547 Arc::new(ServerLogger::default()),
1548 options(),
1549 );
1550
1551 let alloc_size = 768;
1552 let _handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1553 let _new_handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1554
1555 let usage = memory_management.memory_usage();
1556 assert_eq!(usage.number_allocs, 2);
1557 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1558 assert_eq!(usage.bytes_reserved, page_size * 2);
1559 }
1560
1561 #[test_log::test]
1562 fn alloc_respects_alignment_size() {
1563 let page_size = 500;
1564 let mut memory_management = MemoryManagement::from_configuration(
1565 BytesStorage::default(),
1566 &MemoryDeviceProperties::new(page_size, 50),
1567 MemoryConfiguration::Custom {
1568 pool_options: vec![MemoryPoolOptions {
1569 pool_type: PoolType::SlicedPages {
1570 page_size,
1571 max_slice_size: page_size,
1572 max_pool_size: None,
1573 },
1574 dealloc_period: None,
1575 }],
1576 },
1577 Arc::new(ServerLogger::default()),
1578 options(),
1579 );
1580 let alloc_size = 40;
1581 let _handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1582 let _new_handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1583 let usage = memory_management.memory_usage();
1584 assert_eq!(usage.bytes_padding, 10 * 2);
1586 }
1587
1588 #[test_log::test]
1589 fn allocs_on_correct_page() {
1590 let sizes = [100, 200, 300, 400];
1591
1592 let pools = sizes
1593 .iter()
1594 .map(|size| MemoryPoolOptions {
1595 pool_type: PoolType::SlicedPages {
1596 page_size: *size,
1597 max_slice_size: *size,
1598 max_pool_size: None,
1599 },
1600 dealloc_period: None,
1601 })
1602 .collect();
1603 let mut memory_management = MemoryManagement::from_configuration(
1604 BytesStorage::default(),
1605 &MemoryDeviceProperties::new(128 * 1024 * 1024, 10),
1606 MemoryConfiguration::Custom {
1607 pool_options: pools,
1608 },
1609 Arc::new(ServerLogger::default()),
1610 options(),
1611 );
1612 let alloc_sizes = [50, 150, 250, 350];
1614 let _handles =
1615 alloc_sizes.map(|s| memory_management.reserve(s, &mut ErrorGraph::default()));
1616
1617 let usage = memory_management.memory_usage();
1618
1619 assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
1621 assert!(usage.bytes_reserved >= sizes.iter().sum::<u64>());
1622 }
1623
1624 #[test_log::test]
1625 fn resolve_absent_pools_config_keeps_runtime_choice() {
1626 #[cfg(not(exclusive_memory_only))]
1627 assert!(matches!(
1628 MemoryConfiguration::SubSlices
1629 .resolve(None, &DUMMY_MEM_PROPS)
1630 .unwrap(),
1631 MemoryConfiguration::SubSlices
1632 ));
1633 assert!(matches!(
1634 MemoryConfiguration::ExclusivePages
1635 .resolve(None, &DUMMY_MEM_PROPS)
1636 .unwrap(),
1637 MemoryConfiguration::ExclusivePages
1638 ));
1639 }
1640
1641 #[test_log::test]
1642 fn resolve_preset_overrides_runtime_choice() {
1643 let preset = MemoryPoolsConfig::Preset(MemoryPoolsPreset::ExclusivePages);
1644 #[cfg(not(exclusive_memory_only))]
1645 let base = MemoryConfiguration::SubSlices;
1646 #[cfg(exclusive_memory_only)]
1647 let base = MemoryConfiguration::ExclusivePages;
1648
1649 assert!(matches!(
1650 base.resolve(Some(&preset), &DUMMY_MEM_PROPS).unwrap(),
1651 MemoryConfiguration::ExclusivePages
1652 ));
1653 }
1654
1655 #[test_log::test]
1656 #[cfg(exclusive_memory_only)]
1657 fn resolve_rejects_sliced_pools_when_exclusive_only() {
1658 use crate::config::size::MemorySize;
1659
1660 let pools = MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1661 page_size: MemorySize(1024),
1662 max_slice_size: None,
1663 max_pool_size: None,
1664 dealloc_period: None,
1665 }]);
1666 assert_eq!(
1667 MemoryConfiguration::default()
1668 .resolve(Some(&pools), &DUMMY_MEM_PROPS)
1669 .unwrap_err(),
1670 PoolConfigError::SlicedPoolsUnavailable
1671 );
1672 }
1673
1674 #[test_log::test]
1675 #[cfg(not(exclusive_memory_only))]
1676 fn resolve_explicit_list_aligns_and_defaults() {
1677 use crate::config::size::MemorySize;
1678
1679 let pools = MemoryPoolsConfig::Explicit(vec![
1680 MemoryPoolConfig::Exclusive {
1681 max_alloc_size: MemorySize(8 * 1024),
1682 dealloc_period: Some(10000),
1683 },
1684 MemoryPoolConfig::Sliced {
1685 page_size: MemorySize(1000),
1687 max_slice_size: None,
1688 max_pool_size: Some(MemorySize(4096)),
1689 dealloc_period: None,
1690 },
1691 ]);
1692
1693 let resolved = MemoryConfiguration::default()
1694 .resolve(Some(&pools), &DUMMY_MEM_PROPS)
1695 .unwrap();
1696 let MemoryConfiguration::Custom { pool_options } = resolved else {
1697 panic!("expected a custom configuration");
1698 };
1699
1700 assert_eq!(pool_options.len(), 2);
1701 assert!(matches!(
1702 pool_options[0].pool_type,
1703 PoolType::ExclusivePages {
1704 max_alloc_size: 8192
1705 }
1706 ));
1707 assert_eq!(pool_options[0].dealloc_period, Some(10000));
1708 assert!(matches!(
1709 pool_options[1].pool_type,
1710 PoolType::SlicedPages {
1711 page_size: 1024,
1712 max_slice_size: 1024,
1714 max_pool_size: Some(4096),
1715 }
1716 ));
1717 }
1718
1719 #[test_log::test]
1720 #[cfg(not(exclusive_memory_only))]
1721 fn resolve_invalid_pool_configs_fail() {
1722 use crate::config::size::MemorySize;
1723
1724 let cases = [
1725 (
1726 MemoryPoolsConfig::Explicit(vec![]),
1727 PoolConfigError::EmptyPoolList,
1728 ),
1729 (
1730 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1731 page_size: MemorySize(0),
1732 max_slice_size: None,
1733 max_pool_size: None,
1734 dealloc_period: None,
1735 }]),
1736 PoolConfigError::ZeroSize { field: "page_size" },
1737 ),
1738 (
1739 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1740 page_size: MemorySize(1024),
1741 max_slice_size: Some(MemorySize(2048)),
1742 max_pool_size: None,
1743 dealloc_period: None,
1744 }]),
1745 PoolConfigError::SliceLargerThanPage {
1746 page_size: 1024,
1747 max_slice_size: 2048,
1748 },
1749 ),
1750 (
1751 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1752 page_size: MemorySize(2048),
1753 max_slice_size: None,
1754 max_pool_size: Some(MemorySize(1024)),
1755 dealloc_period: None,
1756 }]),
1757 PoolConfigError::CapSmallerThanPage {
1758 page_size: 2048,
1759 max_pool_size: 1024,
1760 },
1761 ),
1762 (
1763 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1764 page_size: MemorySize(1024),
1765 max_slice_size: None,
1766 max_pool_size: Some(MemorySize(64 * 1024 * 1024 * 1024)),
1768 dealloc_period: None,
1769 }]),
1770 PoolConfigError::TooManyPages {
1771 pages: 64 * 1024 * 1024,
1772 },
1773 ),
1774 (
1775 MemoryPoolsConfig::Explicit(vec![
1776 MemoryPoolConfig::Exclusive {
1777 max_alloc_size: MemorySize(1024),
1778 dealloc_period: None,
1779 };
1780 PERSISTENT_POOL_POS as usize
1781 ]),
1782 PoolConfigError::TooManyPools {
1783 count: PERSISTENT_POOL_POS as usize,
1784 },
1785 ),
1786 ];
1787
1788 for (pools, expected) in cases {
1789 let result = MemoryConfiguration::default().resolve(Some(&pools), &DUMMY_MEM_PROPS);
1790 assert_eq!(result.unwrap_err(), expected);
1791 }
1792 }
1793
1794 #[test_log::test]
1798 #[cfg(not(exclusive_memory_only))]
1799 fn resolved_single_arena_reuses_across_sizes() {
1800 use crate::config::size::MemorySize;
1801
1802 let page = 1024 * 1024; let pools = MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1804 page_size: MemorySize(page),
1805 max_slice_size: None,
1806 max_pool_size: None,
1807 dealloc_period: None,
1808 }]);
1809 let config = MemoryConfiguration::default()
1810 .resolve(Some(&pools), &DUMMY_MEM_PROPS)
1811 .unwrap();
1812
1813 let mut memory_management = MemoryManagement::from_configuration(
1814 BytesStorage::default(),
1815 &DUMMY_MEM_PROPS,
1816 config,
1817 Arc::new(ServerLogger::default()),
1818 options(),
1819 );
1820
1821 let small = memory_management
1823 .reserve(4 * 1024, &mut ErrorGraph::default())
1824 .unwrap();
1825 drop(small);
1826 let large = memory_management
1828 .reserve(512 * 1024, &mut ErrorGraph::default())
1829 .unwrap();
1830
1831 let usage = memory_management.memory_usage();
1832 assert_eq!(
1833 usage.bytes_reserved, page,
1834 "both sizes must share a single arena page"
1835 );
1836 assert_eq!(usage.number_allocs, 1);
1837 drop(large);
1838 }
1839
1840 #[test_log::test]
1841 #[cfg(not(exclusive_memory_only))]
1842 fn allocate_deallocate_reallocate() {
1843 let mut memory_management = MemoryManagement::from_configuration(
1844 BytesStorage::default(),
1845 &MemoryDeviceProperties::new(128 * 1024 * 1024, 32),
1846 MemoryConfiguration::SubSlices,
1847 Arc::new(ServerLogger::default()),
1848 options(),
1849 );
1850 let handles: Vec<_> = (0..5)
1852 .map(|i| memory_management.reserve(1000 * (i + 1), &mut ErrorGraph::default()))
1853 .collect();
1854 let usage_before = memory_management.memory_usage();
1855 drop(handles);
1857 let _new_handles: Vec<_> = (0..5)
1859 .map(|i| memory_management.reserve(1000 * (i + 1), &mut ErrorGraph::default()))
1860 .collect();
1861 let usage_after = memory_management.memory_usage();
1862 assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
1863 assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
1864 assert!(usage_before.bytes_reserved >= usage_after.bytes_reserved);
1866 }
1867
1868 #[test_log::test]
1869 #[cfg(not(exclusive_memory_only))]
1870 fn test_fragmentation_resistance() {
1871 let mut memory_management = MemoryManagement::from_configuration(
1872 BytesStorage::default(),
1873 &MemoryDeviceProperties::new(128 * 1024 * 1024, 32),
1874 MemoryConfiguration::SubSlices,
1875 Arc::new(ServerLogger::default()),
1876 options(),
1877 );
1878 let sizes = [50, 1000, 100, 5000, 200, 10000, 300];
1880 let handles: Vec<_> = sizes
1881 .iter()
1882 .map(|&size| {
1883 memory_management
1884 .reserve(size, &mut ErrorGraph::default())
1885 .unwrap()
1886 })
1887 .collect();
1888 let usage_before = memory_management.memory_usage();
1889 for i in (0..handles.len()).step_by(2) {
1891 drop(handles[i].clone());
1892 }
1893 for &size in &sizes[0..sizes.len() / 2] {
1895 memory_management
1896 .reserve(size, &mut ErrorGraph::default())
1897 .unwrap();
1898 }
1899 let usage_after = memory_management.memory_usage();
1900 assert!(usage_after.bytes_reserved <= (usage_before.bytes_reserved as f64 * 1.1) as u64);
1902 }
1903
1904 #[test_log::test]
1906 fn noslice_test_handle_mutability() {
1907 let mut memory_management = MemoryManagement::from_configuration(
1908 BytesStorage::default(),
1909 &MemoryDeviceProperties::new(128 * 1024 * 1024, 32),
1910 MemoryConfiguration::ExclusivePages,
1911 Arc::new(ServerLogger::default()),
1912 options(),
1913 );
1914 let handle = memory_management
1915 .reserve(10, &mut ErrorGraph::default())
1916 .unwrap();
1917 let other_ref = handle.clone();
1918 assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
1919 drop(other_ref);
1920 assert!(handle.can_mut(), "Handle should be mut when only one ref.");
1921 }
1922
1923 #[test_log::test]
1924 fn noslice_alloc_two_chunk() {
1925 let mut memory_management = MemoryManagement::from_configuration(
1926 BytesStorage::default(),
1927 &DUMMY_MEM_PROPS,
1928 MemoryConfiguration::Custom {
1929 pool_options: vec![MemoryPoolOptions {
1930 pool_type: PoolType::ExclusivePages {
1931 max_alloc_size: 1024,
1932 },
1933 dealloc_period: None,
1934 }],
1935 },
1936 Arc::new(ServerLogger::default()),
1937 options(),
1938 );
1939
1940 let alloc_size = 512;
1941 let _handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1942 let _new_handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1943
1944 let usage = memory_management.memory_usage();
1945 assert_eq!(usage.number_allocs, 2);
1946 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1947 assert!(usage.bytes_reserved >= alloc_size * 2);
1948 }
1949
1950 #[test_log::test]
1951 fn noslice_alloc_reuses_storage() {
1952 let mut memory_management = MemoryManagement::from_configuration(
1954 BytesStorage::default(),
1955 &DUMMY_MEM_PROPS,
1956 MemoryConfiguration::Custom {
1957 pool_options: vec![MemoryPoolOptions {
1958 pool_type: PoolType::ExclusivePages {
1959 max_alloc_size: 1024,
1960 },
1961 dealloc_period: None,
1962 }],
1963 },
1964 Arc::new(ServerLogger::default()),
1965 options(),
1966 );
1967
1968 let alloc_size = 512;
1969 let _handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1970 drop(_handle);
1971 let _new_handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1972
1973 let usage = memory_management.memory_usage();
1974 assert_eq!(usage.number_allocs, 1);
1975 assert_eq!(usage.bytes_in_use, alloc_size);
1976 assert!(usage.bytes_reserved >= alloc_size);
1977 }
1978
1979 #[test_log::test]
1980 fn noslice_alloc_allocs_new_storage() {
1981 let mut memory_management = MemoryManagement::from_configuration(
1982 BytesStorage::default(),
1983 &DUMMY_MEM_PROPS,
1984 MemoryConfiguration::Custom {
1985 pool_options: vec![MemoryPoolOptions {
1986 pool_type: PoolType::ExclusivePages {
1987 max_alloc_size: 1024,
1988 },
1989 dealloc_period: None,
1990 }],
1991 },
1992 Arc::new(ServerLogger::default()),
1993 options(),
1994 );
1995
1996 let alloc_size = 768;
1997 let _handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1998 let _new_handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
1999 let usage = memory_management.memory_usage();
2000 assert_eq!(usage.number_allocs, 2);
2001 assert_eq!(usage.bytes_in_use, alloc_size * 2);
2002 assert!(usage.bytes_reserved >= alloc_size * 2);
2003 }
2004
2005 #[test_log::test]
2006 fn noslice_alloc_respects_alignment_size() {
2007 let mut memory_management = MemoryManagement::from_configuration(
2008 BytesStorage::default(),
2009 &MemoryDeviceProperties::new(DUMMY_MEM_PROPS.max_page_size, 50),
2010 MemoryConfiguration::Custom {
2011 pool_options: vec![MemoryPoolOptions {
2012 pool_type: PoolType::ExclusivePages {
2013 max_alloc_size: 50 * 20,
2014 },
2015 dealloc_period: None,
2016 }],
2017 },
2018 Arc::new(ServerLogger::default()),
2019 options(),
2020 );
2021 let alloc_size = 40;
2022 let _handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
2023 let _new_handle = memory_management.reserve(alloc_size, &mut ErrorGraph::default());
2024 let usage = memory_management.memory_usage();
2025 assert_eq!(usage.bytes_padding, 10 * 2);
2027 }
2028
2029 #[test_log::test]
2030 fn noslice_allocs_on_correct_page() {
2031 let pools = [100, 200, 300, 400]
2032 .iter()
2033 .map(|&size| MemoryPoolOptions {
2034 pool_type: PoolType::SlicedPages {
2035 page_size: size,
2036 max_slice_size: size,
2037 max_pool_size: None,
2038 },
2039 dealloc_period: None,
2040 })
2041 .collect();
2042 let mut memory_management = MemoryManagement::from_configuration(
2043 BytesStorage::default(),
2044 &MemoryDeviceProperties::new(DUMMY_MEM_PROPS.max_page_size, 10),
2045 MemoryConfiguration::Custom {
2046 pool_options: pools,
2047 },
2048 Arc::new(ServerLogger::default()),
2049 options(),
2050 );
2051 let alloc_sizes = [50, 150, 250, 350];
2053 let _handles =
2054 alloc_sizes.map(|s| memory_management.reserve(s, &mut ErrorGraph::default()));
2055 let usage = memory_management.memory_usage();
2056 assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
2058 }
2059
2060 #[test_log::test]
2061 fn capture_pins_reused_persistent_slice() {
2062 let mut memory_management = MemoryManagement::from_configuration(
2063 BytesStorage::default(),
2064 &DUMMY_MEM_PROPS,
2065 MemoryConfiguration::ExclusivePages,
2066 Arc::new(ServerLogger::default()),
2067 options(),
2068 );
2069
2070 memory_management.capture_begin();
2072 let first = memory_management
2073 .reserve(1024, &mut ErrorGraph::default())
2074 .unwrap();
2075 drop(first);
2076 drop(memory_management.capture_end());
2077
2078 memory_management.capture_begin();
2081 let second = memory_management
2082 .reserve(1024, &mut ErrorGraph::default())
2083 .unwrap();
2084 drop(second);
2085 let pins = memory_management.capture_end();
2086 assert_eq!(pins.len(), 1, "the reused slice must be retained");
2087
2088 let before = memory_management.memory_usage();
2090 let _other = memory_management
2091 .reserve(1024, &mut ErrorGraph::default())
2092 .unwrap();
2093 let after = memory_management.memory_usage();
2094 assert!(
2095 after.bytes_reserved > before.bytes_reserved,
2096 "a pinned slice was handed to a later allocation"
2097 );
2098 }
2099
2100 #[test_log::test]
2101 fn capture_pins_preexisting_slice_freed_and_reused_midwindow() {
2102 let mut memory_management = MemoryManagement::from_configuration(
2103 BytesStorage::default(),
2104 &DUMMY_MEM_PROPS,
2105 MemoryConfiguration::ExclusivePages,
2106 Arc::new(ServerLogger::default()),
2107 options(),
2108 );
2109
2110 memory_management.capture_begin();
2112 let live = memory_management
2113 .reserve(1024, &mut ErrorGraph::default())
2114 .unwrap();
2115 drop(memory_management.capture_end()); memory_management.capture_begin();
2123 drop(live);
2124 let reused = memory_management
2125 .reserve(1024, &mut ErrorGraph::default())
2126 .unwrap();
2127 drop(reused);
2128 let pins = memory_management.capture_end();
2129 assert_eq!(
2130 pins.len(),
2131 1,
2132 "a pre-existing slice freed and reused mid-window must be pinned"
2133 );
2134 }
2135
2136 #[test_log::test]
2137 fn capture_does_not_retain_untouched_free_slices() {
2138 let mut memory_management = MemoryManagement::from_configuration(
2139 BytesStorage::default(),
2140 &DUMMY_MEM_PROPS,
2141 MemoryConfiguration::ExclusivePages,
2142 Arc::new(ServerLogger::default()),
2143 options(),
2144 );
2145
2146 memory_management.capture_begin();
2148 let earlier = memory_management
2149 .reserve(1024, &mut ErrorGraph::default())
2150 .unwrap();
2151 drop(earlier);
2152 drop(memory_management.capture_end());
2153
2154 memory_management.capture_begin();
2158 let window = memory_management
2159 .reserve(2048, &mut ErrorGraph::default())
2160 .unwrap();
2161 drop(window);
2162 let pins = memory_management.capture_end();
2163 assert_eq!(
2164 pins.len(),
2165 1,
2166 "only the touched slice is retained, not the idle leftover"
2167 );
2168 }
2169
2170 #[test_log::test]
2171 fn capture_survives_explicit_cleanup() {
2172 let mut memory_management = MemoryManagement::from_configuration(
2173 BytesStorage::default(),
2174 &DUMMY_MEM_PROPS,
2175 MemoryConfiguration::ExclusivePages,
2176 Arc::new(ServerLogger::default()),
2177 options(),
2178 );
2179
2180 memory_management.capture_begin();
2181 let handle = memory_management
2182 .reserve(1024, &mut ErrorGraph::default())
2183 .unwrap();
2184 drop(handle);
2185 memory_management.cleanup(true, &mut ErrorGraph::default());
2188 let pins = memory_management.capture_end();
2189 assert_eq!(pins.len(), 1, "pin lost across an explicit cleanup");
2190 }
2191
2192 #[test_log::test]
2193 fn capture_begin_is_reentrant() {
2194 let mut memory_management = MemoryManagement::from_configuration(
2195 BytesStorage::default(),
2196 &DUMMY_MEM_PROPS,
2197 MemoryConfiguration::ExclusivePages,
2198 Arc::new(ServerLogger::default()),
2199 options(),
2200 );
2201
2202 memory_management.capture_begin();
2203 let first = memory_management
2204 .reserve(1024, &mut ErrorGraph::default())
2205 .unwrap();
2206 memory_management.capture_begin();
2209 let second = memory_management
2210 .reserve(2048, &mut ErrorGraph::default())
2211 .unwrap();
2212 drop(first);
2213 drop(second);
2214 let pins = memory_management.capture_end();
2215 assert_eq!(pins.len(), 2, "pins from before the re-entrant begin lost");
2216 assert!(
2217 memory_management.capture_end().is_empty(),
2218 "capture must be fully disarmed"
2219 );
2220 }
2221
2222 #[test_log::test]
2223 fn capture_leaves_preexisting_buffers_alone() {
2224 let mut memory_management = MemoryManagement::from_configuration(
2225 BytesStorage::default(),
2226 &DUMMY_MEM_PROPS,
2227 MemoryConfiguration::ExclusivePages,
2228 Arc::new(ServerLogger::default()),
2229 options(),
2230 );
2231
2232 memory_management.capture_begin();
2235 let preexisting = memory_management
2236 .reserve(1024, &mut ErrorGraph::default())
2237 .unwrap();
2238 drop(memory_management.capture_end());
2239
2240 memory_management.capture_begin();
2241 let window = memory_management
2242 .reserve(2048, &mut ErrorGraph::default())
2243 .unwrap();
2244 drop(window);
2245 let pins = memory_management.capture_end();
2246
2247 assert_eq!(
2250 pins.len(),
2251 1,
2252 "only the window's slice belongs to the graph"
2253 );
2254 assert!(
2255 preexisting.can_mut(),
2256 "a capture must not claim pre-existing live buffers"
2257 );
2258 }
2259
2260 #[test_log::test]
2277 fn capture_priming_leaves_the_working_set_not_the_peak() {
2278 let mut memory_management = MemoryManagement::from_configuration(
2279 BytesStorage::default(),
2280 &DUMMY_MEM_PROPS,
2281 MemoryConfiguration::ExclusivePages,
2282 Arc::new(ServerLogger::default()),
2283 options(),
2284 );
2285
2286 memory_management.capture_begin();
2290 for _ in 0..3 {
2291 let scratch = memory_management
2292 .reserve(1024, &mut ErrorGraph::default())
2293 .unwrap();
2294 drop(scratch);
2295 }
2296 memory_management.capture_priming_end();
2299 let after_warmup = memory_management.memory_usage();
2300
2301 let recorded: Vec<_> = (0..3)
2305 .map(|_| {
2306 memory_management
2307 .reserve(1024, &mut ErrorGraph::default())
2308 .unwrap()
2309 })
2310 .collect();
2311 let after_window = memory_management.memory_usage();
2312
2313 assert_eq!(
2314 after_window.bytes_reserved, after_warmup.bytes_reserved,
2315 "the capture window grew the pool: warmup left only its transient \
2316 peak, so the recorded run had to allocate — which a capture records \
2317 as a memory node and makes the graph un-relaunchable"
2318 );
2319
2320 drop(recorded);
2321 drop(memory_management.capture_end());
2322 }
2323
2324 #[test_log::test]
2328 fn capture_priming_holds_dropped_slices_until_priming_ends() {
2329 let mut memory_management = MemoryManagement::from_configuration(
2330 BytesStorage::default(),
2331 &DUMMY_MEM_PROPS,
2332 MemoryConfiguration::ExclusivePages,
2333 Arc::new(ServerLogger::default()),
2334 options(),
2335 );
2336
2337 memory_management.capture_begin();
2338 let first = memory_management
2339 .reserve(1024, &mut ErrorGraph::default())
2340 .unwrap();
2341 drop(first);
2342
2343 let before_second = memory_management.memory_usage();
2346 let second = memory_management
2347 .reserve(1024, &mut ErrorGraph::default())
2348 .unwrap();
2349 let after_second = memory_management.memory_usage();
2350 assert!(
2351 after_second.bytes_reserved > before_second.bytes_reserved,
2352 "priming must retain a dropped slice instead of recycling it"
2353 );
2354 drop(second);
2355
2356 memory_management.capture_priming_end();
2358 let before_reuse = memory_management.memory_usage();
2359 let reused = memory_management
2360 .reserve(1024, &mut ErrorGraph::default())
2361 .unwrap();
2362 let after_reuse = memory_management.memory_usage();
2363 assert_eq!(
2364 after_reuse.bytes_reserved, before_reuse.bytes_reserved,
2365 "capture_priming_end must release the retained slices for reuse"
2366 );
2367
2368 drop(reused);
2369 drop(memory_management.capture_end());
2370 }
2371
2372 #[test_log::test]
2375 fn capture_end_releases_primed_slices_when_priming_never_ended() {
2376 let mut memory_management = MemoryManagement::from_configuration(
2377 BytesStorage::default(),
2378 &DUMMY_MEM_PROPS,
2379 MemoryConfiguration::ExclusivePages,
2380 Arc::new(ServerLogger::default()),
2381 options(),
2382 );
2383
2384 memory_management.capture_begin();
2385 let scratch = memory_management
2386 .reserve(1024, &mut ErrorGraph::default())
2387 .unwrap();
2388 drop(scratch);
2389 assert!(
2391 memory_management.memory_usage().bytes_in_use > 0,
2392 "priming should still be retaining the dropped slice"
2393 );
2394
2395 drop(memory_management.capture_end());
2398 assert_eq!(
2399 memory_management.memory_usage().bytes_in_use,
2400 0,
2401 "primed slices outlived the capture"
2402 );
2403 }
2404
2405 #[test_log::test]
2406 fn noslice_allocate_deallocate_reallocate() {
2407 let mut memory_management = MemoryManagement::from_configuration(
2408 BytesStorage::default(),
2409 &MemoryDeviceProperties::new(128 * 1024 * 1024, 32),
2410 MemoryConfiguration::ExclusivePages,
2411 Arc::new(ServerLogger::default()),
2412 options(),
2413 );
2414 let handles: Vec<_> = (0..5)
2416 .map(|i| memory_management.reserve(1000 * (i + 1), &mut ErrorGraph::default()))
2417 .collect();
2418 let usage_before = memory_management.memory_usage();
2419 drop(handles);
2421 let _new_handles: Vec<_> = (0..5)
2423 .map(|i| memory_management.reserve(1000 * (i + 1), &mut ErrorGraph::default()))
2424 .collect();
2425 let usage_after = memory_management.memory_usage();
2426 assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
2427 assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
2428 assert_eq!(usage_before.bytes_reserved, usage_after.bytes_reserved);
2429 }
2430}