1use super::{
2 MemoryConfiguration, MemoryPoolOptions, MemoryUsage, PoolType,
3 memory_pool::{ExclusiveMemoryPool, MemoryPool, PersistentPool, SlicedPool},
4};
5use crate::{
6 config::{
7 CubeClRuntimeConfig, RuntimeConfig,
8 memory::{
9 MemoryLogLevel, MemoryPoolConfig, MemoryPoolsConfig, MemoryPoolsPreset,
10 PersistentMemory,
11 },
12 },
13 logging::ServerLogger,
14 memory_management::{BytesFormat, memory_pool::Slice},
15 server::IoError,
16 storage::{ComputeStorage, StorageHandle},
17};
18
19use alloc::format;
20use alloc::string::{String, ToString};
21#[cfg(not(exclusive_memory_only))]
22use alloc::vec;
23use alloc::vec::Vec;
24use cubecl_environment::backtrace::BackTrace;
25use cubecl_environment::collections::HashSet;
26use cubecl_environment::sync::Arc;
27use cubecl_ir::MemoryDeviceProperties;
28
29pub use super::memory_pool::{ManagedMemoryBinding, handle::*};
30
31#[allow(clippy::large_enum_variant)]
34enum DynamicPool {
35 Sliced(SlicedPool),
36 Exclusive(ExclusiveMemoryPool),
37}
38
39impl MemoryPool for DynamicPool {
40 fn accept(&self, size: u64) -> bool {
41 match self {
42 DynamicPool::Sliced(pool) => pool.accept(size),
43 DynamicPool::Exclusive(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 }
52 }
53
54 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
55 fn try_reserve(&mut self, size: u64) -> Option<ManagedMemoryHandle> {
56 match self {
57 DynamicPool::Sliced(m) => m.try_reserve(size),
58 DynamicPool::Exclusive(m) => m.try_reserve(size),
59 }
60 }
61
62 #[cfg_attr(
63 feature = "tracing",
64 tracing::instrument(level = "trace", skip(self, storage))
65 )]
66 fn alloc<Storage: ComputeStorage>(
67 &mut self,
68 storage: &mut Storage,
69 size: u64,
70 ) -> Result<ManagedMemoryHandle, IoError> {
71 match self {
72 DynamicPool::Sliced(m) => m.alloc(storage, size),
73 DynamicPool::Exclusive(m) => m.alloc(storage, size),
74 }
75 }
76
77 fn get_memory_usage(&self) -> MemoryUsage {
78 match self {
79 DynamicPool::Sliced(m) => m.get_memory_usage(),
80 DynamicPool::Exclusive(m) => m.get_memory_usage(),
81 }
82 }
83
84 fn cleanup<Storage: ComputeStorage>(
85 &mut self,
86 storage: &mut Storage,
87 alloc_nr: u64,
88 explicit: bool,
89 ) {
90 match self {
91 DynamicPool::Sliced(m) => m.cleanup(storage, alloc_nr, explicit),
92 DynamicPool::Exclusive(m) => m.cleanup(storage, alloc_nr, explicit),
93 };
94 storage.flush();
95 }
96
97 fn bind(
98 &mut self,
99 reserved: ManagedMemoryHandle,
100 assigned: ManagedMemoryHandle,
101 cursor: u64,
102 ) -> Result<(), IoError> {
103 match self {
104 DynamicPool::Sliced(m) => m.bind(reserved, assigned, cursor),
105 DynamicPool::Exclusive(m) => m.bind(reserved, assigned, cursor),
106 }
107 }
108}
109
110#[derive(Default, Clone, Copy, Debug)]
111pub enum MemoryAllocationMode {
113 #[default]
115 Auto,
116 Persistent,
119}
120
121pub struct MemoryManagement<Storage> {
123 name: String,
124 persistent: PersistentPool,
125 pools: Vec<DynamicPool>,
126 storage: Storage,
127 alloc_reserve_count: u64,
128 mode: MemoryAllocationMode,
129 config: PersistentMemory,
130 logger: Arc<ServerLogger>,
131 capture: Option<CaptureState>,
133}
134
135struct CaptureState {
140 restore_mode: MemoryAllocationMode,
144 touched: HashSet<ManagedMemoryId>,
150 priming: bool,
153 primed: Vec<ManagedMemoryHandle>,
164}
165
166fn generate_bucket_sizes(
167 start_size: u64,
168 end_size: u64,
169 max_buckets: usize,
170 alignment: u64,
171) -> Vec<u64> {
172 let mut buckets = Vec::with_capacity(max_buckets);
173 let log_min = (start_size as f64).ln();
174 let log_max = (end_size as f64).ln();
175 let log_range = log_max - log_min;
176
177 for i in 0..max_buckets {
179 let p = i as f64 / (max_buckets - 1) as f64;
180 let log_size = log_min + log_range * p;
182 let size = log_size.exp() as u64;
183 let aligned_size = size.next_multiple_of(alignment);
184 buckets.push(aligned_size);
185 }
186
187 buckets.dedup();
188 buckets
189}
190
191const DEALLOC_SCALE_MB: u64 = 1024 * 1024 * 1024;
192const BASE_DEALLOC_PERIOD: u64 = 5000;
193
194#[derive(Debug)]
196pub struct MemoryManagementOptions {
197 name: String,
199 memory: MemoryAllocationOption,
201}
202
203impl MemoryManagementOptions {
204 pub fn new<S: Into<String>>(name: S) -> Self {
206 Self {
207 name: name.into(),
208 memory: MemoryAllocationOption::FromConfig,
209 }
210 }
211
212 pub fn mode(mut self, mode: MemoryAllocationMode) -> Self {
214 self.memory = MemoryAllocationOption::Provided(mode);
215 self
216 }
217}
218
219#[derive(Default, Debug)]
220enum MemoryAllocationOption {
222 #[default]
223 FromConfig,
225 Provided(MemoryAllocationMode),
227}
228
229#[derive(Debug, Clone, PartialEq, Eq)]
231pub enum PoolConfigError {
232 EmptyPoolList,
234 ZeroSize {
236 field: &'static str,
238 },
239 SliceLargerThanPage {
241 page_size: u64,
243 max_slice_size: u64,
245 },
246 CapSmallerThanPage {
248 page_size: u64,
250 max_pool_size: u64,
252 },
253 TooManyPages {
255 pages: u64,
257 },
258 TooManyPools {
260 count: usize,
262 },
263 PresetUnavailable {
265 preset: &'static str,
267 },
268 SlicedPoolsUnavailable,
270}
271
272impl core::fmt::Display for PoolConfigError {
273 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
274 match self {
275 PoolConfigError::EmptyPoolList => write!(f, "the pool list is empty"),
276 PoolConfigError::ZeroSize { field } => write!(f, "`{field}` must be non-zero"),
277 PoolConfigError::SliceLargerThanPage {
278 page_size,
279 max_slice_size,
280 } => write!(
281 f,
282 "`max_slice_size` ({max_slice_size}) exceeds `page_size` ({page_size}); a slice can never span pages"
283 ),
284 PoolConfigError::CapSmallerThanPage {
285 page_size,
286 max_pool_size,
287 } => write!(
288 f,
289 "`max_pool_size` ({max_pool_size}) is smaller than `page_size` ({page_size}); the cap can't fit a single page"
290 ),
291 PoolConfigError::TooManyPages { pages } => write!(
292 f,
293 "`max_pool_size` spans {pages} pages of `page_size`, exceeding the maximum of {}; increase `page_size` or lower the cap",
294 u16::MAX
295 ),
296 PoolConfigError::TooManyPools { count } => write!(
297 f,
298 "the pool list has {count} entries, exceeding the maximum of {} dynamic pools",
299 PERSISTENT_POOL_POS - 1
300 ),
301 PoolConfigError::PresetUnavailable { preset } => {
302 write!(f, "the `{preset}` preset is not available in this build")
303 }
304 PoolConfigError::SlicedPoolsUnavailable => {
305 write!(
306 f,
307 "sliced pools are not available in this build (exclusive memory only)"
308 )
309 }
310 }
311 }
312}
313
314impl MemoryConfiguration {
315 pub fn resolve(
331 self,
332 pools: Option<&MemoryPoolsConfig>,
333 properties: &MemoryDeviceProperties,
334 ) -> Result<Self, PoolConfigError> {
335 let Some(pools) = pools else {
336 return Ok(self);
337 };
338
339 match pools {
340 MemoryPoolsConfig::Preset(MemoryPoolsPreset::SubSlices) => {
341 #[cfg(exclusive_memory_only)]
342 {
343 Err(PoolConfigError::PresetUnavailable {
344 preset: "sub-slices",
345 })
346 }
347 #[cfg(not(exclusive_memory_only))]
348 {
349 Ok(MemoryConfiguration::SubSlices)
350 }
351 }
352 MemoryPoolsConfig::Preset(MemoryPoolsPreset::ExclusivePages) => {
353 Ok(MemoryConfiguration::ExclusivePages)
354 }
355 MemoryPoolsConfig::Explicit(entries) => {
356 if entries.is_empty() {
357 return Err(PoolConfigError::EmptyPoolList);
358 }
359 if entries.len() >= PERSISTENT_POOL_POS as usize {
364 return Err(PoolConfigError::TooManyPools {
365 count: entries.len(),
366 });
367 }
368 let pool_options = entries
369 .iter()
370 .map(|entry| pool_options_from_entry(entry, properties))
371 .collect::<Result<Vec<_>, _>>()?;
372 Ok(MemoryConfiguration::Custom { pool_options })
373 }
374 }
375 }
376}
377
378fn pool_options_from_entry(
381 entry: &MemoryPoolConfig,
382 properties: &MemoryDeviceProperties,
383) -> Result<MemoryPoolOptions, PoolConfigError> {
384 let alignment = properties.alignment.max(1);
385 match entry {
386 MemoryPoolConfig::Exclusive {
387 max_alloc_size,
388 dealloc_period,
389 } => {
390 let max_alloc_size = max_alloc_size.bytes().next_multiple_of(alignment);
393 Ok(MemoryPoolOptions {
394 pool_type: PoolType::ExclusivePages { max_alloc_size },
395 dealloc_period: *dealloc_period,
396 })
397 }
398 #[cfg(exclusive_memory_only)]
403 MemoryPoolConfig::Sliced { .. } => Err(PoolConfigError::SlicedPoolsUnavailable),
404 #[cfg(not(exclusive_memory_only))]
405 MemoryPoolConfig::Sliced {
406 page_size,
407 max_slice_size,
408 max_pool_size,
409 dealloc_period,
410 } => {
411 if page_size.bytes() == 0 {
412 return Err(PoolConfigError::ZeroSize { field: "page_size" });
413 }
414
415 let page_size = page_size.bytes().next_multiple_of(alignment);
416 let max_slice_size = match max_slice_size {
417 Some(size) if size.bytes() == 0 => {
418 return Err(PoolConfigError::ZeroSize {
419 field: "max_slice_size",
420 });
421 }
422 Some(size) => size.bytes().next_multiple_of(alignment),
423 None => page_size,
424 };
425 if max_slice_size > page_size {
426 return Err(PoolConfigError::SliceLargerThanPage {
427 page_size,
428 max_slice_size,
429 });
430 }
431 if let Some(cap) = max_pool_size {
432 let cap = cap.bytes();
433 if cap == 0 {
434 return Err(PoolConfigError::ZeroSize {
435 field: "max_pool_size",
436 });
437 }
438 if cap < page_size {
439 return Err(PoolConfigError::CapSmallerThanPage {
440 page_size,
441 max_pool_size: cap,
442 });
443 }
444 let pages = cap / page_size;
445 if pages > u16::MAX as u64 {
446 return Err(PoolConfigError::TooManyPages { pages });
447 }
448 }
449
450 Ok(MemoryPoolOptions {
451 pool_type: PoolType::SlicedPages {
452 page_size,
453 max_slice_size,
454 max_pool_size: max_pool_size.map(|size| size.bytes()),
455 },
456 dealloc_period: *dealloc_period,
457 })
458 }
459 }
460}
461
462const PERSISTENT_POOL_POS: u8 = u8::MAX;
468
469fn build_pools(
473 properties: &MemoryDeviceProperties,
474 config: MemoryConfiguration,
475 logger: &Arc<ServerLogger>,
476 name: &str,
477) -> Vec<DynamicPool> {
478 let pool_options = match config {
479 #[cfg(not(exclusive_memory_only))]
480 MemoryConfiguration::SubSlices => {
481 let memory_alignment = properties.alignment;
483 let max_page = properties.max_page_size;
484 let mut pools = Vec::new();
485
486 const MB: u64 = 1024 * 1024;
487
488 pools.push(MemoryPoolOptions {
491 pool_type: PoolType::ExclusivePages { max_alloc_size: 0 },
492 dealloc_period: None,
493 });
494
495 let mut current = max_page;
496 let mut max_sizes = vec![];
497 let mut page_sizes = vec![];
498 let mut base = pools.len() as u32;
499
500 while current >= 32 * MB {
501 current /= 4;
502
503 current = current.next_multiple_of(memory_alignment);
505
506 max_sizes.push(current / 2u64.pow(base));
507 page_sizes.push(current);
508 base += 1;
509 }
510
511 max_sizes.reverse();
512 page_sizes.reverse();
513
514 for i in 0..max_sizes.len() {
515 let max = max_sizes[i];
516 let page_size = page_sizes[i];
517
518 pools.push(MemoryPoolOptions {
519 pool_type: PoolType::SlicedPages {
521 page_size,
522 max_slice_size: max,
523 max_pool_size: None,
524 },
525 dealloc_period: None,
526 });
527 }
528
529 pools.push(MemoryPoolOptions {
531 pool_type: PoolType::SlicedPages {
532 page_size: max_page / memory_alignment * memory_alignment,
533 max_slice_size: max_page / memory_alignment * memory_alignment,
534 max_pool_size: None,
535 },
536 dealloc_period: None,
537 });
538 pools
539 }
540 MemoryConfiguration::ExclusivePages => {
541 const MIN_BUCKET_SIZE: u64 = 1024 * 32;
545 const NUM_POOLS: usize = 24;
546
547 let sizes = generate_bucket_sizes(
548 MIN_BUCKET_SIZE,
549 properties.max_page_size,
550 NUM_POOLS,
551 properties.alignment,
552 );
553
554 sizes
555 .iter()
556 .map(|&size| {
557 let dealloc_period = (BASE_DEALLOC_PERIOD as f64
558 * (1.0 + size as f64 / (DEALLOC_SCALE_MB as f64)).round())
559 as u64;
560
561 MemoryPoolOptions {
562 pool_type: PoolType::ExclusivePages {
563 max_alloc_size: size,
564 },
565 dealloc_period: Some(dealloc_period),
566 }
567 })
568 .collect()
569 }
570 MemoryConfiguration::Custom { pool_options } => pool_options,
571 };
572
573 logger.log_memory(
574 |level| !matches!(level, MemoryLogLevel::Disabled),
575 || {
576 let mut msg = String::new();
577 for pool in pool_options.iter() {
578 msg += &format!("[{name}] Using memory pool: \n {pool:?}\n");
579 }
580 msg
581 },
582 );
583
584 assert!(
585 pool_options.len() < PERSISTENT_POOL_POS as usize,
586 "at most {} dynamic pools are supported",
587 PERSISTENT_POOL_POS
588 );
589
590 pool_options
591 .iter()
592 .enumerate()
593 .map(|(pool_pos, pool)| {
594 let pool_pos = pool_pos as u8;
595
596 match pool.pool_type {
597 PoolType::SlicedPages {
598 page_size,
599 max_slice_size,
600 max_pool_size,
601 } => DynamicPool::Sliced(SlicedPool::new(
602 page_size,
603 max_slice_size,
604 properties.alignment,
605 pool_pos,
606 max_pool_size,
607 )),
608 PoolType::ExclusivePages { max_alloc_size } => {
609 DynamicPool::Exclusive(ExclusiveMemoryPool::new(
610 max_alloc_size,
611 properties.alignment,
612 pool.dealloc_period.unwrap_or(u64::MAX),
613 pool_pos,
614 ))
615 }
616 }
617 })
618 .collect()
619}
620
621impl<Storage: ComputeStorage> MemoryManagement<Storage> {
622 pub fn from_configuration(
624 storage: Storage,
625 properties: &MemoryDeviceProperties,
626 config: MemoryConfiguration,
627 logger: Arc<ServerLogger>,
628 options: MemoryManagementOptions,
629 ) -> Self {
630 let pools = build_pools(properties, config, &logger, &options.name);
631
632 let config = CubeClRuntimeConfig::get().memory.persistent_memory.clone();
633
634 let mode = match options.memory {
635 MemoryAllocationOption::Provided(mode) => mode,
636 MemoryAllocationOption::FromConfig => match config {
637 PersistentMemory::Enabled | PersistentMemory::SizeMatch => {
638 MemoryAllocationMode::Auto
639 }
640 PersistentMemory::Disabled => MemoryAllocationMode::Auto,
641 PersistentMemory::Enforced => MemoryAllocationMode::Persistent,
642 },
643 };
644
645 Self {
646 name: options.name,
647 persistent: PersistentPool::new(
648 properties.max_page_size,
649 properties.alignment,
650 PERSISTENT_POOL_POS,
651 ),
652 pools,
653 storage,
654 alloc_reserve_count: 0,
655 mode,
656 config,
657 logger,
658 capture: None,
659 }
660 }
661
662 pub fn configure(
675 &mut self,
676 config: MemoryConfiguration,
677 properties: &MemoryDeviceProperties,
678 ) -> bool {
679 self.cleanup(true);
680
681 let dynamic_in_use: u64 = self
684 .pools
685 .iter()
686 .map(|pool| match pool {
687 DynamicPool::Sliced(p) => p.get_memory_usage().bytes_in_use,
688 DynamicPool::Exclusive(p) => p.get_memory_usage().bytes_in_use,
689 })
690 .sum();
691 if dynamic_in_use > 0 {
692 self.logger.log_memory(
693 |level| !matches!(level, MemoryLogLevel::Disabled),
694 || {
695 format!(
696 "[{}] Keeping the current pool layout: {dynamic_in_use} bytes \
697 are still live in the dynamic pools",
698 self.name
699 )
700 },
701 );
702 return false;
703 }
704
705 self.pools = build_pools(properties, config, &self.logger, &self.name);
706 true
707 }
708
709 pub fn capture_begin(&mut self) {
722 if self.capture.is_none() {
723 self.capture = Some(CaptureState {
724 restore_mode: self.mode,
725 touched: HashSet::new(),
726 priming: true,
727 primed: Vec::new(),
728 });
729 }
730 self.mode = MemoryAllocationMode::Persistent;
731 }
732
733 pub fn capture_priming_end(&mut self) {
740 if let Some(capture) = &mut self.capture {
741 capture.priming = false;
742 capture.primed.clear();
745 }
746 }
747
748 pub fn capture_end(&mut self) -> Vec<ManagedMemoryHandle> {
757 match self.capture.take() {
758 Some(capture) => {
759 self.mode = capture.restore_mode;
760 self.persistent.retain_touched(&capture.touched)
761 }
762 None => Vec::new(),
763 }
764 }
765
766 pub fn mode(&mut self, mode: MemoryAllocationMode) {
768 let mode = match self.config {
770 PersistentMemory::Enabled | PersistentMemory::SizeMatch => mode,
771 PersistentMemory::Disabled | PersistentMemory::Enforced => return,
772 };
773
774 self.logger.log_memory(
775 |level| !matches!(level, MemoryLogLevel::Disabled),
776 || {
777 format!(
778 "[{}] Setting memory allocation mode: from {:?} => {mode:?}",
779 self.name, self.mode
780 )
781 },
782 );
783
784 match &mut self.capture {
788 Some(capture) => capture.restore_mode = mode,
789 None => self.mode = mode,
790 }
791 }
792
793 pub fn cleanup(&mut self, explicit: bool) {
795 self.logger.log_memory(
796 |level| !matches!(level, MemoryLogLevel::Disabled) && explicit,
797 || "Manual memory cleanup ...".to_string(),
798 );
799
800 if self.capture.is_some() {
807 return;
808 }
809
810 self.persistent
811 .cleanup(&mut self.storage, self.alloc_reserve_count, explicit);
812
813 for pool in self.pools.iter_mut() {
814 pool.cleanup(&mut self.storage, self.alloc_reserve_count, explicit);
815 }
816
817 if explicit {
821 self.storage.flush();
822 }
823 }
824
825 pub fn get_cursor(&self, binding: ManagedMemoryBinding) -> Result<u64, IoError> {
827 let slice = self.find(binding)?;
828 Ok(slice.cursor)
829 }
830
831 fn find(&self, binding: ManagedMemoryBinding) -> Result<&Slice, IoError> {
833 let id = binding.descriptor();
834
835 if id.location().init == 0 {
836 return Err(IoError::NotFound {
837 backtrace: BackTrace::capture(),
838 reason: "Memory location was never initialized".into(),
839 });
840 }
841
842 let slice = if id.location().pool == PERSISTENT_POOL_POS {
843 self.persistent.find(&binding)?
844 } else {
845 let pool =
846 self.pools
847 .get(id.location().pool as usize)
848 .ok_or_else(|| IoError::NotFound {
849 backtrace: BackTrace::capture(),
850 reason: format!("Pool {} doesn't exist", id.location().pool).into(),
851 })?;
852
853 pool.find(&binding)?
854 };
855
856 if slice.handle.descriptor() != binding.descriptor() {
860 return Err(IoError::NotFound {
861 backtrace: BackTrace::capture(),
862 reason: "Memory location points to a different allocation".into(),
863 });
864 }
865
866 Ok(slice)
867 }
868
869 pub fn get_storage(&mut self, binding: ManagedMemoryBinding) -> Result<StorageHandle, IoError> {
871 let slice = self.find(binding)?;
872 Ok(slice.storage.clone())
873 }
874
875 pub fn get_resource(
877 &mut self,
878 binding: ManagedMemoryBinding,
879 offset_start: Option<u64>,
880 offset_end: Option<u64>,
881 ) -> Result<Storage::Resource, IoError> {
882 let handle = self.get_storage(binding)?;
883
884 let handle = match offset_start {
885 Some(offset) => handle.offset_start(offset),
886 None => handle,
887 };
888 let handle = match offset_end {
889 Some(offset) => handle.offset_end(offset),
890 None => handle,
891 };
892 Ok(self.storage().get(&handle))
893 }
894
895 fn capture_touch(&mut self, handle: &ManagedMemoryHandle) {
905 if let Some(capture) = &mut self.capture {
906 capture.touched.insert(handle.descriptor().id);
907 if capture.priming {
908 capture.primed.push(handle.clone());
911 }
912 }
913 }
914
915 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
917 pub fn reserve(&mut self, size: u64) -> Result<ManagedMemoryHandle, IoError> {
918 self.alloc_reserve_count += 1;
921
922 let persistent_mode = matches!(self.mode, MemoryAllocationMode::Persistent);
929 let size_match = matches!(self.config, PersistentMemory::SizeMatch);
930
931 if (persistent_mode || size_match)
932 && let Some(val) = self.persistent.try_reserve(size)
933 {
934 self.logger.log_memory(
935 |level| matches!(level, MemoryLogLevel::Full),
936 || {
937 format!(
938 "[{}] Reserved memory {size} using persistent memory",
939 self.name
940 )
941 },
942 );
943 self.capture_touch(&val);
944 return Ok(val);
945 }
946
947 if persistent_mode || (size_match && self.persistent.has_size(size)) {
948 let allocated = self.persistent.alloc(&mut self.storage, size);
949
950 self.logger.log_memory(
951 |level| !matches!(level, MemoryLogLevel::Disabled),
952 || {
953 format!(
954 "[{}] Allocated a new memory page using persistent memory, \n{}",
955 self.name, self,
956 )
957 },
958 );
959 if let Ok(handle) = &allocated {
960 self.capture_touch(handle);
961 }
962 return allocated;
963 }
964
965 self.logger.log_memory(
966 |level| matches!(level, MemoryLogLevel::Full),
967 || {
968 format!(
969 "[{}] Reserved memory {} using dynamic pool",
970 self.name,
971 BytesFormat::new(size)
972 )
973 },
974 );
975
976 let pool = self
978 .pools
979 .iter_mut()
980 .find(|p| p.accept(size))
981 .ok_or(IoError::BufferTooBig {
982 size,
983 backtrace: BackTrace::capture(),
984 })?;
985
986 if let Some(slice) = pool.try_reserve(size) {
987 return Ok(slice);
988 }
989
990 let allocated = pool.alloc(&mut self.storage, size);
991
992 self.logger.log_memory(
993 |level| matches!(level, MemoryLogLevel::Full),
994 || {
995 format!(
996 "[{}], Allocated a new memory page, current usage: \n{}",
997 self.name, self
998 )
999 },
1000 );
1001
1002 allocated
1003 }
1004
1005 pub fn storage(&mut self) -> &mut Storage {
1016 &mut self.storage
1017 }
1018
1019 pub fn memory_usage(&self) -> MemoryUsage {
1021 let memory_usage = self.pools.iter().map(|x| x.get_memory_usage()).fold(
1022 MemoryUsage {
1023 number_allocs: 0,
1024 bytes_in_use: 0,
1025 bytes_padding: 0,
1026 bytes_reserved: 0,
1027 },
1028 |m1, m2| m1.combine(m2),
1029 );
1030 memory_usage.combine(self.persistent.get_memory_usage())
1031 }
1032
1033 pub fn print_memory_usage(&self) {
1035 #[cfg(feature = "std")]
1036 log::info!("{}", self.memory_usage());
1037 }
1038
1039 pub fn bind(
1041 &mut self,
1042 reserved: ManagedMemoryHandle,
1043 assigned: ManagedMemoryHandle,
1044 cursor: u64,
1045 ) -> Result<(), IoError> {
1046 let descriptor = reserved.descriptor();
1047
1048 if descriptor.location().init == 0 {
1049 return Err(IoError::NotFound {
1050 backtrace: BackTrace::capture(),
1051 reason: "Reserved memory isn't initialized".into(),
1052 });
1053 }
1054
1055 let pool_index = descriptor.location().pool as usize;
1056 if pool_index == PERSISTENT_POOL_POS as usize {
1057 self.capture_touch(&assigned);
1061 return self.persistent.bind(reserved, assigned, cursor);
1062 }
1063
1064 self.pools
1065 .get_mut(pool_index)
1066 .map(|p| p.bind(reserved, assigned, cursor))
1067 .ok_or_else(|| IoError::NotFound {
1068 backtrace: BackTrace::capture(),
1069 reason: format!("Memory pool {} doesn't exist", pool_index).into(),
1070 })?
1071 }
1072}
1073
1074impl<Storage: ComputeStorage> core::fmt::Display for MemoryManagement<Storage> {
1075 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1076 f.write_str("\n# MemoryManagement\n\n")?;
1077 f.write_fmt(format_args!(" - name: {:?}\n", self.name))?;
1078 f.write_fmt(format_args!("\n## Persistent\n\n{}", self.persistent))?;
1079 f.write_str("\n## Dynamic\n\n")?;
1080
1081 for pool in self.pools.iter() {
1082 match pool {
1083 DynamicPool::Sliced(pool) => f.write_fmt(format_args!("{pool}\n"))?,
1084 DynamicPool::Exclusive(pool) => f.write_fmt(format_args!("{pool}\n"))?,
1085 }
1086 }
1087 let memory_usage = self.memory_usage();
1088 f.write_fmt(format_args!("\n## Summary\n\n{memory_usage}"))?;
1089
1090 Ok(())
1091 }
1092}
1093
1094impl<Storage> core::fmt::Debug for MemoryManagement<Storage> {
1095 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1096 f.write_str(
1097 alloc::format!(
1098 "DynamicMemoryManagement {:?}",
1099 core::any::type_name::<Storage>(),
1100 )
1101 .as_str(),
1102 )
1103 }
1104}
1105
1106#[cfg(test)]
1107mod tests {
1108 use super::*;
1109 use crate::{memory_management::MemoryManagement, storage::BytesStorage};
1110 use alloc::vec;
1111
1112 const DUMMY_MEM_PROPS: MemoryDeviceProperties = MemoryDeviceProperties {
1113 max_page_size: 128 * 1024 * 1024,
1114 alignment: 32,
1115 };
1116
1117 fn options() -> MemoryManagementOptions {
1118 MemoryManagementOptions {
1119 name: "test".into(),
1120 memory: MemoryAllocationOption::FromConfig,
1121 }
1122 }
1123
1124 #[test_log::test]
1126 #[cfg(not(exclusive_memory_only))]
1127 fn test_handle_mutability() {
1128 let mut memory_management = MemoryManagement::from_configuration(
1129 BytesStorage::default(),
1130 &DUMMY_MEM_PROPS,
1131 MemoryConfiguration::SubSlices,
1132 Arc::new(ServerLogger::default()),
1133 options(),
1134 );
1135 let handle = memory_management.reserve(10).unwrap();
1136 let other_ref = handle.clone();
1137 assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
1138 drop(other_ref);
1139 assert!(handle.can_mut(), "Handle should be mut when only one ref.");
1140 }
1141
1142 #[test_log::test]
1144 #[cfg(not(exclusive_memory_only))]
1145 fn test_memory_usage() {
1146 let max_page_size = 512;
1147
1148 let mut memory_management = MemoryManagement::from_configuration(
1149 BytesStorage::default(),
1150 &DUMMY_MEM_PROPS,
1151 MemoryConfiguration::Custom {
1152 pool_options: vec![MemoryPoolOptions {
1153 pool_type: PoolType::ExclusivePages {
1154 max_alloc_size: max_page_size,
1155 },
1156 dealloc_period: None,
1157 }],
1158 },
1159 Arc::new(ServerLogger::default()),
1160 options(),
1161 );
1162 let handle = memory_management.reserve(100);
1163 let usage = memory_management.memory_usage();
1164
1165 assert_eq!(usage.bytes_in_use, 100);
1166 assert!(usage.bytes_reserved >= 100 && usage.bytes_reserved <= max_page_size);
1167
1168 drop(handle);
1170 let _handle = memory_management.reserve(100);
1171 let usage_new = memory_management.memory_usage();
1172 assert_eq!(usage, usage_new);
1173 }
1174
1175 #[test_log::test]
1176 fn find_uninit_binding_returns_not_found() {
1177 let mut memory_management = MemoryManagement::from_configuration(
1178 BytesStorage::default(),
1179 &DUMMY_MEM_PROPS,
1180 MemoryConfiguration::Custom {
1181 pool_options: vec![MemoryPoolOptions {
1182 pool_type: PoolType::SlicedPages {
1183 page_size: 2048,
1184 max_slice_size: 2048,
1185 max_pool_size: None,
1186 },
1187 dealloc_period: None,
1188 }],
1189 },
1190 Arc::new(ServerLogger::default()),
1191 options(),
1192 );
1193
1194 let _live = memory_management.reserve(512).unwrap();
1197
1198 let binding = ManagedMemoryHandle::new().binding();
1199 assert!(matches!(
1200 memory_management.get_cursor(binding),
1201 Err(IoError::NotFound { .. })
1202 ));
1203 }
1204
1205 #[test_log::test]
1206 fn find_stale_descriptor_returns_not_found() {
1207 let mut memory_management = MemoryManagement::from_configuration(
1208 BytesStorage::default(),
1209 &DUMMY_MEM_PROPS,
1210 MemoryConfiguration::Custom {
1211 pool_options: vec![MemoryPoolOptions {
1212 pool_type: PoolType::SlicedPages {
1213 page_size: 2048,
1214 max_slice_size: 2048,
1215 max_pool_size: None,
1216 },
1217 dealloc_period: None,
1218 }],
1219 },
1220 Arc::new(ServerLogger::default()),
1221 options(),
1222 );
1223
1224 let reserved = memory_management.reserve(512).unwrap();
1225 let stale = reserved.clone();
1226 let assigned = ManagedMemoryHandle::new();
1227 let assigned_binding = assigned.clone().binding();
1228
1229 memory_management.bind(reserved, assigned, 0).unwrap();
1230
1231 assert!(matches!(
1234 memory_management.get_cursor(stale.binding()),
1235 Err(IoError::NotFound { .. })
1236 ));
1237 assert!(memory_management.get_cursor(assigned_binding).is_ok());
1238 }
1239
1240 #[test_log::test]
1241 fn held_binding_survives_explicit_cleanup_renumber() {
1242 let mut memory_management = MemoryManagement::from_configuration(
1243 BytesStorage::default(),
1244 &DUMMY_MEM_PROPS,
1245 MemoryConfiguration::Custom {
1246 pool_options: vec![MemoryPoolOptions {
1247 pool_type: PoolType::ExclusivePages {
1248 max_alloc_size: 1024,
1249 },
1250 dealloc_period: None,
1251 }],
1252 },
1253 Arc::new(ServerLogger::default()),
1254 options(),
1255 );
1256
1257 let handle_a = memory_management.reserve(1024).unwrap();
1258 let handle_b = memory_management.reserve(1024).unwrap();
1259 let handle_c = memory_management.reserve(1024).unwrap();
1260
1261 let binding_b = handle_b.binding();
1262 drop(handle_a);
1263 drop(handle_c);
1264
1265 memory_management.cleanup(true);
1267
1268 assert!(memory_management.get_cursor(binding_b.clone()).is_ok());
1269 assert!(memory_management.get_storage(binding_b).is_ok());
1270 assert_eq!(memory_management.memory_usage().bytes_reserved, 1024);
1271 }
1272
1273 fn capped_sliced_config(page_size: u64, max_pool_size: Option<u64>) -> MemoryConfiguration {
1274 MemoryConfiguration::Custom {
1275 pool_options: vec![MemoryPoolOptions {
1276 pool_type: PoolType::SlicedPages {
1277 page_size,
1278 max_slice_size: page_size,
1279 max_pool_size,
1280 },
1281 dealloc_period: None,
1282 }],
1283 }
1284 }
1285
1286 #[test_log::test]
1287 fn capped_sliced_pool_errors_instead_of_growing() {
1288 let mut memory_management = MemoryManagement::from_configuration(
1289 BytesStorage::default(),
1290 &DUMMY_MEM_PROPS,
1291 capped_sliced_config(1024, Some(2048)),
1292 Arc::new(ServerLogger::default()),
1293 options(),
1294 );
1295
1296 let _a = memory_management.reserve(1024).unwrap();
1297 let _b = memory_management.reserve(1024).unwrap();
1298
1299 let result = memory_management.reserve(1024);
1300 assert!(matches!(
1301 result,
1302 Err(IoError::PoolCapacityExceeded { capacity: 2048, .. })
1303 ));
1304 assert_eq!(
1305 memory_management.memory_usage().bytes_reserved,
1306 2048,
1307 "a failed reservation must not grow the pool"
1308 );
1309 }
1310
1311 #[test_log::test]
1312 fn capped_sliced_pool_reuses_freed_memory() {
1313 let mut memory_management = MemoryManagement::from_configuration(
1314 BytesStorage::default(),
1315 &DUMMY_MEM_PROPS,
1316 capped_sliced_config(1024, Some(2048)),
1317 Arc::new(ServerLogger::default()),
1318 options(),
1319 );
1320
1321 let handle_a = memory_management.reserve(1024).unwrap();
1322 let _b = memory_management.reserve(1024).unwrap();
1323 drop(handle_a);
1324
1325 let _c = memory_management.reserve(1024).unwrap();
1328 assert_eq!(memory_management.memory_usage().bytes_reserved, 2048);
1329 }
1330
1331 #[test_log::test]
1332 fn capped_lazy_pool_cleanup_still_frees() {
1333 let mut memory_management = MemoryManagement::from_configuration(
1334 BytesStorage::default(),
1335 &DUMMY_MEM_PROPS,
1336 capped_sliced_config(1024, Some(2048)),
1337 Arc::new(ServerLogger::default()),
1338 options(),
1339 );
1340
1341 let handle_a = memory_management.reserve(1024).unwrap();
1342 let handle_b = memory_management.reserve(1024).unwrap();
1343 drop(handle_a);
1344 drop(handle_b);
1345 memory_management.cleanup(true);
1346 assert_eq!(memory_management.memory_usage().bytes_reserved, 0);
1347
1348 let _a = memory_management.reserve(1024).unwrap();
1350 let _b = memory_management.reserve(1024).unwrap();
1351 assert!(matches!(
1352 memory_management.reserve(1024),
1353 Err(IoError::PoolCapacityExceeded { .. })
1354 ));
1355 }
1356
1357 #[test_log::test]
1358 fn max_pool_size_smaller_than_page_shrinks_page() {
1359 let mut memory_management = MemoryManagement::from_configuration(
1360 BytesStorage::default(),
1361 &DUMMY_MEM_PROPS,
1362 capped_sliced_config(2048, Some(512)),
1363 Arc::new(ServerLogger::default()),
1364 options(),
1365 );
1366
1367 let _small = memory_management.reserve(256).unwrap();
1368 assert!(memory_management.memory_usage().bytes_reserved <= 512);
1369
1370 assert!(memory_management.reserve(1024).is_err());
1372 assert!(memory_management.memory_usage().bytes_reserved <= 512);
1373 }
1374
1375 #[test_log::test]
1376 fn max_pool_size_below_alignment_never_overshoots() {
1377 let mut memory_management = MemoryManagement::from_configuration(
1381 BytesStorage::default(),
1382 &DUMMY_MEM_PROPS,
1383 capped_sliced_config(1024, Some(16)),
1384 Arc::new(ServerLogger::default()),
1385 options(),
1386 );
1387
1388 assert!(matches!(
1389 memory_management.reserve(8),
1390 Err(IoError::PoolCapacityExceeded { .. })
1391 ));
1392 assert_eq!(memory_management.memory_usage().bytes_reserved, 0);
1393 }
1394
1395 #[test_log::test]
1396 fn capacity_error_does_not_fall_through_to_later_pool() {
1397 let mut memory_management = MemoryManagement::from_configuration(
1398 BytesStorage::default(),
1399 &DUMMY_MEM_PROPS,
1400 MemoryConfiguration::Custom {
1401 pool_options: vec![
1402 MemoryPoolOptions {
1403 pool_type: PoolType::SlicedPages {
1404 page_size: 1024,
1405 max_slice_size: 1024,
1406 max_pool_size: Some(1024),
1407 },
1408 dealloc_period: None,
1409 },
1410 MemoryPoolOptions {
1411 pool_type: PoolType::SlicedPages {
1412 page_size: 1024,
1413 max_slice_size: 1024,
1414 max_pool_size: None,
1415 },
1416 dealloc_period: None,
1417 },
1418 ],
1419 },
1420 Arc::new(ServerLogger::default()),
1421 options(),
1422 );
1423
1424 let _fill = memory_management.reserve(1024).unwrap();
1425 assert!(matches!(
1426 memory_management.reserve(1024),
1427 Err(IoError::PoolCapacityExceeded { .. })
1428 ));
1429 assert_eq!(
1430 memory_management.memory_usage().bytes_reserved,
1431 1024,
1432 "the overflow must not silently land in the later pool"
1433 );
1434 }
1435
1436 #[test_log::test]
1437 fn alloc_two_chunks_on_one_page() {
1438 let page_size = 2048;
1439
1440 let mut memory_management = MemoryManagement::from_configuration(
1441 BytesStorage::default(),
1442 &DUMMY_MEM_PROPS,
1443 MemoryConfiguration::Custom {
1444 pool_options: vec![MemoryPoolOptions {
1445 pool_type: PoolType::SlicedPages {
1446 page_size,
1447 max_slice_size: page_size,
1448 max_pool_size: None,
1449 },
1450 dealloc_period: None,
1451 }],
1452 },
1453 Arc::new(ServerLogger::default()),
1454 options(),
1455 );
1456
1457 let alloc_size = 512;
1458 let _handle = memory_management.reserve(alloc_size);
1459 let _new_handle = memory_management.reserve(alloc_size);
1460
1461 let usage = memory_management.memory_usage();
1462 assert_eq!(usage.number_allocs, 2);
1463 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1464 assert_eq!(usage.bytes_reserved, page_size);
1465 }
1466
1467 #[test_log::test]
1468 fn alloc_reuses_storage() {
1469 let page_size = 512;
1471
1472 let mut memory_management = MemoryManagement::from_configuration(
1473 BytesStorage::default(),
1474 &DUMMY_MEM_PROPS,
1475 MemoryConfiguration::Custom {
1476 pool_options: vec![MemoryPoolOptions {
1477 pool_type: PoolType::SlicedPages {
1478 page_size,
1479 max_slice_size: page_size,
1480 max_pool_size: None,
1481 },
1482 dealloc_period: None,
1483 }],
1484 },
1485 Arc::new(ServerLogger::default()),
1486 options(),
1487 );
1488
1489 let alloc_size = 512;
1490 let _handle = memory_management.reserve(alloc_size);
1491 drop(_handle);
1492 let _new_handle = memory_management.reserve(alloc_size);
1493
1494 let usage = memory_management.memory_usage();
1495 assert_eq!(usage.number_allocs, 1);
1496 assert_eq!(usage.bytes_in_use, alloc_size);
1497 assert_eq!(usage.bytes_reserved, page_size);
1498 }
1499
1500 #[test_log::test]
1501 fn alloc_allocs_new_storage() {
1502 let page_size = 1024;
1503
1504 let mut memory_management = MemoryManagement::from_configuration(
1505 BytesStorage::default(),
1506 &DUMMY_MEM_PROPS,
1507 MemoryConfiguration::Custom {
1508 pool_options: vec![MemoryPoolOptions {
1509 pool_type: PoolType::SlicedPages {
1510 page_size,
1511 max_slice_size: page_size,
1512 max_pool_size: None,
1513 },
1514 dealloc_period: None,
1515 }],
1516 },
1517 Arc::new(ServerLogger::default()),
1518 options(),
1519 );
1520
1521 let alloc_size = 768;
1522 let _handle = memory_management.reserve(alloc_size);
1523 let _new_handle = memory_management.reserve(alloc_size);
1524
1525 let usage = memory_management.memory_usage();
1526 assert_eq!(usage.number_allocs, 2);
1527 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1528 assert_eq!(usage.bytes_reserved, page_size * 2);
1529 }
1530
1531 #[test_log::test]
1532 fn alloc_respects_alignment_size() {
1533 let page_size = 500;
1534 let mut memory_management = MemoryManagement::from_configuration(
1535 BytesStorage::default(),
1536 &MemoryDeviceProperties {
1537 max_page_size: page_size,
1538 alignment: 50,
1539 },
1540 MemoryConfiguration::Custom {
1541 pool_options: vec![MemoryPoolOptions {
1542 pool_type: PoolType::SlicedPages {
1543 page_size,
1544 max_slice_size: page_size,
1545 max_pool_size: None,
1546 },
1547 dealloc_period: None,
1548 }],
1549 },
1550 Arc::new(ServerLogger::default()),
1551 options(),
1552 );
1553 let alloc_size = 40;
1554 let _handle = memory_management.reserve(alloc_size);
1555 let _new_handle = memory_management.reserve(alloc_size);
1556 let usage = memory_management.memory_usage();
1557 assert_eq!(usage.bytes_padding, 10 * 2);
1559 }
1560
1561 #[test_log::test]
1562 fn allocs_on_correct_page() {
1563 let sizes = [100, 200, 300, 400];
1564
1565 let pools = sizes
1566 .iter()
1567 .map(|size| MemoryPoolOptions {
1568 pool_type: PoolType::SlicedPages {
1569 page_size: *size,
1570 max_slice_size: *size,
1571 max_pool_size: None,
1572 },
1573 dealloc_period: None,
1574 })
1575 .collect();
1576 let mut memory_management = MemoryManagement::from_configuration(
1577 BytesStorage::default(),
1578 &MemoryDeviceProperties {
1579 max_page_size: 128 * 1024 * 1024,
1580 alignment: 10,
1581 },
1582 MemoryConfiguration::Custom {
1583 pool_options: pools,
1584 },
1585 Arc::new(ServerLogger::default()),
1586 options(),
1587 );
1588 let alloc_sizes = [50, 150, 250, 350];
1590 let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
1591
1592 let usage = memory_management.memory_usage();
1593
1594 assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
1596 assert!(usage.bytes_reserved >= sizes.iter().sum::<u64>());
1597 }
1598
1599 #[test_log::test]
1600 fn resolve_absent_pools_config_keeps_runtime_choice() {
1601 #[cfg(not(exclusive_memory_only))]
1602 assert!(matches!(
1603 MemoryConfiguration::SubSlices
1604 .resolve(None, &DUMMY_MEM_PROPS)
1605 .unwrap(),
1606 MemoryConfiguration::SubSlices
1607 ));
1608 assert!(matches!(
1609 MemoryConfiguration::ExclusivePages
1610 .resolve(None, &DUMMY_MEM_PROPS)
1611 .unwrap(),
1612 MemoryConfiguration::ExclusivePages
1613 ));
1614 }
1615
1616 #[test_log::test]
1617 fn resolve_preset_overrides_runtime_choice() {
1618 let preset = MemoryPoolsConfig::Preset(MemoryPoolsPreset::ExclusivePages);
1619 #[cfg(not(exclusive_memory_only))]
1620 let base = MemoryConfiguration::SubSlices;
1621 #[cfg(exclusive_memory_only)]
1622 let base = MemoryConfiguration::ExclusivePages;
1623
1624 assert!(matches!(
1625 base.resolve(Some(&preset), &DUMMY_MEM_PROPS).unwrap(),
1626 MemoryConfiguration::ExclusivePages
1627 ));
1628 }
1629
1630 #[test_log::test]
1631 #[cfg(exclusive_memory_only)]
1632 fn resolve_rejects_sliced_pools_when_exclusive_only() {
1633 use crate::config::size::MemorySize;
1634
1635 let pools = MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1636 page_size: MemorySize(1024),
1637 max_slice_size: None,
1638 max_pool_size: None,
1639 dealloc_period: None,
1640 }]);
1641 assert_eq!(
1642 MemoryConfiguration::default()
1643 .resolve(Some(&pools), &DUMMY_MEM_PROPS)
1644 .unwrap_err(),
1645 PoolConfigError::SlicedPoolsUnavailable
1646 );
1647 }
1648
1649 #[test_log::test]
1650 #[cfg(not(exclusive_memory_only))]
1651 fn resolve_explicit_list_aligns_and_defaults() {
1652 use crate::config::size::MemorySize;
1653
1654 let pools = MemoryPoolsConfig::Explicit(vec![
1655 MemoryPoolConfig::Exclusive {
1656 max_alloc_size: MemorySize(8 * 1024),
1657 dealloc_period: Some(10000),
1658 },
1659 MemoryPoolConfig::Sliced {
1660 page_size: MemorySize(1000),
1662 max_slice_size: None,
1663 max_pool_size: Some(MemorySize(4096)),
1664 dealloc_period: None,
1665 },
1666 ]);
1667
1668 let resolved = MemoryConfiguration::default()
1669 .resolve(Some(&pools), &DUMMY_MEM_PROPS)
1670 .unwrap();
1671 let MemoryConfiguration::Custom { pool_options } = resolved else {
1672 panic!("expected a custom configuration");
1673 };
1674
1675 assert_eq!(pool_options.len(), 2);
1676 assert!(matches!(
1677 pool_options[0].pool_type,
1678 PoolType::ExclusivePages {
1679 max_alloc_size: 8192
1680 }
1681 ));
1682 assert_eq!(pool_options[0].dealloc_period, Some(10000));
1683 assert!(matches!(
1684 pool_options[1].pool_type,
1685 PoolType::SlicedPages {
1686 page_size: 1024,
1687 max_slice_size: 1024,
1689 max_pool_size: Some(4096),
1690 }
1691 ));
1692 }
1693
1694 #[test_log::test]
1695 #[cfg(not(exclusive_memory_only))]
1696 fn resolve_invalid_pool_configs_fail() {
1697 use crate::config::size::MemorySize;
1698
1699 let cases = [
1700 (
1701 MemoryPoolsConfig::Explicit(vec![]),
1702 PoolConfigError::EmptyPoolList,
1703 ),
1704 (
1705 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1706 page_size: MemorySize(0),
1707 max_slice_size: None,
1708 max_pool_size: None,
1709 dealloc_period: None,
1710 }]),
1711 PoolConfigError::ZeroSize { field: "page_size" },
1712 ),
1713 (
1714 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1715 page_size: MemorySize(1024),
1716 max_slice_size: Some(MemorySize(2048)),
1717 max_pool_size: None,
1718 dealloc_period: None,
1719 }]),
1720 PoolConfigError::SliceLargerThanPage {
1721 page_size: 1024,
1722 max_slice_size: 2048,
1723 },
1724 ),
1725 (
1726 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1727 page_size: MemorySize(2048),
1728 max_slice_size: None,
1729 max_pool_size: Some(MemorySize(1024)),
1730 dealloc_period: None,
1731 }]),
1732 PoolConfigError::CapSmallerThanPage {
1733 page_size: 2048,
1734 max_pool_size: 1024,
1735 },
1736 ),
1737 (
1738 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1739 page_size: MemorySize(1024),
1740 max_slice_size: None,
1741 max_pool_size: Some(MemorySize(64 * 1024 * 1024 * 1024)),
1743 dealloc_period: None,
1744 }]),
1745 PoolConfigError::TooManyPages {
1746 pages: 64 * 1024 * 1024,
1747 },
1748 ),
1749 (
1750 MemoryPoolsConfig::Explicit(vec![
1751 MemoryPoolConfig::Exclusive {
1752 max_alloc_size: MemorySize(1024),
1753 dealloc_period: None,
1754 };
1755 PERSISTENT_POOL_POS as usize
1756 ]),
1757 PoolConfigError::TooManyPools {
1758 count: PERSISTENT_POOL_POS as usize,
1759 },
1760 ),
1761 ];
1762
1763 for (pools, expected) in cases {
1764 let result = MemoryConfiguration::default().resolve(Some(&pools), &DUMMY_MEM_PROPS);
1765 assert_eq!(result.unwrap_err(), expected);
1766 }
1767 }
1768
1769 #[test_log::test]
1773 #[cfg(not(exclusive_memory_only))]
1774 fn resolved_single_arena_reuses_across_sizes() {
1775 use crate::config::size::MemorySize;
1776
1777 let page = 1024 * 1024; let pools = MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1779 page_size: MemorySize(page),
1780 max_slice_size: None,
1781 max_pool_size: None,
1782 dealloc_period: None,
1783 }]);
1784 let config = MemoryConfiguration::default()
1785 .resolve(Some(&pools), &DUMMY_MEM_PROPS)
1786 .unwrap();
1787
1788 let mut memory_management = MemoryManagement::from_configuration(
1789 BytesStorage::default(),
1790 &DUMMY_MEM_PROPS,
1791 config,
1792 Arc::new(ServerLogger::default()),
1793 options(),
1794 );
1795
1796 let small = memory_management.reserve(4 * 1024).unwrap();
1798 drop(small);
1799 let large = memory_management.reserve(512 * 1024).unwrap();
1801
1802 let usage = memory_management.memory_usage();
1803 assert_eq!(
1804 usage.bytes_reserved, page,
1805 "both sizes must share a single arena page"
1806 );
1807 assert_eq!(usage.number_allocs, 1);
1808 drop(large);
1809 }
1810
1811 #[test_log::test]
1812 #[cfg(not(exclusive_memory_only))]
1813 fn allocate_deallocate_reallocate() {
1814 let mut memory_management = MemoryManagement::from_configuration(
1815 BytesStorage::default(),
1816 &MemoryDeviceProperties {
1817 max_page_size: 128 * 1024 * 1024,
1818 alignment: 32,
1819 },
1820 MemoryConfiguration::SubSlices,
1821 Arc::new(ServerLogger::default()),
1822 options(),
1823 );
1824 let handles: Vec<_> = (0..5)
1826 .map(|i| memory_management.reserve(1000 * (i + 1)))
1827 .collect();
1828 let usage_before = memory_management.memory_usage();
1829 drop(handles);
1831 let _new_handles: Vec<_> = (0..5)
1833 .map(|i| memory_management.reserve(1000 * (i + 1)))
1834 .collect();
1835 let usage_after = memory_management.memory_usage();
1836 assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
1837 assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
1838 assert!(usage_before.bytes_reserved >= usage_after.bytes_reserved);
1840 }
1841
1842 #[test_log::test]
1843 #[cfg(not(exclusive_memory_only))]
1844 fn test_fragmentation_resistance() {
1845 let mut memory_management = MemoryManagement::from_configuration(
1846 BytesStorage::default(),
1847 &MemoryDeviceProperties {
1848 max_page_size: 128 * 1024 * 1024,
1849 alignment: 32,
1850 },
1851 MemoryConfiguration::SubSlices,
1852 Arc::new(ServerLogger::default()),
1853 options(),
1854 );
1855 let sizes = [50, 1000, 100, 5000, 200, 10000, 300];
1857 let handles: Vec<_> = sizes
1858 .iter()
1859 .map(|&size| memory_management.reserve(size).unwrap())
1860 .collect();
1861 let usage_before = memory_management.memory_usage();
1862 for i in (0..handles.len()).step_by(2) {
1864 drop(handles[i].clone());
1865 }
1866 for &size in &sizes[0..sizes.len() / 2] {
1868 memory_management.reserve(size).unwrap();
1869 }
1870 let usage_after = memory_management.memory_usage();
1871 assert!(usage_after.bytes_reserved <= (usage_before.bytes_reserved as f64 * 1.1) as u64);
1873 }
1874
1875 #[test_log::test]
1877 fn noslice_test_handle_mutability() {
1878 let mut memory_management = MemoryManagement::from_configuration(
1879 BytesStorage::default(),
1880 &(MemoryDeviceProperties {
1881 max_page_size: 128 * 1024 * 1024,
1882 alignment: 32,
1883 }),
1884 MemoryConfiguration::ExclusivePages,
1885 Arc::new(ServerLogger::default()),
1886 options(),
1887 );
1888 let handle = memory_management.reserve(10).unwrap();
1889 let other_ref = handle.clone();
1890 assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
1891 drop(other_ref);
1892 assert!(handle.can_mut(), "Handle should be mut when only one ref.");
1893 }
1894
1895 #[test_log::test]
1896 fn noslice_alloc_two_chunk() {
1897 let mut memory_management = MemoryManagement::from_configuration(
1898 BytesStorage::default(),
1899 &DUMMY_MEM_PROPS,
1900 MemoryConfiguration::Custom {
1901 pool_options: vec![MemoryPoolOptions {
1902 pool_type: PoolType::ExclusivePages {
1903 max_alloc_size: 1024,
1904 },
1905 dealloc_period: None,
1906 }],
1907 },
1908 Arc::new(ServerLogger::default()),
1909 options(),
1910 );
1911
1912 let alloc_size = 512;
1913 let _handle = memory_management.reserve(alloc_size);
1914 let _new_handle = memory_management.reserve(alloc_size);
1915
1916 let usage = memory_management.memory_usage();
1917 assert_eq!(usage.number_allocs, 2);
1918 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1919 assert!(usage.bytes_reserved >= alloc_size * 2);
1920 }
1921
1922 #[test_log::test]
1923 fn noslice_alloc_reuses_storage() {
1924 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);
1942 drop(_handle);
1943 let _new_handle = memory_management.reserve(alloc_size);
1944
1945 let usage = memory_management.memory_usage();
1946 assert_eq!(usage.number_allocs, 1);
1947 assert_eq!(usage.bytes_in_use, alloc_size);
1948 assert!(usage.bytes_reserved >= alloc_size);
1949 }
1950
1951 #[test_log::test]
1952 fn noslice_alloc_allocs_new_storage() {
1953 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 = 768;
1969 let _handle = memory_management.reserve(alloc_size);
1970 let _new_handle = memory_management.reserve(alloc_size);
1971 let usage = memory_management.memory_usage();
1972 assert_eq!(usage.number_allocs, 2);
1973 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1974 assert!(usage.bytes_reserved >= alloc_size * 2);
1975 }
1976
1977 #[test_log::test]
1978 fn noslice_alloc_respects_alignment_size() {
1979 let mut memory_management = MemoryManagement::from_configuration(
1980 BytesStorage::default(),
1981 &MemoryDeviceProperties {
1982 max_page_size: DUMMY_MEM_PROPS.max_page_size,
1983 alignment: 50,
1984 },
1985 MemoryConfiguration::Custom {
1986 pool_options: vec![MemoryPoolOptions {
1987 pool_type: PoolType::ExclusivePages {
1988 max_alloc_size: 50 * 20,
1989 },
1990 dealloc_period: None,
1991 }],
1992 },
1993 Arc::new(ServerLogger::default()),
1994 options(),
1995 );
1996 let alloc_size = 40;
1997 let _handle = memory_management.reserve(alloc_size);
1998 let _new_handle = memory_management.reserve(alloc_size);
1999 let usage = memory_management.memory_usage();
2000 assert_eq!(usage.bytes_padding, 10 * 2);
2002 }
2003
2004 #[test_log::test]
2005 fn noslice_allocs_on_correct_page() {
2006 let pools = [100, 200, 300, 400]
2007 .iter()
2008 .map(|&size| MemoryPoolOptions {
2009 pool_type: PoolType::SlicedPages {
2010 page_size: size,
2011 max_slice_size: size,
2012 max_pool_size: None,
2013 },
2014 dealloc_period: None,
2015 })
2016 .collect();
2017 let mut memory_management = MemoryManagement::from_configuration(
2018 BytesStorage::default(),
2019 &MemoryDeviceProperties {
2020 max_page_size: DUMMY_MEM_PROPS.max_page_size,
2021 alignment: 10,
2022 },
2023 MemoryConfiguration::Custom {
2024 pool_options: pools,
2025 },
2026 Arc::new(ServerLogger::default()),
2027 options(),
2028 );
2029 let alloc_sizes = [50, 150, 250, 350];
2031 let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
2032 let usage = memory_management.memory_usage();
2033 assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
2035 }
2036
2037 #[test_log::test]
2038 fn capture_pins_reused_persistent_slice() {
2039 let mut memory_management = MemoryManagement::from_configuration(
2040 BytesStorage::default(),
2041 &DUMMY_MEM_PROPS,
2042 MemoryConfiguration::ExclusivePages,
2043 Arc::new(ServerLogger::default()),
2044 options(),
2045 );
2046
2047 memory_management.capture_begin();
2049 let first = memory_management.reserve(1024).unwrap();
2050 drop(first);
2051 drop(memory_management.capture_end());
2052
2053 memory_management.capture_begin();
2056 let second = memory_management.reserve(1024).unwrap();
2057 drop(second);
2058 let pins = memory_management.capture_end();
2059 assert_eq!(pins.len(), 1, "the reused slice must be retained");
2060
2061 let before = memory_management.memory_usage();
2063 let _other = memory_management.reserve(1024).unwrap();
2064 let after = memory_management.memory_usage();
2065 assert!(
2066 after.bytes_reserved > before.bytes_reserved,
2067 "a pinned slice was handed to a later allocation"
2068 );
2069 }
2070
2071 #[test_log::test]
2072 fn capture_pins_preexisting_slice_freed_and_reused_midwindow() {
2073 let mut memory_management = MemoryManagement::from_configuration(
2074 BytesStorage::default(),
2075 &DUMMY_MEM_PROPS,
2076 MemoryConfiguration::ExclusivePages,
2077 Arc::new(ServerLogger::default()),
2078 options(),
2079 );
2080
2081 memory_management.capture_begin();
2083 let live = memory_management.reserve(1024).unwrap();
2084 drop(memory_management.capture_end()); memory_management.capture_begin();
2092 drop(live);
2093 let reused = memory_management.reserve(1024).unwrap();
2094 drop(reused);
2095 let pins = memory_management.capture_end();
2096 assert_eq!(
2097 pins.len(),
2098 1,
2099 "a pre-existing slice freed and reused mid-window must be pinned"
2100 );
2101 }
2102
2103 #[test_log::test]
2104 fn capture_does_not_retain_untouched_free_slices() {
2105 let mut memory_management = MemoryManagement::from_configuration(
2106 BytesStorage::default(),
2107 &DUMMY_MEM_PROPS,
2108 MemoryConfiguration::ExclusivePages,
2109 Arc::new(ServerLogger::default()),
2110 options(),
2111 );
2112
2113 memory_management.capture_begin();
2115 let earlier = memory_management.reserve(1024).unwrap();
2116 drop(earlier);
2117 drop(memory_management.capture_end());
2118
2119 memory_management.capture_begin();
2123 let window = memory_management.reserve(2048).unwrap();
2124 drop(window);
2125 let pins = memory_management.capture_end();
2126 assert_eq!(
2127 pins.len(),
2128 1,
2129 "only the touched slice is retained, not the idle leftover"
2130 );
2131 }
2132
2133 #[test_log::test]
2134 fn capture_survives_explicit_cleanup() {
2135 let mut memory_management = MemoryManagement::from_configuration(
2136 BytesStorage::default(),
2137 &DUMMY_MEM_PROPS,
2138 MemoryConfiguration::ExclusivePages,
2139 Arc::new(ServerLogger::default()),
2140 options(),
2141 );
2142
2143 memory_management.capture_begin();
2144 let handle = memory_management.reserve(1024).unwrap();
2145 drop(handle);
2146 memory_management.cleanup(true);
2149 let pins = memory_management.capture_end();
2150 assert_eq!(pins.len(), 1, "pin lost across an explicit cleanup");
2151 }
2152
2153 #[test_log::test]
2154 fn capture_begin_is_reentrant() {
2155 let mut memory_management = MemoryManagement::from_configuration(
2156 BytesStorage::default(),
2157 &DUMMY_MEM_PROPS,
2158 MemoryConfiguration::ExclusivePages,
2159 Arc::new(ServerLogger::default()),
2160 options(),
2161 );
2162
2163 memory_management.capture_begin();
2164 let first = memory_management.reserve(1024).unwrap();
2165 memory_management.capture_begin();
2168 let second = memory_management.reserve(2048).unwrap();
2169 drop(first);
2170 drop(second);
2171 let pins = memory_management.capture_end();
2172 assert_eq!(pins.len(), 2, "pins from before the re-entrant begin lost");
2173 assert!(
2174 memory_management.capture_end().is_empty(),
2175 "capture must be fully disarmed"
2176 );
2177 }
2178
2179 #[test_log::test]
2180 fn capture_leaves_preexisting_buffers_alone() {
2181 let mut memory_management = MemoryManagement::from_configuration(
2182 BytesStorage::default(),
2183 &DUMMY_MEM_PROPS,
2184 MemoryConfiguration::ExclusivePages,
2185 Arc::new(ServerLogger::default()),
2186 options(),
2187 );
2188
2189 memory_management.capture_begin();
2192 let preexisting = memory_management.reserve(1024).unwrap();
2193 drop(memory_management.capture_end());
2194
2195 memory_management.capture_begin();
2196 let window = memory_management.reserve(2048).unwrap();
2197 drop(window);
2198 let pins = memory_management.capture_end();
2199
2200 assert_eq!(
2203 pins.len(),
2204 1,
2205 "only the window's slice belongs to the graph"
2206 );
2207 assert!(
2208 preexisting.can_mut(),
2209 "a capture must not claim pre-existing live buffers"
2210 );
2211 }
2212
2213 #[test_log::test]
2230 fn capture_priming_leaves_the_working_set_not_the_peak() {
2231 let mut memory_management = MemoryManagement::from_configuration(
2232 BytesStorage::default(),
2233 &DUMMY_MEM_PROPS,
2234 MemoryConfiguration::ExclusivePages,
2235 Arc::new(ServerLogger::default()),
2236 options(),
2237 );
2238
2239 memory_management.capture_begin();
2243 for _ in 0..3 {
2244 let scratch = memory_management.reserve(1024).unwrap();
2245 drop(scratch);
2246 }
2247 memory_management.capture_priming_end();
2250 let after_warmup = memory_management.memory_usage();
2251
2252 let recorded: Vec<_> = (0..3)
2256 .map(|_| memory_management.reserve(1024).unwrap())
2257 .collect();
2258 let after_window = memory_management.memory_usage();
2259
2260 assert_eq!(
2261 after_window.bytes_reserved, after_warmup.bytes_reserved,
2262 "the capture window grew the pool: warmup left only its transient \
2263 peak, so the recorded run had to allocate — which a capture records \
2264 as a memory node and makes the graph un-relaunchable"
2265 );
2266
2267 drop(recorded);
2268 drop(memory_management.capture_end());
2269 }
2270
2271 #[test_log::test]
2275 fn capture_priming_holds_dropped_slices_until_priming_ends() {
2276 let mut memory_management = MemoryManagement::from_configuration(
2277 BytesStorage::default(),
2278 &DUMMY_MEM_PROPS,
2279 MemoryConfiguration::ExclusivePages,
2280 Arc::new(ServerLogger::default()),
2281 options(),
2282 );
2283
2284 memory_management.capture_begin();
2285 let first = memory_management.reserve(1024).unwrap();
2286 drop(first);
2287
2288 let before_second = memory_management.memory_usage();
2291 let second = memory_management.reserve(1024).unwrap();
2292 let after_second = memory_management.memory_usage();
2293 assert!(
2294 after_second.bytes_reserved > before_second.bytes_reserved,
2295 "priming must retain a dropped slice instead of recycling it"
2296 );
2297 drop(second);
2298
2299 memory_management.capture_priming_end();
2301 let before_reuse = memory_management.memory_usage();
2302 let reused = memory_management.reserve(1024).unwrap();
2303 let after_reuse = memory_management.memory_usage();
2304 assert_eq!(
2305 after_reuse.bytes_reserved, before_reuse.bytes_reserved,
2306 "capture_priming_end must release the retained slices for reuse"
2307 );
2308
2309 drop(reused);
2310 drop(memory_management.capture_end());
2311 }
2312
2313 #[test_log::test]
2316 fn capture_end_releases_primed_slices_when_priming_never_ended() {
2317 let mut memory_management = MemoryManagement::from_configuration(
2318 BytesStorage::default(),
2319 &DUMMY_MEM_PROPS,
2320 MemoryConfiguration::ExclusivePages,
2321 Arc::new(ServerLogger::default()),
2322 options(),
2323 );
2324
2325 memory_management.capture_begin();
2326 let scratch = memory_management.reserve(1024).unwrap();
2327 drop(scratch);
2328 assert!(
2330 memory_management.memory_usage().bytes_in_use > 0,
2331 "priming should still be retaining the dropped slice"
2332 );
2333
2334 drop(memory_management.capture_end());
2337 assert_eq!(
2338 memory_management.memory_usage().bytes_in_use,
2339 0,
2340 "primed slices outlived the capture"
2341 );
2342 }
2343
2344 #[test_log::test]
2345 fn noslice_allocate_deallocate_reallocate() {
2346 let mut memory_management = MemoryManagement::from_configuration(
2347 BytesStorage::default(),
2348 &MemoryDeviceProperties {
2349 max_page_size: 128 * 1024 * 1024,
2350 alignment: 32,
2351 },
2352 MemoryConfiguration::ExclusivePages,
2353 Arc::new(ServerLogger::default()),
2354 options(),
2355 );
2356 let handles: Vec<_> = (0..5)
2358 .map(|i| memory_management.reserve(1000 * (i + 1)))
2359 .collect();
2360 let usage_before = memory_management.memory_usage();
2361 drop(handles);
2363 let _new_handles: Vec<_> = (0..5)
2365 .map(|i| memory_management.reserve(1000 * (i + 1)))
2366 .collect();
2367 let usage_after = memory_management.memory_usage();
2368 assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
2369 assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
2370 assert_eq!(usage_before.bytes_reserved, usage_after.bytes_reserved);
2371 }
2372}