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_common::{backtrace::BackTrace, stub::Arc};
25use cubecl_ir::MemoryDeviceProperties;
26use hashbrown::HashSet;
27
28pub use super::memory_pool::{ManagedMemoryBinding, handle::*};
29
30#[allow(clippy::large_enum_variant)]
33enum DynamicPool {
34 Sliced(SlicedPool),
35 Exclusive(ExclusiveMemoryPool),
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 }
44 }
45
46 fn find(&self, binding: &ManagedMemoryBinding) -> Result<&Slice, IoError> {
47 match self {
48 DynamicPool::Sliced(m) => m.find(binding),
49 DynamicPool::Exclusive(m) => m.find(binding),
50 }
51 }
52
53 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
54 fn try_reserve(&mut self, size: u64) -> Option<ManagedMemoryHandle> {
55 match self {
56 DynamicPool::Sliced(m) => m.try_reserve(size),
57 DynamicPool::Exclusive(m) => m.try_reserve(size),
58 }
59 }
60
61 #[cfg_attr(
62 feature = "tracing",
63 tracing::instrument(level = "trace", skip(self, storage))
64 )]
65 fn alloc<Storage: ComputeStorage>(
66 &mut self,
67 storage: &mut Storage,
68 size: u64,
69 ) -> Result<ManagedMemoryHandle, IoError> {
70 match self {
71 DynamicPool::Sliced(m) => m.alloc(storage, size),
72 DynamicPool::Exclusive(m) => m.alloc(storage, size),
73 }
74 }
75
76 fn get_memory_usage(&self) -> MemoryUsage {
77 match self {
78 DynamicPool::Sliced(m) => m.get_memory_usage(),
79 DynamicPool::Exclusive(m) => m.get_memory_usage(),
80 }
81 }
82
83 fn cleanup<Storage: ComputeStorage>(
84 &mut self,
85 storage: &mut Storage,
86 alloc_nr: u64,
87 explicit: bool,
88 ) {
89 match self {
90 DynamicPool::Sliced(m) => m.cleanup(storage, alloc_nr, explicit),
91 DynamicPool::Exclusive(m) => m.cleanup(storage, alloc_nr, explicit),
92 };
93 storage.flush();
94 }
95
96 fn bind(
97 &mut self,
98 reserved: ManagedMemoryHandle,
99 assigned: ManagedMemoryHandle,
100 cursor: u64,
101 ) -> Result<(), IoError> {
102 match self {
103 DynamicPool::Sliced(m) => m.bind(reserved, assigned, cursor),
104 DynamicPool::Exclusive(m) => m.bind(reserved, assigned, cursor),
105 }
106 }
107}
108
109#[derive(Default, Clone, Copy, Debug)]
110pub enum MemoryAllocationMode {
112 #[default]
114 Auto,
115 Persistent,
118}
119
120pub struct MemoryManagement<Storage> {
122 name: String,
123 persistent: PersistentPool,
124 pools: Vec<DynamicPool>,
125 storage: Storage,
126 alloc_reserve_count: u64,
127 mode: MemoryAllocationMode,
128 config: PersistentMemory,
129 logger: Arc<ServerLogger>,
130 capture: Option<CaptureState>,
132}
133
134struct CaptureState {
139 restore_mode: MemoryAllocationMode,
143 touched: HashSet<ManagedMemoryId>,
149}
150
151fn generate_bucket_sizes(
152 start_size: u64,
153 end_size: u64,
154 max_buckets: usize,
155 alignment: u64,
156) -> Vec<u64> {
157 let mut buckets = Vec::with_capacity(max_buckets);
158 let log_min = (start_size as f64).ln();
159 let log_max = (end_size as f64).ln();
160 let log_range = log_max - log_min;
161
162 for i in 0..max_buckets {
164 let p = i as f64 / (max_buckets - 1) as f64;
165 let log_size = log_min + log_range * p;
167 let size = log_size.exp() as u64;
168 let aligned_size = size.next_multiple_of(alignment);
169 buckets.push(aligned_size);
170 }
171
172 buckets.dedup();
173 buckets
174}
175
176const DEALLOC_SCALE_MB: u64 = 1024 * 1024 * 1024;
177const BASE_DEALLOC_PERIOD: u64 = 5000;
178
179#[derive(Debug)]
181pub struct MemoryManagementOptions {
182 name: String,
184 memory: MemoryAllocationOption,
186}
187
188impl MemoryManagementOptions {
189 pub fn new<S: Into<String>>(name: S) -> Self {
191 Self {
192 name: name.into(),
193 memory: MemoryAllocationOption::FromConfig,
194 }
195 }
196
197 pub fn mode(mut self, mode: MemoryAllocationMode) -> Self {
199 self.memory = MemoryAllocationOption::Provided(mode);
200 self
201 }
202}
203
204#[derive(Default, Debug)]
205enum MemoryAllocationOption {
207 #[default]
208 FromConfig,
210 Provided(MemoryAllocationMode),
212}
213
214#[derive(Debug, Clone, PartialEq, Eq)]
216pub enum PoolConfigError {
217 EmptyPoolList,
219 ZeroSize {
221 field: &'static str,
223 },
224 SliceLargerThanPage {
226 page_size: u64,
228 max_slice_size: u64,
230 },
231 CapSmallerThanPage {
233 page_size: u64,
235 max_pool_size: u64,
237 },
238 TooManyPages {
240 pages: u64,
242 },
243 TooManyPools {
245 count: usize,
247 },
248 PresetUnavailable {
250 preset: &'static str,
252 },
253 SlicedPoolsUnavailable,
255}
256
257impl core::fmt::Display for PoolConfigError {
258 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
259 match self {
260 PoolConfigError::EmptyPoolList => write!(f, "the pool list is empty"),
261 PoolConfigError::ZeroSize { field } => write!(f, "`{field}` must be non-zero"),
262 PoolConfigError::SliceLargerThanPage {
263 page_size,
264 max_slice_size,
265 } => write!(
266 f,
267 "`max_slice_size` ({max_slice_size}) exceeds `page_size` ({page_size}); a slice can never span pages"
268 ),
269 PoolConfigError::CapSmallerThanPage {
270 page_size,
271 max_pool_size,
272 } => write!(
273 f,
274 "`max_pool_size` ({max_pool_size}) is smaller than `page_size` ({page_size}); the cap can't fit a single page"
275 ),
276 PoolConfigError::TooManyPages { pages } => write!(
277 f,
278 "`max_pool_size` spans {pages} pages of `page_size`, exceeding the maximum of {}; increase `page_size` or lower the cap",
279 u16::MAX
280 ),
281 PoolConfigError::TooManyPools { count } => write!(
282 f,
283 "the pool list has {count} entries, exceeding the maximum of {} dynamic pools",
284 PERSISTENT_POOL_POS - 1
285 ),
286 PoolConfigError::PresetUnavailable { preset } => {
287 write!(f, "the `{preset}` preset is not available in this build")
288 }
289 PoolConfigError::SlicedPoolsUnavailable => {
290 write!(
291 f,
292 "sliced pools are not available in this build (exclusive memory only)"
293 )
294 }
295 }
296 }
297}
298
299impl MemoryConfiguration {
300 pub fn resolve(
316 self,
317 pools: Option<&MemoryPoolsConfig>,
318 properties: &MemoryDeviceProperties,
319 ) -> Result<Self, PoolConfigError> {
320 let Some(pools) = pools else {
321 return Ok(self);
322 };
323
324 match pools {
325 MemoryPoolsConfig::Preset(MemoryPoolsPreset::SubSlices) => {
326 #[cfg(exclusive_memory_only)]
327 {
328 Err(PoolConfigError::PresetUnavailable {
329 preset: "sub-slices",
330 })
331 }
332 #[cfg(not(exclusive_memory_only))]
333 {
334 Ok(MemoryConfiguration::SubSlices)
335 }
336 }
337 MemoryPoolsConfig::Preset(MemoryPoolsPreset::ExclusivePages) => {
338 Ok(MemoryConfiguration::ExclusivePages)
339 }
340 MemoryPoolsConfig::Explicit(entries) => {
341 if entries.is_empty() {
342 return Err(PoolConfigError::EmptyPoolList);
343 }
344 if entries.len() >= PERSISTENT_POOL_POS as usize {
349 return Err(PoolConfigError::TooManyPools {
350 count: entries.len(),
351 });
352 }
353 let pool_options = entries
354 .iter()
355 .map(|entry| pool_options_from_entry(entry, properties))
356 .collect::<Result<Vec<_>, _>>()?;
357 Ok(MemoryConfiguration::Custom { pool_options })
358 }
359 }
360 }
361}
362
363fn pool_options_from_entry(
366 entry: &MemoryPoolConfig,
367 properties: &MemoryDeviceProperties,
368) -> Result<MemoryPoolOptions, PoolConfigError> {
369 let alignment = properties.alignment.max(1);
370 match entry {
371 MemoryPoolConfig::Exclusive {
372 max_alloc_size,
373 dealloc_period,
374 } => {
375 let max_alloc_size = max_alloc_size.bytes().next_multiple_of(alignment);
378 Ok(MemoryPoolOptions {
379 pool_type: PoolType::ExclusivePages { max_alloc_size },
380 dealloc_period: *dealloc_period,
381 })
382 }
383 #[cfg(exclusive_memory_only)]
388 MemoryPoolConfig::Sliced { .. } => Err(PoolConfigError::SlicedPoolsUnavailable),
389 #[cfg(not(exclusive_memory_only))]
390 MemoryPoolConfig::Sliced {
391 page_size,
392 max_slice_size,
393 max_pool_size,
394 dealloc_period,
395 } => {
396 if page_size.bytes() == 0 {
397 return Err(PoolConfigError::ZeroSize { field: "page_size" });
398 }
399
400 let page_size = page_size.bytes().next_multiple_of(alignment);
401 let max_slice_size = match max_slice_size {
402 Some(size) if size.bytes() == 0 => {
403 return Err(PoolConfigError::ZeroSize {
404 field: "max_slice_size",
405 });
406 }
407 Some(size) => size.bytes().next_multiple_of(alignment),
408 None => page_size,
409 };
410 if max_slice_size > page_size {
411 return Err(PoolConfigError::SliceLargerThanPage {
412 page_size,
413 max_slice_size,
414 });
415 }
416 if let Some(cap) = max_pool_size {
417 let cap = cap.bytes();
418 if cap == 0 {
419 return Err(PoolConfigError::ZeroSize {
420 field: "max_pool_size",
421 });
422 }
423 if cap < page_size {
424 return Err(PoolConfigError::CapSmallerThanPage {
425 page_size,
426 max_pool_size: cap,
427 });
428 }
429 let pages = cap / page_size;
430 if pages > u16::MAX as u64 {
431 return Err(PoolConfigError::TooManyPages { pages });
432 }
433 }
434
435 Ok(MemoryPoolOptions {
436 pool_type: PoolType::SlicedPages {
437 page_size,
438 max_slice_size,
439 max_pool_size: max_pool_size.map(|size| size.bytes()),
440 },
441 dealloc_period: *dealloc_period,
442 })
443 }
444 }
445}
446
447const PERSISTENT_POOL_POS: u8 = u8::MAX;
453
454fn build_pools(
458 properties: &MemoryDeviceProperties,
459 config: MemoryConfiguration,
460 logger: &Arc<ServerLogger>,
461 name: &str,
462) -> Vec<DynamicPool> {
463 let pool_options = match config {
464 #[cfg(not(exclusive_memory_only))]
465 MemoryConfiguration::SubSlices => {
466 let memory_alignment = properties.alignment;
468 let max_page = properties.max_page_size;
469 let mut pools = Vec::new();
470
471 const MB: u64 = 1024 * 1024;
472
473 pools.push(MemoryPoolOptions {
476 pool_type: PoolType::ExclusivePages { max_alloc_size: 0 },
477 dealloc_period: None,
478 });
479
480 let mut current = max_page;
481 let mut max_sizes = vec![];
482 let mut page_sizes = vec![];
483 let mut base = pools.len() as u32;
484
485 while current >= 32 * MB {
486 current /= 4;
487
488 current = current.next_multiple_of(memory_alignment);
490
491 max_sizes.push(current / 2u64.pow(base));
492 page_sizes.push(current);
493 base += 1;
494 }
495
496 max_sizes.reverse();
497 page_sizes.reverse();
498
499 for i in 0..max_sizes.len() {
500 let max = max_sizes[i];
501 let page_size = page_sizes[i];
502
503 pools.push(MemoryPoolOptions {
504 pool_type: PoolType::SlicedPages {
506 page_size,
507 max_slice_size: max,
508 max_pool_size: None,
509 },
510 dealloc_period: None,
511 });
512 }
513
514 pools.push(MemoryPoolOptions {
516 pool_type: PoolType::SlicedPages {
517 page_size: max_page / memory_alignment * memory_alignment,
518 max_slice_size: max_page / memory_alignment * memory_alignment,
519 max_pool_size: None,
520 },
521 dealloc_period: None,
522 });
523 pools
524 }
525 MemoryConfiguration::ExclusivePages => {
526 const MIN_BUCKET_SIZE: u64 = 1024 * 32;
530 const NUM_POOLS: usize = 24;
531
532 let sizes = generate_bucket_sizes(
533 MIN_BUCKET_SIZE,
534 properties.max_page_size,
535 NUM_POOLS,
536 properties.alignment,
537 );
538
539 sizes
540 .iter()
541 .map(|&size| {
542 let dealloc_period = (BASE_DEALLOC_PERIOD as f64
543 * (1.0 + size as f64 / (DEALLOC_SCALE_MB as f64)).round())
544 as u64;
545
546 MemoryPoolOptions {
547 pool_type: PoolType::ExclusivePages {
548 max_alloc_size: size,
549 },
550 dealloc_period: Some(dealloc_period),
551 }
552 })
553 .collect()
554 }
555 MemoryConfiguration::Custom { pool_options } => pool_options,
556 };
557
558 logger.log_memory(
559 |level| !matches!(level, MemoryLogLevel::Disabled),
560 || {
561 let mut msg = String::new();
562 for pool in pool_options.iter() {
563 msg += &format!("[{name}] Using memory pool: \n {pool:?}\n");
564 }
565 msg
566 },
567 );
568
569 assert!(
570 pool_options.len() < PERSISTENT_POOL_POS as usize,
571 "at most {} dynamic pools are supported",
572 PERSISTENT_POOL_POS
573 );
574
575 pool_options
576 .iter()
577 .enumerate()
578 .map(|(pool_pos, pool)| {
579 let pool_pos = pool_pos as u8;
580
581 match pool.pool_type {
582 PoolType::SlicedPages {
583 page_size,
584 max_slice_size,
585 max_pool_size,
586 } => DynamicPool::Sliced(SlicedPool::new(
587 page_size,
588 max_slice_size,
589 properties.alignment,
590 pool_pos,
591 max_pool_size,
592 )),
593 PoolType::ExclusivePages { max_alloc_size } => {
594 DynamicPool::Exclusive(ExclusiveMemoryPool::new(
595 max_alloc_size,
596 properties.alignment,
597 pool.dealloc_period.unwrap_or(u64::MAX),
598 pool_pos,
599 ))
600 }
601 }
602 })
603 .collect()
604}
605
606impl<Storage: ComputeStorage> MemoryManagement<Storage> {
607 pub fn from_configuration(
609 storage: Storage,
610 properties: &MemoryDeviceProperties,
611 config: MemoryConfiguration,
612 logger: Arc<ServerLogger>,
613 options: MemoryManagementOptions,
614 ) -> Self {
615 let pools = build_pools(properties, config, &logger, &options.name);
616
617 let config = CubeClRuntimeConfig::get().memory.persistent_memory.clone();
618
619 let mode = match options.memory {
620 MemoryAllocationOption::Provided(mode) => mode,
621 MemoryAllocationOption::FromConfig => match config {
622 PersistentMemory::Enabled | PersistentMemory::SizeMatch => {
623 MemoryAllocationMode::Auto
624 }
625 PersistentMemory::Disabled => MemoryAllocationMode::Auto,
626 PersistentMemory::Enforced => MemoryAllocationMode::Persistent,
627 },
628 };
629
630 Self {
631 name: options.name,
632 persistent: PersistentPool::new(
633 properties.max_page_size,
634 properties.alignment,
635 PERSISTENT_POOL_POS,
636 ),
637 pools,
638 storage,
639 alloc_reserve_count: 0,
640 mode,
641 config,
642 logger,
643 capture: None,
644 }
645 }
646
647 pub fn configure(
660 &mut self,
661 config: MemoryConfiguration,
662 properties: &MemoryDeviceProperties,
663 ) -> bool {
664 self.cleanup(true);
665
666 let dynamic_in_use: u64 = self
669 .pools
670 .iter()
671 .map(|pool| match pool {
672 DynamicPool::Sliced(p) => p.get_memory_usage().bytes_in_use,
673 DynamicPool::Exclusive(p) => p.get_memory_usage().bytes_in_use,
674 })
675 .sum();
676 if dynamic_in_use > 0 {
677 self.logger.log_memory(
678 |level| !matches!(level, MemoryLogLevel::Disabled),
679 || {
680 format!(
681 "[{}] Keeping the current pool layout: {dynamic_in_use} bytes \
682 are still live in the dynamic pools",
683 self.name
684 )
685 },
686 );
687 return false;
688 }
689
690 self.pools = build_pools(properties, config, &self.logger, &self.name);
691 true
692 }
693
694 pub fn capture_begin(&mut self) {
707 if self.capture.is_none() {
708 self.capture = Some(CaptureState {
709 restore_mode: self.mode,
710 touched: HashSet::new(),
711 });
712 }
713 self.mode = MemoryAllocationMode::Persistent;
714 }
715
716 pub fn capture_end(&mut self) -> Vec<ManagedMemoryHandle> {
725 match self.capture.take() {
726 Some(capture) => {
727 self.mode = capture.restore_mode;
728 self.persistent.retain_touched(&capture.touched)
729 }
730 None => Vec::new(),
731 }
732 }
733
734 pub fn mode(&mut self, mode: MemoryAllocationMode) {
736 let mode = match self.config {
738 PersistentMemory::Enabled | PersistentMemory::SizeMatch => mode,
739 PersistentMemory::Disabled | PersistentMemory::Enforced => return,
740 };
741
742 self.logger.log_memory(
743 |level| !matches!(level, MemoryLogLevel::Disabled),
744 || {
745 format!(
746 "[{}] Setting memory allocation mode: from {:?} => {mode:?}",
747 self.name, self.mode
748 )
749 },
750 );
751
752 match &mut self.capture {
756 Some(capture) => capture.restore_mode = mode,
757 None => self.mode = mode,
758 }
759 }
760
761 pub fn cleanup(&mut self, explicit: bool) {
763 self.logger.log_memory(
764 |level| !matches!(level, MemoryLogLevel::Disabled) && explicit,
765 || "Manual memory cleanup ...".to_string(),
766 );
767
768 if self.capture.is_some() {
775 return;
776 }
777
778 self.persistent
779 .cleanup(&mut self.storage, self.alloc_reserve_count, explicit);
780
781 for pool in self.pools.iter_mut() {
782 pool.cleanup(&mut self.storage, self.alloc_reserve_count, explicit);
783 }
784
785 if explicit {
789 self.storage.flush();
790 }
791 }
792
793 pub fn get_cursor(&self, binding: ManagedMemoryBinding) -> Result<u64, IoError> {
795 let slice = self.find(binding)?;
796 Ok(slice.cursor)
797 }
798
799 fn find(&self, binding: ManagedMemoryBinding) -> Result<&Slice, IoError> {
801 let id = binding.descriptor();
802
803 if id.location().init == 0 {
804 return Err(IoError::NotFound {
805 backtrace: BackTrace::capture(),
806 reason: "Memory location was never initialized".into(),
807 });
808 }
809
810 let slice = if id.location().pool == PERSISTENT_POOL_POS {
811 self.persistent.find(&binding)?
812 } else {
813 let pool =
814 self.pools
815 .get(id.location().pool as usize)
816 .ok_or_else(|| IoError::NotFound {
817 backtrace: BackTrace::capture(),
818 reason: format!("Pool {} doesn't exist", id.location().pool).into(),
819 })?;
820
821 pool.find(&binding)?
822 };
823
824 if slice.handle.descriptor() != binding.descriptor() {
828 return Err(IoError::NotFound {
829 backtrace: BackTrace::capture(),
830 reason: "Memory location points to a different allocation".into(),
831 });
832 }
833
834 Ok(slice)
835 }
836
837 pub fn get_storage(&mut self, binding: ManagedMemoryBinding) -> Result<StorageHandle, IoError> {
839 let slice = self.find(binding)?;
840 Ok(slice.storage.clone())
841 }
842
843 pub fn get_resource(
845 &mut self,
846 binding: ManagedMemoryBinding,
847 offset_start: Option<u64>,
848 offset_end: Option<u64>,
849 ) -> Result<Storage::Resource, IoError> {
850 let handle = self.get_storage(binding)?;
851
852 let handle = match offset_start {
853 Some(offset) => handle.offset_start(offset),
854 None => handle,
855 };
856 let handle = match offset_end {
857 Some(offset) => handle.offset_end(offset),
858 None => handle,
859 };
860 Ok(self.storage().get(&handle))
861 }
862
863 fn capture_touch(&mut self, handle: &ManagedMemoryHandle) {
873 if let Some(capture) = &mut self.capture {
874 capture.touched.insert(handle.descriptor().id);
875 }
876 }
877
878 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
880 pub fn reserve(&mut self, size: u64) -> Result<ManagedMemoryHandle, IoError> {
881 self.alloc_reserve_count += 1;
884
885 let persistent_mode = matches!(self.mode, MemoryAllocationMode::Persistent);
892 let size_match = matches!(self.config, PersistentMemory::SizeMatch);
893
894 if (persistent_mode || size_match)
895 && let Some(val) = self.persistent.try_reserve(size)
896 {
897 self.logger.log_memory(
898 |level| matches!(level, MemoryLogLevel::Full),
899 || {
900 format!(
901 "[{}] Reserved memory {size} using persistent memory",
902 self.name
903 )
904 },
905 );
906 self.capture_touch(&val);
907 return Ok(val);
908 }
909
910 if persistent_mode || (size_match && self.persistent.has_size(size)) {
911 let allocated = self.persistent.alloc(&mut self.storage, size);
912
913 self.logger.log_memory(
914 |level| !matches!(level, MemoryLogLevel::Disabled),
915 || {
916 format!(
917 "[{}] Allocated a new memory page using persistent memory, \n{}",
918 self.name, self,
919 )
920 },
921 );
922 if let Ok(handle) = &allocated {
923 self.capture_touch(handle);
924 }
925 return allocated;
926 }
927
928 self.logger.log_memory(
929 |level| matches!(level, MemoryLogLevel::Full),
930 || {
931 format!(
932 "[{}] Reserved memory {} using dynamic pool",
933 self.name,
934 BytesFormat::new(size)
935 )
936 },
937 );
938
939 let pool = self
941 .pools
942 .iter_mut()
943 .find(|p| p.accept(size))
944 .ok_or(IoError::BufferTooBig {
945 size,
946 backtrace: BackTrace::capture(),
947 })?;
948
949 if let Some(slice) = pool.try_reserve(size) {
950 return Ok(slice);
951 }
952
953 let allocated = pool.alloc(&mut self.storage, size);
954
955 self.logger.log_memory(
956 |level| matches!(level, MemoryLogLevel::Full),
957 || {
958 format!(
959 "[{}], Allocated a new memory page, current usage: \n{}",
960 self.name, self
961 )
962 },
963 );
964
965 allocated
966 }
967
968 pub fn storage(&mut self) -> &mut Storage {
979 &mut self.storage
980 }
981
982 pub fn memory_usage(&self) -> MemoryUsage {
984 let memory_usage = self.pools.iter().map(|x| x.get_memory_usage()).fold(
985 MemoryUsage {
986 number_allocs: 0,
987 bytes_in_use: 0,
988 bytes_padding: 0,
989 bytes_reserved: 0,
990 },
991 |m1, m2| m1.combine(m2),
992 );
993 memory_usage.combine(self.persistent.get_memory_usage())
994 }
995
996 pub fn print_memory_usage(&self) {
998 #[cfg(feature = "std")]
999 log::info!("{}", self.memory_usage());
1000 }
1001
1002 pub fn bind(
1004 &mut self,
1005 reserved: ManagedMemoryHandle,
1006 assigned: ManagedMemoryHandle,
1007 cursor: u64,
1008 ) -> Result<(), IoError> {
1009 let descriptor = reserved.descriptor();
1010
1011 if descriptor.location().init == 0 {
1012 return Err(IoError::NotFound {
1013 backtrace: BackTrace::capture(),
1014 reason: "Reserved memory isn't initialized".into(),
1015 });
1016 }
1017
1018 let pool_index = descriptor.location().pool as usize;
1019 if pool_index == PERSISTENT_POOL_POS as usize {
1020 self.capture_touch(&assigned);
1024 return self.persistent.bind(reserved, assigned, cursor);
1025 }
1026
1027 self.pools
1028 .get_mut(pool_index)
1029 .map(|p| p.bind(reserved, assigned, cursor))
1030 .ok_or_else(|| IoError::NotFound {
1031 backtrace: BackTrace::capture(),
1032 reason: format!("Memory pool {} doesn't exist", pool_index).into(),
1033 })?
1034 }
1035}
1036
1037impl<Storage: ComputeStorage> core::fmt::Display for MemoryManagement<Storage> {
1038 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1039 f.write_str("\n# MemoryManagement\n\n")?;
1040 f.write_fmt(format_args!(" - name: {:?}\n", self.name))?;
1041 f.write_fmt(format_args!("\n## Persistent\n\n{}", self.persistent))?;
1042 f.write_str("\n## Dynamic\n\n")?;
1043
1044 for pool in self.pools.iter() {
1045 match pool {
1046 DynamicPool::Sliced(pool) => f.write_fmt(format_args!("{pool}\n"))?,
1047 DynamicPool::Exclusive(pool) => f.write_fmt(format_args!("{pool}\n"))?,
1048 }
1049 }
1050 let memory_usage = self.memory_usage();
1051 f.write_fmt(format_args!("\n## Summary\n\n{memory_usage}"))?;
1052
1053 Ok(())
1054 }
1055}
1056
1057impl<Storage> core::fmt::Debug for MemoryManagement<Storage> {
1058 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1059 f.write_str(
1060 alloc::format!(
1061 "DynamicMemoryManagement {:?}",
1062 core::any::type_name::<Storage>(),
1063 )
1064 .as_str(),
1065 )
1066 }
1067}
1068
1069#[cfg(test)]
1070mod tests {
1071 use super::*;
1072 use crate::{memory_management::MemoryManagement, storage::BytesStorage};
1073 use alloc::vec;
1074
1075 const DUMMY_MEM_PROPS: MemoryDeviceProperties = MemoryDeviceProperties {
1076 max_page_size: 128 * 1024 * 1024,
1077 alignment: 32,
1078 };
1079
1080 fn options() -> MemoryManagementOptions {
1081 MemoryManagementOptions {
1082 name: "test".into(),
1083 memory: MemoryAllocationOption::FromConfig,
1084 }
1085 }
1086
1087 #[test_log::test]
1089 #[cfg(not(exclusive_memory_only))]
1090 fn test_handle_mutability() {
1091 let mut memory_management = MemoryManagement::from_configuration(
1092 BytesStorage::default(),
1093 &DUMMY_MEM_PROPS,
1094 MemoryConfiguration::SubSlices,
1095 Arc::new(ServerLogger::default()),
1096 options(),
1097 );
1098 let handle = memory_management.reserve(10).unwrap();
1099 let other_ref = handle.clone();
1100 assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
1101 drop(other_ref);
1102 assert!(handle.can_mut(), "Handle should be mut when only one ref.");
1103 }
1104
1105 #[test_log::test]
1107 #[cfg(not(exclusive_memory_only))]
1108 fn test_memory_usage() {
1109 let max_page_size = 512;
1110
1111 let mut memory_management = MemoryManagement::from_configuration(
1112 BytesStorage::default(),
1113 &DUMMY_MEM_PROPS,
1114 MemoryConfiguration::Custom {
1115 pool_options: vec![MemoryPoolOptions {
1116 pool_type: PoolType::ExclusivePages {
1117 max_alloc_size: max_page_size,
1118 },
1119 dealloc_period: None,
1120 }],
1121 },
1122 Arc::new(ServerLogger::default()),
1123 options(),
1124 );
1125 let handle = memory_management.reserve(100);
1126 let usage = memory_management.memory_usage();
1127
1128 assert_eq!(usage.bytes_in_use, 100);
1129 assert!(usage.bytes_reserved >= 100 && usage.bytes_reserved <= max_page_size);
1130
1131 drop(handle);
1133 let _handle = memory_management.reserve(100);
1134 let usage_new = memory_management.memory_usage();
1135 assert_eq!(usage, usage_new);
1136 }
1137
1138 #[test_log::test]
1139 fn find_uninit_binding_returns_not_found() {
1140 let mut memory_management = MemoryManagement::from_configuration(
1141 BytesStorage::default(),
1142 &DUMMY_MEM_PROPS,
1143 MemoryConfiguration::Custom {
1144 pool_options: vec![MemoryPoolOptions {
1145 pool_type: PoolType::SlicedPages {
1146 page_size: 2048,
1147 max_slice_size: 2048,
1148 max_pool_size: None,
1149 },
1150 dealloc_period: None,
1151 }],
1152 },
1153 Arc::new(ServerLogger::default()),
1154 options(),
1155 );
1156
1157 let _live = memory_management.reserve(512).unwrap();
1160
1161 let binding = ManagedMemoryHandle::new().binding();
1162 assert!(matches!(
1163 memory_management.get_cursor(binding),
1164 Err(IoError::NotFound { .. })
1165 ));
1166 }
1167
1168 #[test_log::test]
1169 fn find_stale_descriptor_returns_not_found() {
1170 let mut memory_management = MemoryManagement::from_configuration(
1171 BytesStorage::default(),
1172 &DUMMY_MEM_PROPS,
1173 MemoryConfiguration::Custom {
1174 pool_options: vec![MemoryPoolOptions {
1175 pool_type: PoolType::SlicedPages {
1176 page_size: 2048,
1177 max_slice_size: 2048,
1178 max_pool_size: None,
1179 },
1180 dealloc_period: None,
1181 }],
1182 },
1183 Arc::new(ServerLogger::default()),
1184 options(),
1185 );
1186
1187 let reserved = memory_management.reserve(512).unwrap();
1188 let stale = reserved.clone();
1189 let assigned = ManagedMemoryHandle::new();
1190 let assigned_binding = assigned.clone().binding();
1191
1192 memory_management.bind(reserved, assigned, 0).unwrap();
1193
1194 assert!(matches!(
1197 memory_management.get_cursor(stale.binding()),
1198 Err(IoError::NotFound { .. })
1199 ));
1200 assert!(memory_management.get_cursor(assigned_binding).is_ok());
1201 }
1202
1203 #[test_log::test]
1204 fn held_binding_survives_explicit_cleanup_renumber() {
1205 let mut memory_management = MemoryManagement::from_configuration(
1206 BytesStorage::default(),
1207 &DUMMY_MEM_PROPS,
1208 MemoryConfiguration::Custom {
1209 pool_options: vec![MemoryPoolOptions {
1210 pool_type: PoolType::ExclusivePages {
1211 max_alloc_size: 1024,
1212 },
1213 dealloc_period: None,
1214 }],
1215 },
1216 Arc::new(ServerLogger::default()),
1217 options(),
1218 );
1219
1220 let handle_a = memory_management.reserve(1024).unwrap();
1221 let handle_b = memory_management.reserve(1024).unwrap();
1222 let handle_c = memory_management.reserve(1024).unwrap();
1223
1224 let binding_b = handle_b.binding();
1225 drop(handle_a);
1226 drop(handle_c);
1227
1228 memory_management.cleanup(true);
1230
1231 assert!(memory_management.get_cursor(binding_b.clone()).is_ok());
1232 assert!(memory_management.get_storage(binding_b).is_ok());
1233 assert_eq!(memory_management.memory_usage().bytes_reserved, 1024);
1234 }
1235
1236 fn capped_sliced_config(page_size: u64, max_pool_size: Option<u64>) -> MemoryConfiguration {
1237 MemoryConfiguration::Custom {
1238 pool_options: vec![MemoryPoolOptions {
1239 pool_type: PoolType::SlicedPages {
1240 page_size,
1241 max_slice_size: page_size,
1242 max_pool_size,
1243 },
1244 dealloc_period: None,
1245 }],
1246 }
1247 }
1248
1249 #[test_log::test]
1250 fn capped_sliced_pool_errors_instead_of_growing() {
1251 let mut memory_management = MemoryManagement::from_configuration(
1252 BytesStorage::default(),
1253 &DUMMY_MEM_PROPS,
1254 capped_sliced_config(1024, Some(2048)),
1255 Arc::new(ServerLogger::default()),
1256 options(),
1257 );
1258
1259 let _a = memory_management.reserve(1024).unwrap();
1260 let _b = memory_management.reserve(1024).unwrap();
1261
1262 let result = memory_management.reserve(1024);
1263 assert!(matches!(
1264 result,
1265 Err(IoError::PoolCapacityExceeded { capacity: 2048, .. })
1266 ));
1267 assert_eq!(
1268 memory_management.memory_usage().bytes_reserved,
1269 2048,
1270 "a failed reservation must not grow the pool"
1271 );
1272 }
1273
1274 #[test_log::test]
1275 fn capped_sliced_pool_reuses_freed_memory() {
1276 let mut memory_management = MemoryManagement::from_configuration(
1277 BytesStorage::default(),
1278 &DUMMY_MEM_PROPS,
1279 capped_sliced_config(1024, Some(2048)),
1280 Arc::new(ServerLogger::default()),
1281 options(),
1282 );
1283
1284 let handle_a = memory_management.reserve(1024).unwrap();
1285 let _b = memory_management.reserve(1024).unwrap();
1286 drop(handle_a);
1287
1288 let _c = memory_management.reserve(1024).unwrap();
1291 assert_eq!(memory_management.memory_usage().bytes_reserved, 2048);
1292 }
1293
1294 #[test_log::test]
1295 fn capped_lazy_pool_cleanup_still_frees() {
1296 let mut memory_management = MemoryManagement::from_configuration(
1297 BytesStorage::default(),
1298 &DUMMY_MEM_PROPS,
1299 capped_sliced_config(1024, Some(2048)),
1300 Arc::new(ServerLogger::default()),
1301 options(),
1302 );
1303
1304 let handle_a = memory_management.reserve(1024).unwrap();
1305 let handle_b = memory_management.reserve(1024).unwrap();
1306 drop(handle_a);
1307 drop(handle_b);
1308 memory_management.cleanup(true);
1309 assert_eq!(memory_management.memory_usage().bytes_reserved, 0);
1310
1311 let _a = memory_management.reserve(1024).unwrap();
1313 let _b = memory_management.reserve(1024).unwrap();
1314 assert!(matches!(
1315 memory_management.reserve(1024),
1316 Err(IoError::PoolCapacityExceeded { .. })
1317 ));
1318 }
1319
1320 #[test_log::test]
1321 fn max_pool_size_smaller_than_page_shrinks_page() {
1322 let mut memory_management = MemoryManagement::from_configuration(
1323 BytesStorage::default(),
1324 &DUMMY_MEM_PROPS,
1325 capped_sliced_config(2048, Some(512)),
1326 Arc::new(ServerLogger::default()),
1327 options(),
1328 );
1329
1330 let _small = memory_management.reserve(256).unwrap();
1331 assert!(memory_management.memory_usage().bytes_reserved <= 512);
1332
1333 assert!(memory_management.reserve(1024).is_err());
1335 assert!(memory_management.memory_usage().bytes_reserved <= 512);
1336 }
1337
1338 #[test_log::test]
1339 fn max_pool_size_below_alignment_never_overshoots() {
1340 let mut memory_management = MemoryManagement::from_configuration(
1344 BytesStorage::default(),
1345 &DUMMY_MEM_PROPS,
1346 capped_sliced_config(1024, Some(16)),
1347 Arc::new(ServerLogger::default()),
1348 options(),
1349 );
1350
1351 assert!(matches!(
1352 memory_management.reserve(8),
1353 Err(IoError::PoolCapacityExceeded { .. })
1354 ));
1355 assert_eq!(memory_management.memory_usage().bytes_reserved, 0);
1356 }
1357
1358 #[test_log::test]
1359 fn capacity_error_does_not_fall_through_to_later_pool() {
1360 let mut memory_management = MemoryManagement::from_configuration(
1361 BytesStorage::default(),
1362 &DUMMY_MEM_PROPS,
1363 MemoryConfiguration::Custom {
1364 pool_options: vec![
1365 MemoryPoolOptions {
1366 pool_type: PoolType::SlicedPages {
1367 page_size: 1024,
1368 max_slice_size: 1024,
1369 max_pool_size: Some(1024),
1370 },
1371 dealloc_period: None,
1372 },
1373 MemoryPoolOptions {
1374 pool_type: PoolType::SlicedPages {
1375 page_size: 1024,
1376 max_slice_size: 1024,
1377 max_pool_size: None,
1378 },
1379 dealloc_period: None,
1380 },
1381 ],
1382 },
1383 Arc::new(ServerLogger::default()),
1384 options(),
1385 );
1386
1387 let _fill = memory_management.reserve(1024).unwrap();
1388 assert!(matches!(
1389 memory_management.reserve(1024),
1390 Err(IoError::PoolCapacityExceeded { .. })
1391 ));
1392 assert_eq!(
1393 memory_management.memory_usage().bytes_reserved,
1394 1024,
1395 "the overflow must not silently land in the later pool"
1396 );
1397 }
1398
1399 #[test_log::test]
1400 fn alloc_two_chunks_on_one_page() {
1401 let page_size = 2048;
1402
1403 let mut memory_management = MemoryManagement::from_configuration(
1404 BytesStorage::default(),
1405 &DUMMY_MEM_PROPS,
1406 MemoryConfiguration::Custom {
1407 pool_options: vec![MemoryPoolOptions {
1408 pool_type: PoolType::SlicedPages {
1409 page_size,
1410 max_slice_size: page_size,
1411 max_pool_size: None,
1412 },
1413 dealloc_period: None,
1414 }],
1415 },
1416 Arc::new(ServerLogger::default()),
1417 options(),
1418 );
1419
1420 let alloc_size = 512;
1421 let _handle = memory_management.reserve(alloc_size);
1422 let _new_handle = memory_management.reserve(alloc_size);
1423
1424 let usage = memory_management.memory_usage();
1425 assert_eq!(usage.number_allocs, 2);
1426 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1427 assert_eq!(usage.bytes_reserved, page_size);
1428 }
1429
1430 #[test_log::test]
1431 fn alloc_reuses_storage() {
1432 let page_size = 512;
1434
1435 let mut memory_management = MemoryManagement::from_configuration(
1436 BytesStorage::default(),
1437 &DUMMY_MEM_PROPS,
1438 MemoryConfiguration::Custom {
1439 pool_options: vec![MemoryPoolOptions {
1440 pool_type: PoolType::SlicedPages {
1441 page_size,
1442 max_slice_size: page_size,
1443 max_pool_size: None,
1444 },
1445 dealloc_period: None,
1446 }],
1447 },
1448 Arc::new(ServerLogger::default()),
1449 options(),
1450 );
1451
1452 let alloc_size = 512;
1453 let _handle = memory_management.reserve(alloc_size);
1454 drop(_handle);
1455 let _new_handle = memory_management.reserve(alloc_size);
1456
1457 let usage = memory_management.memory_usage();
1458 assert_eq!(usage.number_allocs, 1);
1459 assert_eq!(usage.bytes_in_use, alloc_size);
1460 assert_eq!(usage.bytes_reserved, page_size);
1461 }
1462
1463 #[test_log::test]
1464 fn alloc_allocs_new_storage() {
1465 let page_size = 1024;
1466
1467 let mut memory_management = MemoryManagement::from_configuration(
1468 BytesStorage::default(),
1469 &DUMMY_MEM_PROPS,
1470 MemoryConfiguration::Custom {
1471 pool_options: vec![MemoryPoolOptions {
1472 pool_type: PoolType::SlicedPages {
1473 page_size,
1474 max_slice_size: page_size,
1475 max_pool_size: None,
1476 },
1477 dealloc_period: None,
1478 }],
1479 },
1480 Arc::new(ServerLogger::default()),
1481 options(),
1482 );
1483
1484 let alloc_size = 768;
1485 let _handle = memory_management.reserve(alloc_size);
1486 let _new_handle = memory_management.reserve(alloc_size);
1487
1488 let usage = memory_management.memory_usage();
1489 assert_eq!(usage.number_allocs, 2);
1490 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1491 assert_eq!(usage.bytes_reserved, page_size * 2);
1492 }
1493
1494 #[test_log::test]
1495 fn alloc_respects_alignment_size() {
1496 let page_size = 500;
1497 let mut memory_management = MemoryManagement::from_configuration(
1498 BytesStorage::default(),
1499 &MemoryDeviceProperties {
1500 max_page_size: page_size,
1501 alignment: 50,
1502 },
1503 MemoryConfiguration::Custom {
1504 pool_options: vec![MemoryPoolOptions {
1505 pool_type: PoolType::SlicedPages {
1506 page_size,
1507 max_slice_size: page_size,
1508 max_pool_size: None,
1509 },
1510 dealloc_period: None,
1511 }],
1512 },
1513 Arc::new(ServerLogger::default()),
1514 options(),
1515 );
1516 let alloc_size = 40;
1517 let _handle = memory_management.reserve(alloc_size);
1518 let _new_handle = memory_management.reserve(alloc_size);
1519 let usage = memory_management.memory_usage();
1520 assert_eq!(usage.bytes_padding, 10 * 2);
1522 }
1523
1524 #[test_log::test]
1525 fn allocs_on_correct_page() {
1526 let sizes = [100, 200, 300, 400];
1527
1528 let pools = sizes
1529 .iter()
1530 .map(|size| MemoryPoolOptions {
1531 pool_type: PoolType::SlicedPages {
1532 page_size: *size,
1533 max_slice_size: *size,
1534 max_pool_size: None,
1535 },
1536 dealloc_period: None,
1537 })
1538 .collect();
1539 let mut memory_management = MemoryManagement::from_configuration(
1540 BytesStorage::default(),
1541 &MemoryDeviceProperties {
1542 max_page_size: 128 * 1024 * 1024,
1543 alignment: 10,
1544 },
1545 MemoryConfiguration::Custom {
1546 pool_options: pools,
1547 },
1548 Arc::new(ServerLogger::default()),
1549 options(),
1550 );
1551 let alloc_sizes = [50, 150, 250, 350];
1553 let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
1554
1555 let usage = memory_management.memory_usage();
1556
1557 assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
1559 assert!(usage.bytes_reserved >= sizes.iter().sum::<u64>());
1560 }
1561
1562 #[test_log::test]
1563 fn resolve_absent_pools_config_keeps_runtime_choice() {
1564 #[cfg(not(exclusive_memory_only))]
1565 assert!(matches!(
1566 MemoryConfiguration::SubSlices
1567 .resolve(None, &DUMMY_MEM_PROPS)
1568 .unwrap(),
1569 MemoryConfiguration::SubSlices
1570 ));
1571 assert!(matches!(
1572 MemoryConfiguration::ExclusivePages
1573 .resolve(None, &DUMMY_MEM_PROPS)
1574 .unwrap(),
1575 MemoryConfiguration::ExclusivePages
1576 ));
1577 }
1578
1579 #[test_log::test]
1580 fn resolve_preset_overrides_runtime_choice() {
1581 let preset = MemoryPoolsConfig::Preset(MemoryPoolsPreset::ExclusivePages);
1582 #[cfg(not(exclusive_memory_only))]
1583 let base = MemoryConfiguration::SubSlices;
1584 #[cfg(exclusive_memory_only)]
1585 let base = MemoryConfiguration::ExclusivePages;
1586
1587 assert!(matches!(
1588 base.resolve(Some(&preset), &DUMMY_MEM_PROPS).unwrap(),
1589 MemoryConfiguration::ExclusivePages
1590 ));
1591 }
1592
1593 #[test_log::test]
1594 #[cfg(exclusive_memory_only)]
1595 fn resolve_rejects_sliced_pools_when_exclusive_only() {
1596 use crate::config::size::MemorySize;
1597
1598 let pools = MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1599 page_size: MemorySize(1024),
1600 max_slice_size: None,
1601 max_pool_size: None,
1602 dealloc_period: None,
1603 }]);
1604 assert_eq!(
1605 MemoryConfiguration::default()
1606 .resolve(Some(&pools), &DUMMY_MEM_PROPS)
1607 .unwrap_err(),
1608 PoolConfigError::SlicedPoolsUnavailable
1609 );
1610 }
1611
1612 #[test_log::test]
1613 #[cfg(not(exclusive_memory_only))]
1614 fn resolve_explicit_list_aligns_and_defaults() {
1615 use crate::config::size::MemorySize;
1616
1617 let pools = MemoryPoolsConfig::Explicit(vec![
1618 MemoryPoolConfig::Exclusive {
1619 max_alloc_size: MemorySize(8 * 1024),
1620 dealloc_period: Some(10000),
1621 },
1622 MemoryPoolConfig::Sliced {
1623 page_size: MemorySize(1000),
1625 max_slice_size: None,
1626 max_pool_size: Some(MemorySize(4096)),
1627 dealloc_period: None,
1628 },
1629 ]);
1630
1631 let resolved = MemoryConfiguration::default()
1632 .resolve(Some(&pools), &DUMMY_MEM_PROPS)
1633 .unwrap();
1634 let MemoryConfiguration::Custom { pool_options } = resolved else {
1635 panic!("expected a custom configuration");
1636 };
1637
1638 assert_eq!(pool_options.len(), 2);
1639 assert!(matches!(
1640 pool_options[0].pool_type,
1641 PoolType::ExclusivePages {
1642 max_alloc_size: 8192
1643 }
1644 ));
1645 assert_eq!(pool_options[0].dealloc_period, Some(10000));
1646 assert!(matches!(
1647 pool_options[1].pool_type,
1648 PoolType::SlicedPages {
1649 page_size: 1024,
1650 max_slice_size: 1024,
1652 max_pool_size: Some(4096),
1653 }
1654 ));
1655 }
1656
1657 #[test_log::test]
1658 #[cfg(not(exclusive_memory_only))]
1659 fn resolve_invalid_pool_configs_fail() {
1660 use crate::config::size::MemorySize;
1661
1662 let cases = [
1663 (
1664 MemoryPoolsConfig::Explicit(vec![]),
1665 PoolConfigError::EmptyPoolList,
1666 ),
1667 (
1668 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1669 page_size: MemorySize(0),
1670 max_slice_size: None,
1671 max_pool_size: None,
1672 dealloc_period: None,
1673 }]),
1674 PoolConfigError::ZeroSize { field: "page_size" },
1675 ),
1676 (
1677 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1678 page_size: MemorySize(1024),
1679 max_slice_size: Some(MemorySize(2048)),
1680 max_pool_size: None,
1681 dealloc_period: None,
1682 }]),
1683 PoolConfigError::SliceLargerThanPage {
1684 page_size: 1024,
1685 max_slice_size: 2048,
1686 },
1687 ),
1688 (
1689 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1690 page_size: MemorySize(2048),
1691 max_slice_size: None,
1692 max_pool_size: Some(MemorySize(1024)),
1693 dealloc_period: None,
1694 }]),
1695 PoolConfigError::CapSmallerThanPage {
1696 page_size: 2048,
1697 max_pool_size: 1024,
1698 },
1699 ),
1700 (
1701 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1702 page_size: MemorySize(1024),
1703 max_slice_size: None,
1704 max_pool_size: Some(MemorySize(64 * 1024 * 1024 * 1024)),
1706 dealloc_period: None,
1707 }]),
1708 PoolConfigError::TooManyPages {
1709 pages: 64 * 1024 * 1024,
1710 },
1711 ),
1712 (
1713 MemoryPoolsConfig::Explicit(vec![
1714 MemoryPoolConfig::Exclusive {
1715 max_alloc_size: MemorySize(1024),
1716 dealloc_period: None,
1717 };
1718 PERSISTENT_POOL_POS as usize
1719 ]),
1720 PoolConfigError::TooManyPools {
1721 count: PERSISTENT_POOL_POS as usize,
1722 },
1723 ),
1724 ];
1725
1726 for (pools, expected) in cases {
1727 let result = MemoryConfiguration::default().resolve(Some(&pools), &DUMMY_MEM_PROPS);
1728 assert_eq!(result.unwrap_err(), expected);
1729 }
1730 }
1731
1732 #[test_log::test]
1736 #[cfg(not(exclusive_memory_only))]
1737 fn resolved_single_arena_reuses_across_sizes() {
1738 use crate::config::size::MemorySize;
1739
1740 let page = 1024 * 1024; let pools = MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1742 page_size: MemorySize(page),
1743 max_slice_size: None,
1744 max_pool_size: None,
1745 dealloc_period: None,
1746 }]);
1747 let config = MemoryConfiguration::default()
1748 .resolve(Some(&pools), &DUMMY_MEM_PROPS)
1749 .unwrap();
1750
1751 let mut memory_management = MemoryManagement::from_configuration(
1752 BytesStorage::default(),
1753 &DUMMY_MEM_PROPS,
1754 config,
1755 Arc::new(ServerLogger::default()),
1756 options(),
1757 );
1758
1759 let small = memory_management.reserve(4 * 1024).unwrap();
1761 drop(small);
1762 let large = memory_management.reserve(512 * 1024).unwrap();
1764
1765 let usage = memory_management.memory_usage();
1766 assert_eq!(
1767 usage.bytes_reserved, page,
1768 "both sizes must share a single arena page"
1769 );
1770 assert_eq!(usage.number_allocs, 1);
1771 drop(large);
1772 }
1773
1774 #[test_log::test]
1775 #[cfg(not(exclusive_memory_only))]
1776 fn allocate_deallocate_reallocate() {
1777 let mut memory_management = MemoryManagement::from_configuration(
1778 BytesStorage::default(),
1779 &MemoryDeviceProperties {
1780 max_page_size: 128 * 1024 * 1024,
1781 alignment: 32,
1782 },
1783 MemoryConfiguration::SubSlices,
1784 Arc::new(ServerLogger::default()),
1785 options(),
1786 );
1787 let handles: Vec<_> = (0..5)
1789 .map(|i| memory_management.reserve(1000 * (i + 1)))
1790 .collect();
1791 let usage_before = memory_management.memory_usage();
1792 drop(handles);
1794 let _new_handles: Vec<_> = (0..5)
1796 .map(|i| memory_management.reserve(1000 * (i + 1)))
1797 .collect();
1798 let usage_after = memory_management.memory_usage();
1799 assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
1800 assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
1801 assert!(usage_before.bytes_reserved >= usage_after.bytes_reserved);
1803 }
1804
1805 #[test_log::test]
1806 #[cfg(not(exclusive_memory_only))]
1807 fn test_fragmentation_resistance() {
1808 let mut memory_management = MemoryManagement::from_configuration(
1809 BytesStorage::default(),
1810 &MemoryDeviceProperties {
1811 max_page_size: 128 * 1024 * 1024,
1812 alignment: 32,
1813 },
1814 MemoryConfiguration::SubSlices,
1815 Arc::new(ServerLogger::default()),
1816 options(),
1817 );
1818 let sizes = [50, 1000, 100, 5000, 200, 10000, 300];
1820 let handles: Vec<_> = sizes
1821 .iter()
1822 .map(|&size| memory_management.reserve(size).unwrap())
1823 .collect();
1824 let usage_before = memory_management.memory_usage();
1825 for i in (0..handles.len()).step_by(2) {
1827 drop(handles[i].clone());
1828 }
1829 for &size in &sizes[0..sizes.len() / 2] {
1831 memory_management.reserve(size).unwrap();
1832 }
1833 let usage_after = memory_management.memory_usage();
1834 assert!(usage_after.bytes_reserved <= (usage_before.bytes_reserved as f64 * 1.1) as u64);
1836 }
1837
1838 #[test_log::test]
1840 fn noslice_test_handle_mutability() {
1841 let mut memory_management = MemoryManagement::from_configuration(
1842 BytesStorage::default(),
1843 &(MemoryDeviceProperties {
1844 max_page_size: 128 * 1024 * 1024,
1845 alignment: 32,
1846 }),
1847 MemoryConfiguration::ExclusivePages,
1848 Arc::new(ServerLogger::default()),
1849 options(),
1850 );
1851 let handle = memory_management.reserve(10).unwrap();
1852 let other_ref = handle.clone();
1853 assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
1854 drop(other_ref);
1855 assert!(handle.can_mut(), "Handle should be mut when only one ref.");
1856 }
1857
1858 #[test_log::test]
1859 fn noslice_alloc_two_chunk() {
1860 let mut memory_management = MemoryManagement::from_configuration(
1861 BytesStorage::default(),
1862 &DUMMY_MEM_PROPS,
1863 MemoryConfiguration::Custom {
1864 pool_options: vec![MemoryPoolOptions {
1865 pool_type: PoolType::ExclusivePages {
1866 max_alloc_size: 1024,
1867 },
1868 dealloc_period: None,
1869 }],
1870 },
1871 Arc::new(ServerLogger::default()),
1872 options(),
1873 );
1874
1875 let alloc_size = 512;
1876 let _handle = memory_management.reserve(alloc_size);
1877 let _new_handle = memory_management.reserve(alloc_size);
1878
1879 let usage = memory_management.memory_usage();
1880 assert_eq!(usage.number_allocs, 2);
1881 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1882 assert!(usage.bytes_reserved >= alloc_size * 2);
1883 }
1884
1885 #[test_log::test]
1886 fn noslice_alloc_reuses_storage() {
1887 let mut memory_management = MemoryManagement::from_configuration(
1889 BytesStorage::default(),
1890 &DUMMY_MEM_PROPS,
1891 MemoryConfiguration::Custom {
1892 pool_options: vec![MemoryPoolOptions {
1893 pool_type: PoolType::ExclusivePages {
1894 max_alloc_size: 1024,
1895 },
1896 dealloc_period: None,
1897 }],
1898 },
1899 Arc::new(ServerLogger::default()),
1900 options(),
1901 );
1902
1903 let alloc_size = 512;
1904 let _handle = memory_management.reserve(alloc_size);
1905 drop(_handle);
1906 let _new_handle = memory_management.reserve(alloc_size);
1907
1908 let usage = memory_management.memory_usage();
1909 assert_eq!(usage.number_allocs, 1);
1910 assert_eq!(usage.bytes_in_use, alloc_size);
1911 assert!(usage.bytes_reserved >= alloc_size);
1912 }
1913
1914 #[test_log::test]
1915 fn noslice_alloc_allocs_new_storage() {
1916 let mut memory_management = MemoryManagement::from_configuration(
1917 BytesStorage::default(),
1918 &DUMMY_MEM_PROPS,
1919 MemoryConfiguration::Custom {
1920 pool_options: vec![MemoryPoolOptions {
1921 pool_type: PoolType::ExclusivePages {
1922 max_alloc_size: 1024,
1923 },
1924 dealloc_period: None,
1925 }],
1926 },
1927 Arc::new(ServerLogger::default()),
1928 options(),
1929 );
1930
1931 let alloc_size = 768;
1932 let _handle = memory_management.reserve(alloc_size);
1933 let _new_handle = memory_management.reserve(alloc_size);
1934 let usage = memory_management.memory_usage();
1935 assert_eq!(usage.number_allocs, 2);
1936 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1937 assert!(usage.bytes_reserved >= alloc_size * 2);
1938 }
1939
1940 #[test_log::test]
1941 fn noslice_alloc_respects_alignment_size() {
1942 let mut memory_management = MemoryManagement::from_configuration(
1943 BytesStorage::default(),
1944 &MemoryDeviceProperties {
1945 max_page_size: DUMMY_MEM_PROPS.max_page_size,
1946 alignment: 50,
1947 },
1948 MemoryConfiguration::Custom {
1949 pool_options: vec![MemoryPoolOptions {
1950 pool_type: PoolType::ExclusivePages {
1951 max_alloc_size: 50 * 20,
1952 },
1953 dealloc_period: None,
1954 }],
1955 },
1956 Arc::new(ServerLogger::default()),
1957 options(),
1958 );
1959 let alloc_size = 40;
1960 let _handle = memory_management.reserve(alloc_size);
1961 let _new_handle = memory_management.reserve(alloc_size);
1962 let usage = memory_management.memory_usage();
1963 assert_eq!(usage.bytes_padding, 10 * 2);
1965 }
1966
1967 #[test_log::test]
1968 fn noslice_allocs_on_correct_page() {
1969 let pools = [100, 200, 300, 400]
1970 .iter()
1971 .map(|&size| MemoryPoolOptions {
1972 pool_type: PoolType::SlicedPages {
1973 page_size: size,
1974 max_slice_size: size,
1975 max_pool_size: None,
1976 },
1977 dealloc_period: None,
1978 })
1979 .collect();
1980 let mut memory_management = MemoryManagement::from_configuration(
1981 BytesStorage::default(),
1982 &MemoryDeviceProperties {
1983 max_page_size: DUMMY_MEM_PROPS.max_page_size,
1984 alignment: 10,
1985 },
1986 MemoryConfiguration::Custom {
1987 pool_options: pools,
1988 },
1989 Arc::new(ServerLogger::default()),
1990 options(),
1991 );
1992 let alloc_sizes = [50, 150, 250, 350];
1994 let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
1995 let usage = memory_management.memory_usage();
1996 assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
1998 }
1999
2000 #[test_log::test]
2001 fn capture_pins_reused_persistent_slice() {
2002 let mut memory_management = MemoryManagement::from_configuration(
2003 BytesStorage::default(),
2004 &DUMMY_MEM_PROPS,
2005 MemoryConfiguration::ExclusivePages,
2006 Arc::new(ServerLogger::default()),
2007 options(),
2008 );
2009
2010 memory_management.capture_begin();
2012 let first = memory_management.reserve(1024).unwrap();
2013 drop(first);
2014 drop(memory_management.capture_end());
2015
2016 memory_management.capture_begin();
2019 let second = memory_management.reserve(1024).unwrap();
2020 drop(second);
2021 let pins = memory_management.capture_end();
2022 assert_eq!(pins.len(), 1, "the reused slice must be retained");
2023
2024 let before = memory_management.memory_usage();
2026 let _other = memory_management.reserve(1024).unwrap();
2027 let after = memory_management.memory_usage();
2028 assert!(
2029 after.bytes_reserved > before.bytes_reserved,
2030 "a pinned slice was handed to a later allocation"
2031 );
2032 }
2033
2034 #[test_log::test]
2035 fn capture_pins_preexisting_slice_freed_and_reused_midwindow() {
2036 let mut memory_management = MemoryManagement::from_configuration(
2037 BytesStorage::default(),
2038 &DUMMY_MEM_PROPS,
2039 MemoryConfiguration::ExclusivePages,
2040 Arc::new(ServerLogger::default()),
2041 options(),
2042 );
2043
2044 memory_management.capture_begin();
2046 let live = memory_management.reserve(1024).unwrap();
2047 drop(memory_management.capture_end()); memory_management.capture_begin();
2055 drop(live);
2056 let reused = memory_management.reserve(1024).unwrap();
2057 drop(reused);
2058 let pins = memory_management.capture_end();
2059 assert_eq!(
2060 pins.len(),
2061 1,
2062 "a pre-existing slice freed and reused mid-window must be pinned"
2063 );
2064 }
2065
2066 #[test_log::test]
2067 fn capture_does_not_retain_untouched_free_slices() {
2068 let mut memory_management = MemoryManagement::from_configuration(
2069 BytesStorage::default(),
2070 &DUMMY_MEM_PROPS,
2071 MemoryConfiguration::ExclusivePages,
2072 Arc::new(ServerLogger::default()),
2073 options(),
2074 );
2075
2076 memory_management.capture_begin();
2078 let earlier = memory_management.reserve(1024).unwrap();
2079 drop(earlier);
2080 drop(memory_management.capture_end());
2081
2082 memory_management.capture_begin();
2086 let window = memory_management.reserve(2048).unwrap();
2087 drop(window);
2088 let pins = memory_management.capture_end();
2089 assert_eq!(
2090 pins.len(),
2091 1,
2092 "only the touched slice is retained, not the idle leftover"
2093 );
2094 }
2095
2096 #[test_log::test]
2097 fn capture_survives_explicit_cleanup() {
2098 let mut memory_management = MemoryManagement::from_configuration(
2099 BytesStorage::default(),
2100 &DUMMY_MEM_PROPS,
2101 MemoryConfiguration::ExclusivePages,
2102 Arc::new(ServerLogger::default()),
2103 options(),
2104 );
2105
2106 memory_management.capture_begin();
2107 let handle = memory_management.reserve(1024).unwrap();
2108 drop(handle);
2109 memory_management.cleanup(true);
2112 let pins = memory_management.capture_end();
2113 assert_eq!(pins.len(), 1, "pin lost across an explicit cleanup");
2114 }
2115
2116 #[test_log::test]
2117 fn capture_begin_is_reentrant() {
2118 let mut memory_management = MemoryManagement::from_configuration(
2119 BytesStorage::default(),
2120 &DUMMY_MEM_PROPS,
2121 MemoryConfiguration::ExclusivePages,
2122 Arc::new(ServerLogger::default()),
2123 options(),
2124 );
2125
2126 memory_management.capture_begin();
2127 let first = memory_management.reserve(1024).unwrap();
2128 memory_management.capture_begin();
2131 let second = memory_management.reserve(2048).unwrap();
2132 drop(first);
2133 drop(second);
2134 let pins = memory_management.capture_end();
2135 assert_eq!(pins.len(), 2, "pins from before the re-entrant begin lost");
2136 assert!(
2137 memory_management.capture_end().is_empty(),
2138 "capture must be fully disarmed"
2139 );
2140 }
2141
2142 #[test_log::test]
2143 fn capture_leaves_preexisting_buffers_alone() {
2144 let mut memory_management = MemoryManagement::from_configuration(
2145 BytesStorage::default(),
2146 &DUMMY_MEM_PROPS,
2147 MemoryConfiguration::ExclusivePages,
2148 Arc::new(ServerLogger::default()),
2149 options(),
2150 );
2151
2152 memory_management.capture_begin();
2155 let preexisting = memory_management.reserve(1024).unwrap();
2156 drop(memory_management.capture_end());
2157
2158 memory_management.capture_begin();
2159 let window = memory_management.reserve(2048).unwrap();
2160 drop(window);
2161 let pins = memory_management.capture_end();
2162
2163 assert_eq!(
2166 pins.len(),
2167 1,
2168 "only the window's slice belongs to the graph"
2169 );
2170 assert!(
2171 preexisting.can_mut(),
2172 "a capture must not claim pre-existing live buffers"
2173 );
2174 }
2175
2176 #[test_log::test]
2177 fn noslice_allocate_deallocate_reallocate() {
2178 let mut memory_management = MemoryManagement::from_configuration(
2179 BytesStorage::default(),
2180 &MemoryDeviceProperties {
2181 max_page_size: 128 * 1024 * 1024,
2182 alignment: 32,
2183 },
2184 MemoryConfiguration::ExclusivePages,
2185 Arc::new(ServerLogger::default()),
2186 options(),
2187 );
2188 let handles: Vec<_> = (0..5)
2190 .map(|i| memory_management.reserve(1000 * (i + 1)))
2191 .collect();
2192 let usage_before = memory_management.memory_usage();
2193 drop(handles);
2195 let _new_handles: Vec<_> = (0..5)
2197 .map(|i| memory_management.reserve(1000 * (i + 1)))
2198 .collect();
2199 let usage_after = memory_management.memory_usage();
2200 assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
2201 assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
2202 assert_eq!(usage_before.bytes_reserved, usage_after.bytes_reserved);
2203 }
2204}