1use super::{
2 MemoryConfiguration, MemoryPoolOptions, MemoryReport, MemoryUsage, PoolType,
3 memory_pool::{
4 DirectPool, ExclusiveMemoryPool, MemoryPool, PageMapping, PersistentPool, SlicedPool,
5 },
6};
7use crate::{
8 config::{
9 CubeClRuntimeConfig, RuntimeConfig,
10 memory::{
11 MemoryLogLevel, MemoryPoolConfig, MemoryPoolsConfig, MemoryPoolsPreset,
12 PersistentMemory,
13 },
14 },
15 logging::ServerLogger,
16 memory_management::{BytesFormat, memory_pool::Slice},
17 server::IoError,
18 storage::{ComputeStorage, StorageHandle},
19};
20
21use alloc::format;
22use alloc::string::{String, ToString};
23#[cfg(not(exclusive_memory_only))]
24use alloc::vec;
25use alloc::vec::Vec;
26use cubecl_environment::backtrace::BackTrace;
27use cubecl_environment::collections::HashSet;
28use cubecl_environment::sync::Arc;
29use cubecl_ir::MemoryDeviceProperties;
30
31pub use super::memory_pool::{ManagedMemoryBinding, handle::*};
32
33#[allow(clippy::large_enum_variant)]
36enum DynamicPool {
37 Sliced(SlicedPool),
38 Exclusive(ExclusiveMemoryPool),
39 Direct(DirectPool),
40}
41
42impl MemoryPool for DynamicPool {
43 fn accept(&self, size: u64) -> bool {
44 match self {
45 DynamicPool::Sliced(pool) => pool.accept(size),
46 DynamicPool::Exclusive(pool) => pool.accept(size),
47 DynamicPool::Direct(pool) => pool.accept(size),
48 }
49 }
50
51 fn find(&self, binding: &ManagedMemoryBinding) -> Result<&Slice, IoError> {
52 match self {
53 DynamicPool::Sliced(m) => m.find(binding),
54 DynamicPool::Exclusive(m) => m.find(binding),
55 DynamicPool::Direct(m) => m.find(binding),
56 }
57 }
58
59 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
60 fn try_reserve(&mut self, size: u64) -> Option<ManagedMemoryHandle> {
61 match self {
62 DynamicPool::Sliced(m) => m.try_reserve(size),
63 DynamicPool::Exclusive(m) => m.try_reserve(size),
64 DynamicPool::Direct(m) => m.try_reserve(size),
65 }
66 }
67
68 #[cfg_attr(
69 feature = "tracing",
70 tracing::instrument(level = "trace", skip(self, storage))
71 )]
72 fn alloc<Storage: ComputeStorage>(
73 &mut self,
74 storage: &mut Storage,
75 size: u64,
76 mapping: PageMapping,
77 ) -> Result<ManagedMemoryHandle, IoError> {
78 match self {
79 DynamicPool::Sliced(m) => m.alloc(storage, size, mapping),
80 DynamicPool::Exclusive(m) => m.alloc(storage, size, mapping),
81 DynamicPool::Direct(m) => m.alloc(storage, size, mapping),
82 }
83 }
84
85 fn materialize<Storage: ComputeStorage>(
86 &mut self,
87 storage: &mut Storage,
88 binding: &ManagedMemoryBinding,
89 ) -> Result<(), IoError> {
90 match self {
91 DynamicPool::Sliced(m) => m.materialize(storage, binding),
92 DynamicPool::Exclusive(m) => m.materialize(storage, binding),
93 DynamicPool::Direct(m) => m.materialize(storage, binding),
94 }
95 }
96
97 fn get_memory_usage(&self) -> MemoryUsage {
98 match self {
99 DynamicPool::Sliced(m) => m.get_memory_usage(),
100 DynamicPool::Exclusive(m) => m.get_memory_usage(),
101 DynamicPool::Direct(m) => m.get_memory_usage(),
102 }
103 }
104
105 fn cleanup<Storage: ComputeStorage>(
106 &mut self,
107 storage: &mut Storage,
108 alloc_nr: u64,
109 explicit: bool,
110 ) {
111 match self {
112 DynamicPool::Sliced(m) => m.cleanup(storage, alloc_nr, explicit),
113 DynamicPool::Exclusive(m) => m.cleanup(storage, alloc_nr, explicit),
114 DynamicPool::Direct(m) => m.cleanup(storage, alloc_nr, explicit),
115 };
116 storage.flush();
117 }
118
119 fn bind(
120 &mut self,
121 reserved: ManagedMemoryHandle,
122 assigned: ManagedMemoryHandle,
123 cursor: u64,
124 ) -> Result<(), IoError> {
125 match self {
126 DynamicPool::Sliced(m) => m.bind(reserved, assigned, cursor),
127 DynamicPool::Exclusive(m) => m.bind(reserved, assigned, cursor),
128 DynamicPool::Direct(m) => m.bind(reserved, assigned, cursor),
129 }
130 }
131}
132
133impl core::fmt::Display for DynamicPool {
134 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
135 match self {
136 DynamicPool::Sliced(pool) => write!(f, "{pool}"),
137 DynamicPool::Exclusive(pool) => write!(f, "{pool}"),
138 DynamicPool::Direct(pool) => write!(f, "{pool}"),
139 }
140 }
141}
142
143impl DynamicPool {
144 fn report(&self) -> super::MemoryPoolReport {
145 match self {
146 DynamicPool::Sliced(m) => m.report(),
147 DynamicPool::Exclusive(m) => m.report(),
148 DynamicPool::Direct(m) => m.report(),
149 }
150 }
151}
152
153#[derive(Default, Clone, Copy, Debug)]
154pub enum MemoryAllocationMode {
156 #[default]
158 Auto,
159 Persistent,
162}
163
164pub struct MemoryManagement<Storage> {
166 name: String,
167 persistent: PersistentPool,
168 pools: Vec<DynamicPool>,
169 capacity_warned: HashSet<usize>,
173 storage: Storage,
174 alloc_reserve_count: u64,
175 mode: MemoryAllocationMode,
176 persistent_windows: u64,
179 config: PersistentMemory,
180 logger: Arc<ServerLogger>,
181 capture: Option<CaptureState>,
183}
184
185struct CaptureState {
190 restore_mode: MemoryAllocationMode,
194 touched: HashSet<ManagedMemoryId>,
200 priming: bool,
203 primed: Vec<ManagedMemoryHandle>,
214}
215
216fn generate_bucket_sizes(
217 start_size: u64,
218 end_size: u64,
219 max_buckets: usize,
220 alignment: u64,
221) -> Vec<u64> {
222 let mut buckets = Vec::with_capacity(max_buckets);
223 let log_min = (start_size as f64).ln();
224 let log_max = (end_size as f64).ln();
225 let log_range = log_max - log_min;
226
227 for i in 0..max_buckets {
229 let p = i as f64 / (max_buckets - 1) as f64;
230 let log_size = log_min + log_range * p;
232 let size = log_size.exp() as u64;
233 let aligned_size = size.next_multiple_of(alignment);
234 buckets.push(aligned_size);
235 }
236
237 buckets.dedup();
238 buckets
239}
240
241const DEALLOC_SCALE_MB: u64 = 1024 * 1024 * 1024;
242const BASE_DEALLOC_PERIOD: u64 = 5000;
243
244#[derive(Debug)]
246pub struct MemoryManagementOptions {
247 name: String,
249 memory: MemoryAllocationOption,
251}
252
253impl MemoryManagementOptions {
254 pub fn new<S: Into<String>>(name: S) -> Self {
256 Self {
257 name: name.into(),
258 memory: MemoryAllocationOption::FromConfig,
259 }
260 }
261
262 pub fn mode(mut self, mode: MemoryAllocationMode) -> Self {
264 self.memory = MemoryAllocationOption::Provided(mode);
265 self
266 }
267}
268
269#[derive(Default, Debug)]
270enum MemoryAllocationOption {
272 #[default]
273 FromConfig,
275 Provided(MemoryAllocationMode),
277}
278
279#[derive(Debug, Clone, PartialEq, Eq)]
281pub enum PoolConfigError {
282 EmptyPoolList,
284 ZeroSize {
286 field: &'static str,
288 },
289 SliceLargerThanPage {
291 page_size: u64,
293 max_slice_size: u64,
295 },
296 CapSmallerThanPage {
298 page_size: u64,
300 max_pool_size: u64,
302 },
303 TooManyPages {
305 pages: u64,
307 },
308 TooManyPools {
310 count: usize,
312 },
313 PresetUnavailable {
315 preset: &'static str,
317 },
318 SlicedPoolsUnavailable,
320}
321
322impl core::fmt::Display for PoolConfigError {
323 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
324 match self {
325 PoolConfigError::EmptyPoolList => write!(f, "the pool list is empty"),
326 PoolConfigError::ZeroSize { field } => write!(f, "`{field}` must be non-zero"),
327 PoolConfigError::SliceLargerThanPage {
328 page_size,
329 max_slice_size,
330 } => write!(
331 f,
332 "`max_slice_size` ({max_slice_size}) exceeds `page_size` ({page_size}); a slice can never span pages"
333 ),
334 PoolConfigError::CapSmallerThanPage {
335 page_size,
336 max_pool_size,
337 } => write!(
338 f,
339 "`max_pool_size` ({max_pool_size}) is smaller than `page_size` ({page_size}); the cap can't fit a single page"
340 ),
341 PoolConfigError::TooManyPages { pages } => write!(
342 f,
343 "`max_pool_size` spans {pages} pages of `page_size`, exceeding the maximum of {}; increase `page_size` or lower the cap",
344 u16::MAX
345 ),
346 PoolConfigError::TooManyPools { count } => write!(
347 f,
348 "the pool list has {count} entries, exceeding the maximum of {} dynamic pools",
349 PERSISTENT_POOL_POS - 1
350 ),
351 PoolConfigError::PresetUnavailable { preset } => {
352 write!(f, "the `{preset}` preset is not available in this build")
353 }
354 PoolConfigError::SlicedPoolsUnavailable => {
355 write!(
356 f,
357 "sliced pools are not available in this build (exclusive memory only)"
358 )
359 }
360 }
361 }
362}
363
364impl core::error::Error for PoolConfigError {}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372pub enum InstallMemoryPoolsError {
373 PoolsInUse {
381 bytes_in_use: u64,
383 },
384 StreamUnavailable,
388 Unsupported,
391}
392
393impl core::fmt::Display for InstallMemoryPoolsError {
394 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
395 match self {
396 InstallMemoryPoolsError::PoolsInUse { bytes_in_use } => write!(
397 f,
398 "the dynamic pools kept their layout: {bytes_in_use} bytes are still live in them"
399 ),
400 InstallMemoryPoolsError::StreamUnavailable => write!(
401 f,
402 "the calling stream kept its layout: it is already in an error state"
403 ),
404 InstallMemoryPoolsError::Unsupported => {
405 write!(f, "this server has no configurable dynamic memory pools")
406 }
407 }
408 }
409}
410
411impl core::error::Error for InstallMemoryPoolsError {}
412
413impl MemoryConfiguration {
414 pub fn resolve(
430 self,
431 pools: Option<&MemoryPoolsConfig>,
432 properties: &MemoryDeviceProperties,
433 ) -> Result<Self, PoolConfigError> {
434 let Some(pools) = pools else {
435 return Ok(self);
436 };
437
438 match pools {
439 MemoryPoolsConfig::Preset(MemoryPoolsPreset::SubSlices) => {
440 #[cfg(exclusive_memory_only)]
441 {
442 Err(PoolConfigError::PresetUnavailable {
443 preset: "sub-slices",
444 })
445 }
446 #[cfg(not(exclusive_memory_only))]
447 {
448 Ok(MemoryConfiguration::SubSlices)
449 }
450 }
451 MemoryPoolsConfig::Preset(MemoryPoolsPreset::ExclusivePages) => {
452 Ok(MemoryConfiguration::ExclusivePages)
453 }
454 MemoryPoolsConfig::Explicit(entries) => {
455 if entries.is_empty() {
456 return Err(PoolConfigError::EmptyPoolList);
457 }
458 if entries.len() >= PERSISTENT_POOL_POS as usize {
463 return Err(PoolConfigError::TooManyPools {
464 count: entries.len(),
465 });
466 }
467 let pool_options = entries
468 .iter()
469 .map(|entry| pool_options_from_entry(entry, properties))
470 .collect::<Result<Vec<_>, _>>()?;
471 Ok(MemoryConfiguration::Custom { pool_options })
472 }
473 }
474 }
475}
476
477fn pool_options_from_entry(
480 entry: &MemoryPoolConfig,
481 properties: &MemoryDeviceProperties,
482) -> Result<MemoryPoolOptions, PoolConfigError> {
483 let alignment = properties.alignment.max(1);
484 match entry {
485 MemoryPoolConfig::Exclusive {
486 max_alloc_size,
487 dealloc_period,
488 } => {
489 let max_alloc_size = max_alloc_size.bytes().next_multiple_of(alignment);
492 Ok(MemoryPoolOptions {
493 pool_type: PoolType::ExclusivePages { max_alloc_size },
494 dealloc_period: *dealloc_period,
495 })
496 }
497 MemoryPoolConfig::Direct { reclaim_at } => Ok(MemoryPoolOptions {
498 pool_type: PoolType::Direct {
499 reclaim_at: reclaim_at.map(|size| size.bytes()),
500 },
501 dealloc_period: None,
504 }),
505 #[cfg(exclusive_memory_only)]
510 MemoryPoolConfig::Sliced { .. } => Err(PoolConfigError::SlicedPoolsUnavailable),
511 #[cfg(not(exclusive_memory_only))]
512 MemoryPoolConfig::Sliced {
513 page_size,
514 max_slice_size,
515 max_pool_size,
516 dealloc_period,
517 } => {
518 if page_size.bytes() == 0 {
519 return Err(PoolConfigError::ZeroSize { field: "page_size" });
520 }
521
522 let page_size = page_size.bytes().next_multiple_of(alignment);
523 let max_slice_size = match max_slice_size {
524 Some(size) if size.bytes() == 0 => {
525 return Err(PoolConfigError::ZeroSize {
526 field: "max_slice_size",
527 });
528 }
529 Some(size) => size.bytes().next_multiple_of(alignment),
530 None => page_size,
531 };
532 if max_slice_size > page_size {
533 return Err(PoolConfigError::SliceLargerThanPage {
534 page_size,
535 max_slice_size,
536 });
537 }
538 if let Some(cap) = max_pool_size {
539 let cap = cap.bytes();
540 if cap == 0 {
541 return Err(PoolConfigError::ZeroSize {
542 field: "max_pool_size",
543 });
544 }
545 if cap < page_size {
546 return Err(PoolConfigError::CapSmallerThanPage {
547 page_size,
548 max_pool_size: cap,
549 });
550 }
551 let pages = cap / page_size;
552 if pages > u16::MAX as u64 {
553 return Err(PoolConfigError::TooManyPages { pages });
554 }
555 }
556
557 Ok(MemoryPoolOptions {
558 pool_type: PoolType::SlicedPages {
559 page_size,
560 max_slice_size,
561 max_pool_size: max_pool_size.map(|size| size.bytes()),
562 },
563 dealloc_period: *dealloc_period,
564 })
565 }
566 }
567}
568
569const PERSISTENT_POOL_POS: u8 = u8::MAX;
575
576fn build_pools(
580 properties: &MemoryDeviceProperties,
581 config: MemoryConfiguration,
582 logger: &Arc<ServerLogger>,
583 name: &str,
584) -> Vec<DynamicPool> {
585 let pool_options = match config {
586 #[cfg(not(exclusive_memory_only))]
587 MemoryConfiguration::SubSlices => {
588 let memory_alignment = properties.alignment;
590 let max_page = properties.max_page_size;
591 let mut pools = Vec::new();
592
593 const MB: u64 = 1024 * 1024;
594
595 pools.push(MemoryPoolOptions {
598 pool_type: PoolType::ExclusivePages { max_alloc_size: 0 },
599 dealloc_period: None,
600 });
601
602 let mut current = max_page;
603 let mut max_sizes = vec![];
604 let mut page_sizes = vec![];
605 let mut base = pools.len() as u32;
606
607 while current >= 32 * MB {
608 current /= 4;
609
610 current = current.next_multiple_of(memory_alignment);
612
613 max_sizes.push(current / 2u64.pow(base));
614 page_sizes.push(current);
615 base += 1;
616 }
617
618 max_sizes.reverse();
619 page_sizes.reverse();
620
621 for i in 0..max_sizes.len() {
622 let max = max_sizes[i];
623 let page_size = page_sizes[i];
624
625 pools.push(MemoryPoolOptions {
626 pool_type: PoolType::SlicedPages {
628 page_size,
629 max_slice_size: max,
630 max_pool_size: None,
631 },
632 dealloc_period: None,
633 });
634 }
635
636 let max_alloc = max_page / memory_alignment * memory_alignment;
644 let dealloc_period = (BASE_DEALLOC_PERIOD as f64
645 * (1.0 + max_alloc as f64 / (DEALLOC_SCALE_MB as f64)).round())
646 as u64;
647 pools.push(MemoryPoolOptions {
648 pool_type: PoolType::ExclusivePages {
649 max_alloc_size: max_alloc,
650 },
651 dealloc_period: Some(dealloc_period),
652 });
653 pools
654 }
655 MemoryConfiguration::ExclusivePages => {
656 const MIN_BUCKET_SIZE: u64 = 1024 * 32;
660 const NUM_POOLS: usize = 24;
661
662 let sizes = generate_bucket_sizes(
663 MIN_BUCKET_SIZE,
664 properties.max_page_size,
665 NUM_POOLS,
666 properties.alignment,
667 );
668
669 sizes
670 .iter()
671 .map(|&size| {
672 let dealloc_period = (BASE_DEALLOC_PERIOD as f64
673 * (1.0 + size as f64 / (DEALLOC_SCALE_MB as f64)).round())
674 as u64;
675
676 MemoryPoolOptions {
677 pool_type: PoolType::ExclusivePages {
678 max_alloc_size: size,
679 },
680 dealloc_period: Some(dealloc_period),
681 }
682 })
683 .collect()
684 }
685 MemoryConfiguration::Custom { pool_options } => pool_options,
686 };
687
688 logger.log_memory(
689 |level| !matches!(level, MemoryLogLevel::Disabled),
690 || {
691 let mut msg = String::new();
692 for pool in pool_options.iter() {
693 msg += &format!("[{name}] Using memory pool: \n {pool:?}\n");
694 }
695 msg
696 },
697 );
698
699 assert!(
700 pool_options.len() < PERSISTENT_POOL_POS as usize,
701 "at most {} dynamic pools are supported",
702 PERSISTENT_POOL_POS - 1
703 );
704
705 pool_options
706 .iter()
707 .enumerate()
708 .map(|(pool_pos, pool)| {
709 let pool_pos = pool_pos as u8;
710
711 match pool.pool_type {
712 PoolType::SlicedPages {
713 page_size,
714 max_slice_size,
715 max_pool_size,
716 } => DynamicPool::Sliced(SlicedPool::new(
717 page_size,
718 max_slice_size,
719 properties.alignment,
720 pool_pos,
721 max_pool_size,
722 )),
723 PoolType::ExclusivePages { max_alloc_size } => {
724 DynamicPool::Exclusive(ExclusiveMemoryPool::new(
725 max_alloc_size,
726 properties.alignment,
727 pool.dealloc_period.unwrap_or(u64::MAX),
728 pool_pos,
729 ))
730 }
731 PoolType::Direct { reclaim_at } => {
732 DynamicPool::Direct(DirectPool::new(properties.alignment, pool_pos, reclaim_at))
733 }
734 }
735 })
736 .collect()
737}
738
739impl<Storage: ComputeStorage> MemoryManagement<Storage> {
740 pub fn from_configuration(
742 storage: Storage,
743 properties: &MemoryDeviceProperties,
744 config: MemoryConfiguration,
745 logger: Arc<ServerLogger>,
746 options: MemoryManagementOptions,
747 ) -> Self {
748 let pools = build_pools(properties, config, &logger, &options.name);
749
750 let config = CubeClRuntimeConfig::get().memory.persistent_memory.clone();
751
752 let mode = match options.memory {
753 MemoryAllocationOption::Provided(mode) => mode,
754 MemoryAllocationOption::FromConfig => match config {
755 PersistentMemory::Enabled | PersistentMemory::SizeMatch => {
756 MemoryAllocationMode::Auto
757 }
758 PersistentMemory::Disabled => MemoryAllocationMode::Auto,
759 PersistentMemory::Enforced => MemoryAllocationMode::Persistent,
760 },
761 };
762
763 Self {
764 name: options.name,
765 persistent: PersistentPool::new(
766 properties.max_page_size,
767 properties.alignment,
768 PERSISTENT_POOL_POS,
769 ),
770 pools,
771 capacity_warned: HashSet::new(),
772 storage,
773 alloc_reserve_count: 0,
774 mode,
775 persistent_windows: 0,
776 config,
777 logger,
778 capture: None,
779 }
780 }
781
782 pub fn install_pools(
806 &mut self,
807 config: MemoryConfiguration,
808 properties: &MemoryDeviceProperties,
809 ) -> Result<(), InstallMemoryPoolsError> {
810 self.cleanup(true);
811
812 let dynamic_in_use: u64 = self
815 .pools
816 .iter()
817 .map(|pool| pool.get_memory_usage().bytes_in_use)
818 .sum();
819 if dynamic_in_use > 0 {
820 return Err(InstallMemoryPoolsError::PoolsInUse {
821 bytes_in_use: dynamic_in_use,
822 });
823 }
824
825 self.pools = build_pools(properties, config, &self.logger, &self.name);
826 self.capacity_warned.clear();
829 Ok(())
830 }
831
832 pub fn capture_begin(&mut self) {
845 if self.capture.is_none() {
846 self.capture = Some(CaptureState {
847 restore_mode: self.mode,
848 touched: HashSet::new(),
849 priming: true,
850 primed: Vec::new(),
851 });
852 }
853 self.mode = MemoryAllocationMode::Persistent;
854 }
855
856 pub fn capture_priming_end(&mut self) {
863 if let Some(capture) = &mut self.capture {
864 capture.priming = false;
865 capture.primed.clear();
868 }
869 }
870
871 pub fn capture_end(&mut self) -> Vec<ManagedMemoryHandle> {
880 match self.capture.take() {
881 Some(capture) => {
882 self.mode = capture.restore_mode;
883 self.persistent.retain_touched(&capture.touched)
884 }
885 None => Vec::new(),
886 }
887 }
888
889 pub fn mode(&mut self, mode: MemoryAllocationMode) {
901 let mode = match self.config {
903 PersistentMemory::Enabled | PersistentMemory::SizeMatch => mode,
904 PersistentMemory::Disabled | PersistentMemory::Enforced => return,
905 };
906
907 match mode {
908 MemoryAllocationMode::Persistent => self.persistent_windows += 1,
909 MemoryAllocationMode::Auto => {
910 self.persistent_windows = self.persistent_windows.saturating_sub(1)
911 }
912 }
913 let mode = match self.persistent_windows > 0 {
914 true => MemoryAllocationMode::Persistent,
915 false => MemoryAllocationMode::Auto,
916 };
917
918 self.logger.log_memory(
919 |level| !matches!(level, MemoryLogLevel::Disabled),
920 || {
921 format!(
922 "[{}] Setting memory allocation mode: from {:?} => {mode:?}",
923 self.name, self.mode
924 )
925 },
926 );
927
928 match &mut self.capture {
932 Some(capture) => capture.restore_mode = mode,
933 None => self.mode = mode,
934 }
935 }
936
937 pub fn cleanup(&mut self, explicit: bool) {
939 self.logger.log_memory(
940 |level| !matches!(level, MemoryLogLevel::Disabled) && explicit,
941 || "Manual memory cleanup ...".to_string(),
942 );
943
944 if self.capture.is_some() {
951 return;
952 }
953
954 self.persistent
955 .cleanup(&mut self.storage, self.alloc_reserve_count, explicit);
956
957 for pool in self.pools.iter_mut() {
958 pool.cleanup(&mut self.storage, self.alloc_reserve_count, explicit);
959 }
960
961 if explicit {
965 self.storage.flush();
966 }
967 }
968
969 pub fn get_cursor(&self, binding: ManagedMemoryBinding) -> Result<u64, IoError> {
971 let slice = self.find(binding)?;
972 Ok(slice.cursor)
973 }
974
975 fn find(&self, binding: ManagedMemoryBinding) -> Result<&Slice, IoError> {
977 let id = binding.descriptor();
978
979 if id.location().init == 0 {
980 return Err(IoError::NotFound {
981 backtrace: BackTrace::capture(),
982 reason: "Memory location was never initialized".into(),
983 });
984 }
985
986 let slice = if id.location().pool == PERSISTENT_POOL_POS {
987 self.persistent.find(&binding)?
988 } else {
989 let pool =
990 self.pools
991 .get(id.location().pool as usize)
992 .ok_or_else(|| IoError::NotFound {
993 backtrace: BackTrace::capture(),
994 reason: format!("Pool {} doesn't exist", id.location().pool).into(),
995 })?;
996
997 pool.find(&binding)?
998 };
999
1000 if slice.handle.descriptor() != binding.descriptor() {
1004 return Err(IoError::NotFound {
1005 backtrace: BackTrace::capture(),
1006 reason: "Memory location points to a different allocation".into(),
1007 });
1008 }
1009
1010 Ok(slice)
1011 }
1012
1013 pub fn get_storage(&mut self, binding: ManagedMemoryBinding) -> Result<StorageHandle, IoError> {
1020 self.materialize(&binding)?;
1021 let slice = self.find(binding)?;
1022 Ok(slice.storage.clone())
1023 }
1024
1025 fn materialize(&mut self, binding: &ManagedMemoryBinding) -> Result<(), IoError> {
1029 let location = binding.descriptor().location();
1030 if location.init == 0 {
1031 return Ok(());
1032 }
1033 match location.pool {
1034 PERSISTENT_POOL_POS => self.persistent.materialize(&mut self.storage, binding),
1035 pool => match self.pools.get_mut(pool as usize) {
1036 Some(pool) => pool.materialize(&mut self.storage, binding),
1037 None => Ok(()),
1038 },
1039 }
1040 }
1041
1042 pub fn get_resource(
1044 &mut self,
1045 binding: ManagedMemoryBinding,
1046 offset_start: Option<u64>,
1047 offset_end: Option<u64>,
1048 ) -> Result<Storage::Resource, IoError> {
1049 let handle = self.get_storage(binding)?;
1050
1051 let handle = match offset_start {
1052 Some(offset) => handle.offset_start(offset),
1053 None => handle,
1054 };
1055 let handle = match offset_end {
1056 Some(offset) => handle.offset_end(offset),
1057 None => handle,
1058 };
1059 self.storage().get(&handle)
1060 }
1061
1062 fn capture_touch(&mut self, handle: &ManagedMemoryHandle) {
1072 if let Some(capture) = &mut self.capture {
1073 capture.touched.insert(handle.descriptor().id);
1074 if capture.priming {
1075 capture.primed.push(handle.clone());
1078 }
1079 }
1080 }
1081
1082 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
1084 pub fn reserve(&mut self, size: u64) -> Result<ManagedMemoryHandle, IoError> {
1085 self.alloc_reserve_count += 1;
1088
1089 self.cleanup(false);
1095
1096 let mapping = PageMapping::current();
1097
1098 let persistent_mode = matches!(self.mode, MemoryAllocationMode::Persistent);
1105 let size_match = matches!(self.config, PersistentMemory::SizeMatch);
1106
1107 if (persistent_mode || size_match)
1108 && let Some(val) = self.persistent.try_reserve(size)
1109 {
1110 self.logger.log_memory(
1111 |level| matches!(level, MemoryLogLevel::Full),
1112 || {
1113 format!(
1114 "[{}] Reserved memory {size} using persistent memory",
1115 self.name
1116 )
1117 },
1118 );
1119 self.capture_touch(&val);
1120 return Ok(val);
1121 }
1122
1123 if persistent_mode || (size_match && self.persistent.has_size(size)) {
1124 let allocated = self.persistent.alloc(&mut self.storage, size, mapping);
1125
1126 self.logger.log_memory(
1127 |level| !matches!(level, MemoryLogLevel::Disabled),
1128 || {
1129 format!(
1130 "[{}] Allocated a new memory page using persistent memory, \n{}",
1131 self.name, self,
1132 )
1133 },
1134 );
1135 if let Ok(handle) = &allocated {
1136 self.capture_touch(handle);
1137 }
1138 return allocated;
1139 }
1140
1141 self.logger.log_memory(
1142 |level| matches!(level, MemoryLogLevel::Full),
1143 || {
1144 format!(
1145 "[{}] Reserved memory {} using dynamic pool",
1146 self.name,
1147 BytesFormat::new(size)
1148 )
1149 },
1150 );
1151
1152 let mut capacity_exceeded = None;
1161 let mut reserved = None;
1162
1163 for (index, pool) in self
1164 .pools
1165 .iter_mut()
1166 .enumerate()
1167 .filter(|(_, pool)| pool.accept(size))
1168 {
1169 if let Some(slice) = pool.try_reserve(size) {
1170 return Ok(slice);
1171 }
1172
1173 match pool.alloc(&mut self.storage, size, mapping) {
1174 Ok(handle) => {
1175 reserved = Some(handle);
1176 break;
1177 }
1178 Err(err @ IoError::PoolCapacityExceeded { .. }) => {
1179 if self.capacity_warned.insert(index) {
1187 log::warn!(
1188 "[{}] memory pool {index} is at capacity (first hit at an \
1189 allocation of {size} B); spilling to the next accepting pool. \
1190 The measured plan is short for this workload.",
1191 self.name
1192 );
1193 }
1194 capacity_exceeded = Some(err);
1195 }
1196 Err(err) => return Err(capacity_exceeded.unwrap_or(err)),
1200 }
1201 }
1202
1203 let Some(reserved) = reserved else {
1204 return Err(capacity_exceeded.unwrap_or_else(|| IoError::BufferTooBig {
1205 size,
1206 backtrace: BackTrace::capture(),
1207 }));
1208 };
1209
1210 self.logger.log_memory(
1211 |level| matches!(level, MemoryLogLevel::Full),
1212 || {
1213 format!(
1214 "[{}], Allocated a new memory page, current usage: \n{}",
1215 self.name, self
1216 )
1217 },
1218 );
1219
1220 Ok(reserved)
1221 }
1222
1223 pub fn storage(&mut self) -> &mut Storage {
1234 &mut self.storage
1235 }
1236
1237 pub fn memory_usage(&self) -> MemoryUsage {
1239 let memory_usage = self.pools.iter().map(|x| x.get_memory_usage()).fold(
1240 MemoryUsage {
1241 number_allocs: 0,
1242 bytes_in_use: 0,
1243 bytes_padding: 0,
1244 bytes_reserved: 0,
1245 },
1246 |m1, m2| m1.combine(m2),
1247 );
1248 memory_usage.combine(self.persistent.get_memory_usage())
1249 }
1250
1251 pub fn memory_report(&self) -> MemoryReport {
1257 MemoryReport {
1258 dynamic: self.pools.iter().map(|pool| pool.report()).collect(),
1259 persistent: self.persistent.report(),
1260 }
1261 }
1262
1263 pub fn print_memory_usage(&self) {
1265 #[cfg(feature = "std")]
1266 log::info!("{}", self.memory_usage());
1267 }
1268
1269 pub fn bind(
1271 &mut self,
1272 reserved: ManagedMemoryHandle,
1273 assigned: ManagedMemoryHandle,
1274 cursor: u64,
1275 ) -> Result<(), IoError> {
1276 let descriptor = reserved.descriptor();
1277
1278 if descriptor.location().init == 0 {
1279 return Err(IoError::NotFound {
1280 backtrace: BackTrace::capture(),
1281 reason: "Reserved memory isn't initialized".into(),
1282 });
1283 }
1284
1285 let pool_index = descriptor.location().pool as usize;
1286 if pool_index == PERSISTENT_POOL_POS as usize {
1287 self.capture_touch(&assigned);
1291 return self.persistent.bind(reserved, assigned, cursor);
1292 }
1293
1294 self.pools
1295 .get_mut(pool_index)
1296 .map(|p| p.bind(reserved, assigned, cursor))
1297 .ok_or_else(|| IoError::NotFound {
1298 backtrace: BackTrace::capture(),
1299 reason: format!("Memory pool {} doesn't exist", pool_index).into(),
1300 })?
1301 }
1302}
1303
1304impl<Storage: ComputeStorage> core::fmt::Display for MemoryManagement<Storage> {
1305 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1306 f.write_str("\n# MemoryManagement\n\n")?;
1307 f.write_fmt(format_args!(" - name: {:?}\n", self.name))?;
1308 f.write_fmt(format_args!("\n## Persistent\n\n{}", self.persistent))?;
1309 f.write_str("\n## Dynamic\n\n")?;
1310
1311 for pool in self.pools.iter() {
1312 f.write_fmt(format_args!("{pool}\n"))?;
1313 }
1314 let memory_usage = self.memory_usage();
1315 f.write_fmt(format_args!("\n## Summary\n\n{memory_usage}"))?;
1316
1317 Ok(())
1318 }
1319}
1320
1321impl<Storage> core::fmt::Debug for MemoryManagement<Storage> {
1322 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1323 f.write_str(
1324 alloc::format!(
1325 "DynamicMemoryManagement {:?}",
1326 core::any::type_name::<Storage>(),
1327 )
1328 .as_str(),
1329 )
1330 }
1331}
1332
1333#[cfg(test)]
1334mod tests {
1335 use super::*;
1336 use crate::{memory_management::MemoryManagement, storage::BytesStorage};
1337 use alloc::vec;
1338
1339 const DUMMY_MEM_PROPS: MemoryDeviceProperties = MemoryDeviceProperties {
1340 max_page_size: 128 * 1024 * 1024,
1341 alignment: 32,
1342 };
1343
1344 fn options() -> MemoryManagementOptions {
1345 MemoryManagementOptions {
1346 name: "test".into(),
1347 memory: MemoryAllocationOption::FromConfig,
1348 }
1349 }
1350
1351 #[test_log::test]
1353 #[cfg(not(exclusive_memory_only))]
1354 fn test_handle_mutability() {
1355 let mut memory_management = MemoryManagement::from_configuration(
1356 BytesStorage::default(),
1357 &DUMMY_MEM_PROPS,
1358 MemoryConfiguration::SubSlices,
1359 Arc::new(ServerLogger::default()),
1360 options(),
1361 );
1362 let handle = memory_management.reserve(10).unwrap();
1363 let other_ref = handle.clone();
1364 assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
1365 drop(other_ref);
1366 assert!(handle.can_mut(), "Handle should be mut when only one ref.");
1367 }
1368
1369 #[test_log::test]
1371 #[cfg(not(exclusive_memory_only))]
1372 fn test_memory_usage() {
1373 let max_page_size = 512;
1374
1375 let mut memory_management = MemoryManagement::from_configuration(
1376 BytesStorage::default(),
1377 &DUMMY_MEM_PROPS,
1378 MemoryConfiguration::Custom {
1379 pool_options: vec![MemoryPoolOptions {
1380 pool_type: PoolType::ExclusivePages {
1381 max_alloc_size: max_page_size,
1382 },
1383 dealloc_period: None,
1384 }],
1385 },
1386 Arc::new(ServerLogger::default()),
1387 options(),
1388 );
1389 let handle = memory_management.reserve(100);
1390 let usage = memory_management.memory_usage();
1391
1392 assert_eq!(usage.bytes_in_use, 100);
1393 assert!(usage.bytes_reserved >= 100 && usage.bytes_reserved <= max_page_size);
1394
1395 drop(handle);
1397 let _handle = memory_management.reserve(100);
1398 let usage_new = memory_management.memory_usage();
1399 assert_eq!(usage, usage_new);
1400 }
1401
1402 #[test_log::test]
1403 fn find_uninit_binding_returns_not_found() {
1404 let mut memory_management = MemoryManagement::from_configuration(
1405 BytesStorage::default(),
1406 &DUMMY_MEM_PROPS,
1407 MemoryConfiguration::Custom {
1408 pool_options: vec![MemoryPoolOptions {
1409 pool_type: PoolType::SlicedPages {
1410 page_size: 2048,
1411 max_slice_size: 2048,
1412 max_pool_size: None,
1413 },
1414 dealloc_period: None,
1415 }],
1416 },
1417 Arc::new(ServerLogger::default()),
1418 options(),
1419 );
1420
1421 let _live = memory_management.reserve(512).unwrap();
1424
1425 let binding = ManagedMemoryHandle::new().binding();
1426 assert!(matches!(
1427 memory_management.get_cursor(binding),
1428 Err(IoError::NotFound { .. })
1429 ));
1430 }
1431
1432 #[test_log::test]
1433 fn find_stale_descriptor_returns_not_found() {
1434 let mut memory_management = MemoryManagement::from_configuration(
1435 BytesStorage::default(),
1436 &DUMMY_MEM_PROPS,
1437 MemoryConfiguration::Custom {
1438 pool_options: vec![MemoryPoolOptions {
1439 pool_type: PoolType::SlicedPages {
1440 page_size: 2048,
1441 max_slice_size: 2048,
1442 max_pool_size: None,
1443 },
1444 dealloc_period: None,
1445 }],
1446 },
1447 Arc::new(ServerLogger::default()),
1448 options(),
1449 );
1450
1451 let reserved = memory_management.reserve(512).unwrap();
1452 let stale = reserved.clone();
1453 let assigned = ManagedMemoryHandle::new();
1454 let assigned_binding = assigned.clone().binding();
1455
1456 memory_management.bind(reserved, assigned, 0).unwrap();
1457
1458 assert!(matches!(
1461 memory_management.get_cursor(stale.binding()),
1462 Err(IoError::NotFound { .. })
1463 ));
1464 assert!(memory_management.get_cursor(assigned_binding).is_ok());
1465 }
1466
1467 #[test_log::test]
1468 fn held_binding_survives_explicit_cleanup_renumber() {
1469 let mut memory_management = MemoryManagement::from_configuration(
1470 BytesStorage::default(),
1471 &DUMMY_MEM_PROPS,
1472 MemoryConfiguration::Custom {
1473 pool_options: vec![MemoryPoolOptions {
1474 pool_type: PoolType::ExclusivePages {
1475 max_alloc_size: 1024,
1476 },
1477 dealloc_period: None,
1478 }],
1479 },
1480 Arc::new(ServerLogger::default()),
1481 options(),
1482 );
1483
1484 let handle_a = memory_management.reserve(1024).unwrap();
1485 let handle_b = memory_management.reserve(1024).unwrap();
1486 let handle_c = memory_management.reserve(1024).unwrap();
1487
1488 let binding_b = handle_b.binding();
1489 drop(handle_a);
1490 drop(handle_c);
1491
1492 memory_management.cleanup(true);
1494
1495 assert!(memory_management.get_cursor(binding_b.clone()).is_ok());
1496 assert!(memory_management.get_storage(binding_b).is_ok());
1497 assert_eq!(memory_management.memory_usage().bytes_reserved, 1024);
1498 }
1499
1500 fn capped_sliced_config(page_size: u64, max_pool_size: Option<u64>) -> MemoryConfiguration {
1501 MemoryConfiguration::Custom {
1502 pool_options: vec![MemoryPoolOptions {
1503 pool_type: PoolType::SlicedPages {
1504 page_size,
1505 max_slice_size: page_size,
1506 max_pool_size,
1507 },
1508 dealloc_period: None,
1509 }],
1510 }
1511 }
1512
1513 #[test_log::test]
1514 fn capped_sliced_pool_errors_instead_of_growing() {
1515 let mut memory_management = MemoryManagement::from_configuration(
1516 BytesStorage::default(),
1517 &DUMMY_MEM_PROPS,
1518 capped_sliced_config(1024, Some(2048)),
1519 Arc::new(ServerLogger::default()),
1520 options(),
1521 );
1522
1523 let _a = memory_management.reserve(1024).unwrap();
1524 let _b = memory_management.reserve(1024).unwrap();
1525
1526 let result = memory_management.reserve(1024);
1527 assert!(matches!(
1528 result,
1529 Err(IoError::PoolCapacityExceeded { capacity: 2048, .. })
1530 ));
1531 assert_eq!(
1532 memory_management.memory_usage().bytes_reserved,
1533 2048,
1534 "a failed reservation must not grow the pool"
1535 );
1536 }
1537
1538 #[test_log::test]
1539 fn capped_sliced_pool_reuses_freed_memory() {
1540 let mut memory_management = MemoryManagement::from_configuration(
1541 BytesStorage::default(),
1542 &DUMMY_MEM_PROPS,
1543 capped_sliced_config(1024, Some(2048)),
1544 Arc::new(ServerLogger::default()),
1545 options(),
1546 );
1547
1548 let handle_a = memory_management.reserve(1024).unwrap();
1549 let _b = memory_management.reserve(1024).unwrap();
1550 drop(handle_a);
1551
1552 let _c = memory_management.reserve(1024).unwrap();
1555 assert_eq!(memory_management.memory_usage().bytes_reserved, 2048);
1556 }
1557
1558 #[test_log::test]
1559 fn capped_lazy_pool_cleanup_still_frees() {
1560 let mut memory_management = MemoryManagement::from_configuration(
1561 BytesStorage::default(),
1562 &DUMMY_MEM_PROPS,
1563 capped_sliced_config(1024, Some(2048)),
1564 Arc::new(ServerLogger::default()),
1565 options(),
1566 );
1567
1568 let handle_a = memory_management.reserve(1024).unwrap();
1569 let handle_b = memory_management.reserve(1024).unwrap();
1570 drop(handle_a);
1571 drop(handle_b);
1572 memory_management.cleanup(true);
1573 assert_eq!(memory_management.memory_usage().bytes_reserved, 0);
1574
1575 let _a = memory_management.reserve(1024).unwrap();
1577 let _b = memory_management.reserve(1024).unwrap();
1578 assert!(matches!(
1579 memory_management.reserve(1024),
1580 Err(IoError::PoolCapacityExceeded { .. })
1581 ));
1582 }
1583
1584 #[test_log::test]
1585 fn max_pool_size_smaller_than_page_shrinks_page() {
1586 let mut memory_management = MemoryManagement::from_configuration(
1587 BytesStorage::default(),
1588 &DUMMY_MEM_PROPS,
1589 capped_sliced_config(2048, Some(512)),
1590 Arc::new(ServerLogger::default()),
1591 options(),
1592 );
1593
1594 let _small = memory_management.reserve(256).unwrap();
1595 assert!(memory_management.memory_usage().bytes_reserved <= 512);
1596
1597 assert!(memory_management.reserve(1024).is_err());
1599 assert!(memory_management.memory_usage().bytes_reserved <= 512);
1600 }
1601
1602 #[test_log::test]
1603 fn max_pool_size_below_alignment_never_overshoots() {
1604 let mut memory_management = MemoryManagement::from_configuration(
1608 BytesStorage::default(),
1609 &DUMMY_MEM_PROPS,
1610 capped_sliced_config(1024, Some(16)),
1611 Arc::new(ServerLogger::default()),
1612 options(),
1613 );
1614
1615 assert!(matches!(
1616 memory_management.reserve(8),
1617 Err(IoError::PoolCapacityExceeded { .. })
1618 ));
1619 assert_eq!(memory_management.memory_usage().bytes_reserved, 0);
1620 }
1621
1622 #[test_log::test]
1628 fn persistent_windows_nest() {
1629 let mut memory_management = MemoryManagement::from_configuration(
1630 BytesStorage::default(),
1631 &DUMMY_MEM_PROPS,
1632 MemoryConfiguration::Custom {
1633 pool_options: vec![MemoryPoolOptions {
1634 pool_type: PoolType::SlicedPages {
1635 page_size: 4096,
1636 max_slice_size: 4096,
1637 max_pool_size: None,
1638 },
1639 dealloc_period: None,
1640 }],
1641 },
1642 Arc::new(ServerLogger::default()),
1643 options(),
1644 );
1645
1646 memory_management.mode(MemoryAllocationMode::Persistent); memory_management.mode(MemoryAllocationMode::Persistent); memory_management.mode(MemoryAllocationMode::Auto); let weight = memory_management.reserve(1024).unwrap();
1652 let report = memory_management.memory_report();
1653 assert_eq!(
1654 report.persistent.usage.bytes_in_use, 1024,
1655 "an allocation inside the outer window is persistent"
1656 );
1657 assert_eq!(
1658 report.dynamic[0].pages_peak, 0,
1659 "nothing leaked into the dynamic pools"
1660 );
1661
1662 memory_management.mode(MemoryAllocationMode::Auto);
1664 let _transient = memory_management.reserve(1024).unwrap();
1665 assert_eq!(memory_management.memory_report().dynamic[0].pages_peak, 1);
1666
1667 drop(weight);
1668 }
1669
1670 #[test_log::test]
1679 fn capacity_overflow_falls_through_to_later_accepting_pool() {
1680 let mut memory_management = MemoryManagement::from_configuration(
1681 BytesStorage::default(),
1682 &DUMMY_MEM_PROPS,
1683 MemoryConfiguration::Custom {
1684 pool_options: vec![
1685 MemoryPoolOptions {
1686 pool_type: PoolType::SlicedPages {
1687 page_size: 1024,
1688 max_slice_size: 1024,
1689 max_pool_size: Some(1024),
1690 },
1691 dealloc_period: None,
1692 },
1693 MemoryPoolOptions {
1694 pool_type: PoolType::SlicedPages {
1695 page_size: 1024,
1696 max_slice_size: 1024,
1697 max_pool_size: None,
1698 },
1699 dealloc_period: None,
1700 },
1701 ],
1702 },
1703 Arc::new(ServerLogger::default()),
1704 options(),
1705 );
1706
1707 let _fill = memory_management.reserve(1024).unwrap();
1708 let _overflow = memory_management.reserve(1024).unwrap();
1709 assert_eq!(
1710 memory_management.memory_usage().bytes_reserved,
1711 2048,
1712 "the overflow landed in the later pool, not a second arena page"
1713 );
1714
1715 let report = memory_management.memory_report();
1716 assert_eq!(report.dynamic[0].pages_peak, 1, "the cap held");
1717 assert_eq!(report.dynamic[1].pages_peak, 1, "the tail caught the spill");
1718 }
1719
1720 #[test_log::test]
1721 fn alloc_two_chunks_on_one_page() {
1722 let page_size = 2048;
1723
1724 let mut memory_management = MemoryManagement::from_configuration(
1725 BytesStorage::default(),
1726 &DUMMY_MEM_PROPS,
1727 MemoryConfiguration::Custom {
1728 pool_options: vec![MemoryPoolOptions {
1729 pool_type: PoolType::SlicedPages {
1730 page_size,
1731 max_slice_size: page_size,
1732 max_pool_size: None,
1733 },
1734 dealloc_period: None,
1735 }],
1736 },
1737 Arc::new(ServerLogger::default()),
1738 options(),
1739 );
1740
1741 let alloc_size = 512;
1742 let _handle = memory_management.reserve(alloc_size);
1743 let _new_handle = memory_management.reserve(alloc_size);
1744
1745 let usage = memory_management.memory_usage();
1746 assert_eq!(usage.number_allocs, 2);
1747 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1748 assert_eq!(usage.bytes_reserved, page_size);
1749 }
1750
1751 #[test_log::test]
1752 fn alloc_reuses_storage() {
1753 let page_size = 512;
1755
1756 let mut memory_management = MemoryManagement::from_configuration(
1757 BytesStorage::default(),
1758 &DUMMY_MEM_PROPS,
1759 MemoryConfiguration::Custom {
1760 pool_options: vec![MemoryPoolOptions {
1761 pool_type: PoolType::SlicedPages {
1762 page_size,
1763 max_slice_size: page_size,
1764 max_pool_size: None,
1765 },
1766 dealloc_period: None,
1767 }],
1768 },
1769 Arc::new(ServerLogger::default()),
1770 options(),
1771 );
1772
1773 let alloc_size = 512;
1774 let _handle = memory_management.reserve(alloc_size);
1775 drop(_handle);
1776 let _new_handle = memory_management.reserve(alloc_size);
1777
1778 let usage = memory_management.memory_usage();
1779 assert_eq!(usage.number_allocs, 1);
1780 assert_eq!(usage.bytes_in_use, alloc_size);
1781 assert_eq!(usage.bytes_reserved, page_size);
1782 }
1783
1784 #[test_log::test]
1785 fn alloc_allocs_new_storage() {
1786 let page_size = 1024;
1787
1788 let mut memory_management = MemoryManagement::from_configuration(
1789 BytesStorage::default(),
1790 &DUMMY_MEM_PROPS,
1791 MemoryConfiguration::Custom {
1792 pool_options: vec![MemoryPoolOptions {
1793 pool_type: PoolType::SlicedPages {
1794 page_size,
1795 max_slice_size: page_size,
1796 max_pool_size: None,
1797 },
1798 dealloc_period: None,
1799 }],
1800 },
1801 Arc::new(ServerLogger::default()),
1802 options(),
1803 );
1804
1805 let alloc_size = 768;
1806 let _handle = memory_management.reserve(alloc_size);
1807 let _new_handle = memory_management.reserve(alloc_size);
1808
1809 let usage = memory_management.memory_usage();
1810 assert_eq!(usage.number_allocs, 2);
1811 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1812 assert_eq!(usage.bytes_reserved, page_size * 2);
1813 }
1814
1815 #[test_log::test]
1816 fn alloc_respects_alignment_size() {
1817 let page_size = 500;
1818 let mut memory_management = MemoryManagement::from_configuration(
1819 BytesStorage::default(),
1820 &MemoryDeviceProperties {
1821 max_page_size: page_size,
1822 alignment: 50,
1823 },
1824 MemoryConfiguration::Custom {
1825 pool_options: vec![MemoryPoolOptions {
1826 pool_type: PoolType::SlicedPages {
1827 page_size,
1828 max_slice_size: page_size,
1829 max_pool_size: None,
1830 },
1831 dealloc_period: None,
1832 }],
1833 },
1834 Arc::new(ServerLogger::default()),
1835 options(),
1836 );
1837 let alloc_size = 40;
1838 let _handle = memory_management.reserve(alloc_size);
1839 let _new_handle = memory_management.reserve(alloc_size);
1840 let usage = memory_management.memory_usage();
1841 assert_eq!(usage.bytes_padding, 10 * 2);
1843 }
1844
1845 #[test_log::test]
1846 fn allocs_on_correct_page() {
1847 let sizes = [100, 200, 300, 400];
1848
1849 let pools = sizes
1850 .iter()
1851 .map(|size| MemoryPoolOptions {
1852 pool_type: PoolType::SlicedPages {
1853 page_size: *size,
1854 max_slice_size: *size,
1855 max_pool_size: None,
1856 },
1857 dealloc_period: None,
1858 })
1859 .collect();
1860 let mut memory_management = MemoryManagement::from_configuration(
1861 BytesStorage::default(),
1862 &MemoryDeviceProperties {
1863 max_page_size: 128 * 1024 * 1024,
1864 alignment: 10,
1865 },
1866 MemoryConfiguration::Custom {
1867 pool_options: pools,
1868 },
1869 Arc::new(ServerLogger::default()),
1870 options(),
1871 );
1872 let alloc_sizes = [50, 150, 250, 350];
1874 let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
1875
1876 let usage = memory_management.memory_usage();
1877
1878 assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
1880 assert!(usage.bytes_reserved >= sizes.iter().sum::<u64>());
1881 }
1882
1883 #[test_log::test]
1884 fn resolve_absent_pools_config_keeps_runtime_choice() {
1885 #[cfg(not(exclusive_memory_only))]
1886 assert!(matches!(
1887 MemoryConfiguration::SubSlices
1888 .resolve(None, &DUMMY_MEM_PROPS)
1889 .unwrap(),
1890 MemoryConfiguration::SubSlices
1891 ));
1892 assert!(matches!(
1893 MemoryConfiguration::ExclusivePages
1894 .resolve(None, &DUMMY_MEM_PROPS)
1895 .unwrap(),
1896 MemoryConfiguration::ExclusivePages
1897 ));
1898 }
1899
1900 #[test_log::test]
1901 fn resolve_preset_overrides_runtime_choice() {
1902 let preset = MemoryPoolsConfig::Preset(MemoryPoolsPreset::ExclusivePages);
1903 #[cfg(not(exclusive_memory_only))]
1904 let base = MemoryConfiguration::SubSlices;
1905 #[cfg(exclusive_memory_only)]
1906 let base = MemoryConfiguration::ExclusivePages;
1907
1908 assert!(matches!(
1909 base.resolve(Some(&preset), &DUMMY_MEM_PROPS).unwrap(),
1910 MemoryConfiguration::ExclusivePages
1911 ));
1912 }
1913
1914 #[test_log::test]
1915 #[cfg(exclusive_memory_only)]
1916 fn resolve_rejects_sliced_pools_when_exclusive_only() {
1917 use crate::config::size::MemorySize;
1918
1919 let pools = MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1920 page_size: MemorySize(1024),
1921 max_slice_size: None,
1922 max_pool_size: None,
1923 dealloc_period: None,
1924 }]);
1925 assert_eq!(
1926 MemoryConfiguration::default()
1927 .resolve(Some(&pools), &DUMMY_MEM_PROPS)
1928 .unwrap_err(),
1929 PoolConfigError::SlicedPoolsUnavailable
1930 );
1931 }
1932
1933 #[test_log::test]
1934 #[cfg(not(exclusive_memory_only))]
1935 fn resolve_explicit_list_aligns_and_defaults() {
1936 use crate::config::size::MemorySize;
1937
1938 let pools = MemoryPoolsConfig::Explicit(vec![
1939 MemoryPoolConfig::Exclusive {
1940 max_alloc_size: MemorySize(8 * 1024),
1941 dealloc_period: Some(10000),
1942 },
1943 MemoryPoolConfig::Sliced {
1944 page_size: MemorySize(1000),
1946 max_slice_size: None,
1947 max_pool_size: Some(MemorySize(4096)),
1948 dealloc_period: None,
1949 },
1950 ]);
1951
1952 let resolved = MemoryConfiguration::default()
1953 .resolve(Some(&pools), &DUMMY_MEM_PROPS)
1954 .unwrap();
1955 let MemoryConfiguration::Custom { pool_options } = resolved else {
1956 panic!("expected a custom configuration");
1957 };
1958
1959 assert_eq!(pool_options.len(), 2);
1960 assert!(matches!(
1961 pool_options[0].pool_type,
1962 PoolType::ExclusivePages {
1963 max_alloc_size: 8192
1964 }
1965 ));
1966 assert_eq!(pool_options[0].dealloc_period, Some(10000));
1967 assert!(matches!(
1968 pool_options[1].pool_type,
1969 PoolType::SlicedPages {
1970 page_size: 1024,
1971 max_slice_size: 1024,
1973 max_pool_size: Some(4096),
1974 }
1975 ));
1976 }
1977
1978 #[test_log::test]
1979 #[cfg(not(exclusive_memory_only))]
1980 fn resolve_invalid_pool_configs_fail() {
1981 use crate::config::size::MemorySize;
1982
1983 let cases = [
1984 (
1985 MemoryPoolsConfig::Explicit(vec![]),
1986 PoolConfigError::EmptyPoolList,
1987 ),
1988 (
1989 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1990 page_size: MemorySize(0),
1991 max_slice_size: None,
1992 max_pool_size: None,
1993 dealloc_period: None,
1994 }]),
1995 PoolConfigError::ZeroSize { field: "page_size" },
1996 ),
1997 (
1998 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
1999 page_size: MemorySize(1024),
2000 max_slice_size: Some(MemorySize(2048)),
2001 max_pool_size: None,
2002 dealloc_period: None,
2003 }]),
2004 PoolConfigError::SliceLargerThanPage {
2005 page_size: 1024,
2006 max_slice_size: 2048,
2007 },
2008 ),
2009 (
2010 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
2011 page_size: MemorySize(2048),
2012 max_slice_size: None,
2013 max_pool_size: Some(MemorySize(1024)),
2014 dealloc_period: None,
2015 }]),
2016 PoolConfigError::CapSmallerThanPage {
2017 page_size: 2048,
2018 max_pool_size: 1024,
2019 },
2020 ),
2021 (
2022 MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
2023 page_size: MemorySize(1024),
2024 max_slice_size: None,
2025 max_pool_size: Some(MemorySize(64 * 1024 * 1024 * 1024)),
2027 dealloc_period: None,
2028 }]),
2029 PoolConfigError::TooManyPages {
2030 pages: 64 * 1024 * 1024,
2031 },
2032 ),
2033 (
2034 MemoryPoolsConfig::Explicit(vec![
2035 MemoryPoolConfig::Exclusive {
2036 max_alloc_size: MemorySize(1024),
2037 dealloc_period: None,
2038 };
2039 PERSISTENT_POOL_POS as usize
2040 ]),
2041 PoolConfigError::TooManyPools {
2042 count: PERSISTENT_POOL_POS as usize,
2043 },
2044 ),
2045 ];
2046
2047 for (pools, expected) in cases {
2048 let result = MemoryConfiguration::default().resolve(Some(&pools), &DUMMY_MEM_PROPS);
2049 assert_eq!(result.unwrap_err(), expected);
2050 }
2051 }
2052
2053 #[test_log::test]
2057 #[cfg(not(exclusive_memory_only))]
2058 fn resolved_single_arena_reuses_across_sizes() {
2059 use crate::config::size::MemorySize;
2060
2061 let page = 1024 * 1024; let pools = MemoryPoolsConfig::Explicit(vec![MemoryPoolConfig::Sliced {
2063 page_size: MemorySize(page),
2064 max_slice_size: None,
2065 max_pool_size: None,
2066 dealloc_period: None,
2067 }]);
2068 let config = MemoryConfiguration::default()
2069 .resolve(Some(&pools), &DUMMY_MEM_PROPS)
2070 .unwrap();
2071
2072 let mut memory_management = MemoryManagement::from_configuration(
2073 BytesStorage::default(),
2074 &DUMMY_MEM_PROPS,
2075 config,
2076 Arc::new(ServerLogger::default()),
2077 options(),
2078 );
2079
2080 let small = memory_management.reserve(4 * 1024).unwrap();
2082 drop(small);
2083 let large = memory_management.reserve(512 * 1024).unwrap();
2085
2086 let usage = memory_management.memory_usage();
2087 assert_eq!(
2088 usage.bytes_reserved, page,
2089 "both sizes must share a single arena page"
2090 );
2091 assert_eq!(usage.number_allocs, 1);
2092 drop(large);
2093 }
2094
2095 #[test_log::test]
2096 #[cfg(not(exclusive_memory_only))]
2097 fn allocate_deallocate_reallocate() {
2098 let mut memory_management = MemoryManagement::from_configuration(
2099 BytesStorage::default(),
2100 &MemoryDeviceProperties {
2101 max_page_size: 128 * 1024 * 1024,
2102 alignment: 32,
2103 },
2104 MemoryConfiguration::SubSlices,
2105 Arc::new(ServerLogger::default()),
2106 options(),
2107 );
2108 let handles: Vec<_> = (0..5)
2110 .map(|i| memory_management.reserve(1000 * (i + 1)))
2111 .collect();
2112 let usage_before = memory_management.memory_usage();
2113 drop(handles);
2115 let _new_handles: Vec<_> = (0..5)
2117 .map(|i| memory_management.reserve(1000 * (i + 1)))
2118 .collect();
2119 let usage_after = memory_management.memory_usage();
2120 assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
2121 assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
2122 assert!(usage_before.bytes_reserved >= usage_after.bytes_reserved);
2124 }
2125
2126 #[test_log::test]
2127 #[cfg(not(exclusive_memory_only))]
2128 fn test_fragmentation_resistance() {
2129 let mut memory_management = MemoryManagement::from_configuration(
2130 BytesStorage::default(),
2131 &MemoryDeviceProperties {
2132 max_page_size: 128 * 1024 * 1024,
2133 alignment: 32,
2134 },
2135 MemoryConfiguration::SubSlices,
2136 Arc::new(ServerLogger::default()),
2137 options(),
2138 );
2139 let sizes = [50, 1000, 100, 5000, 200, 10000, 300];
2141 let handles: Vec<_> = sizes
2142 .iter()
2143 .map(|&size| memory_management.reserve(size).unwrap())
2144 .collect();
2145 let usage_before = memory_management.memory_usage();
2146 for i in (0..handles.len()).step_by(2) {
2148 drop(handles[i].clone());
2149 }
2150 for &size in &sizes[0..sizes.len() / 2] {
2152 memory_management.reserve(size).unwrap();
2153 }
2154 let usage_after = memory_management.memory_usage();
2155 assert!(usage_after.bytes_reserved <= (usage_before.bytes_reserved as f64 * 1.1) as u64);
2157 }
2158
2159 #[test_log::test]
2161 fn noslice_test_handle_mutability() {
2162 let mut memory_management = MemoryManagement::from_configuration(
2163 BytesStorage::default(),
2164 &(MemoryDeviceProperties {
2165 max_page_size: 128 * 1024 * 1024,
2166 alignment: 32,
2167 }),
2168 MemoryConfiguration::ExclusivePages,
2169 Arc::new(ServerLogger::default()),
2170 options(),
2171 );
2172 let handle = memory_management.reserve(10).unwrap();
2173 let other_ref = handle.clone();
2174 assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
2175 drop(other_ref);
2176 assert!(handle.can_mut(), "Handle should be mut when only one ref.");
2177 }
2178
2179 #[test_log::test]
2180 fn noslice_alloc_two_chunk() {
2181 let mut memory_management = MemoryManagement::from_configuration(
2182 BytesStorage::default(),
2183 &DUMMY_MEM_PROPS,
2184 MemoryConfiguration::Custom {
2185 pool_options: vec![MemoryPoolOptions {
2186 pool_type: PoolType::ExclusivePages {
2187 max_alloc_size: 1024,
2188 },
2189 dealloc_period: None,
2190 }],
2191 },
2192 Arc::new(ServerLogger::default()),
2193 options(),
2194 );
2195
2196 let alloc_size = 512;
2197 let _handle = memory_management.reserve(alloc_size);
2198 let _new_handle = memory_management.reserve(alloc_size);
2199
2200 let usage = memory_management.memory_usage();
2201 assert_eq!(usage.number_allocs, 2);
2202 assert_eq!(usage.bytes_in_use, alloc_size * 2);
2203 assert!(usage.bytes_reserved >= alloc_size * 2);
2204 }
2205
2206 #[test_log::test]
2207 fn noslice_alloc_reuses_storage() {
2208 let mut memory_management = MemoryManagement::from_configuration(
2210 BytesStorage::default(),
2211 &DUMMY_MEM_PROPS,
2212 MemoryConfiguration::Custom {
2213 pool_options: vec![MemoryPoolOptions {
2214 pool_type: PoolType::ExclusivePages {
2215 max_alloc_size: 1024,
2216 },
2217 dealloc_period: None,
2218 }],
2219 },
2220 Arc::new(ServerLogger::default()),
2221 options(),
2222 );
2223
2224 let alloc_size = 512;
2225 let _handle = memory_management.reserve(alloc_size);
2226 drop(_handle);
2227 let _new_handle = memory_management.reserve(alloc_size);
2228
2229 let usage = memory_management.memory_usage();
2230 assert_eq!(usage.number_allocs, 1);
2231 assert_eq!(usage.bytes_in_use, alloc_size);
2232 assert!(usage.bytes_reserved >= alloc_size);
2233 }
2234
2235 #[test_log::test]
2236 fn noslice_alloc_allocs_new_storage() {
2237 let mut memory_management = MemoryManagement::from_configuration(
2238 BytesStorage::default(),
2239 &DUMMY_MEM_PROPS,
2240 MemoryConfiguration::Custom {
2241 pool_options: vec![MemoryPoolOptions {
2242 pool_type: PoolType::ExclusivePages {
2243 max_alloc_size: 1024,
2244 },
2245 dealloc_period: None,
2246 }],
2247 },
2248 Arc::new(ServerLogger::default()),
2249 options(),
2250 );
2251
2252 let alloc_size = 768;
2253 let _handle = memory_management.reserve(alloc_size);
2254 let _new_handle = memory_management.reserve(alloc_size);
2255 let usage = memory_management.memory_usage();
2256 assert_eq!(usage.number_allocs, 2);
2257 assert_eq!(usage.bytes_in_use, alloc_size * 2);
2258 assert!(usage.bytes_reserved >= alloc_size * 2);
2259 }
2260
2261 #[test_log::test]
2262 fn noslice_alloc_respects_alignment_size() {
2263 let mut memory_management = MemoryManagement::from_configuration(
2264 BytesStorage::default(),
2265 &MemoryDeviceProperties {
2266 max_page_size: DUMMY_MEM_PROPS.max_page_size,
2267 alignment: 50,
2268 },
2269 MemoryConfiguration::Custom {
2270 pool_options: vec![MemoryPoolOptions {
2271 pool_type: PoolType::ExclusivePages {
2272 max_alloc_size: 50 * 20,
2273 },
2274 dealloc_period: None,
2275 }],
2276 },
2277 Arc::new(ServerLogger::default()),
2278 options(),
2279 );
2280 let alloc_size = 40;
2281 let _handle = memory_management.reserve(alloc_size);
2282 let _new_handle = memory_management.reserve(alloc_size);
2283 let usage = memory_management.memory_usage();
2284 assert_eq!(usage.bytes_padding, 10 * 2);
2286 }
2287
2288 #[test_log::test]
2289 fn noslice_allocs_on_correct_page() {
2290 let pools = [100, 200, 300, 400]
2291 .iter()
2292 .map(|&size| MemoryPoolOptions {
2293 pool_type: PoolType::SlicedPages {
2294 page_size: size,
2295 max_slice_size: size,
2296 max_pool_size: None,
2297 },
2298 dealloc_period: None,
2299 })
2300 .collect();
2301 let mut memory_management = MemoryManagement::from_configuration(
2302 BytesStorage::default(),
2303 &MemoryDeviceProperties {
2304 max_page_size: DUMMY_MEM_PROPS.max_page_size,
2305 alignment: 10,
2306 },
2307 MemoryConfiguration::Custom {
2308 pool_options: pools,
2309 },
2310 Arc::new(ServerLogger::default()),
2311 options(),
2312 );
2313 let alloc_sizes = [50, 150, 250, 350];
2315 let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
2316 let usage = memory_management.memory_usage();
2317 assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
2319 }
2320
2321 #[test_log::test]
2322 fn capture_pins_reused_persistent_slice() {
2323 let mut memory_management = MemoryManagement::from_configuration(
2324 BytesStorage::default(),
2325 &DUMMY_MEM_PROPS,
2326 MemoryConfiguration::ExclusivePages,
2327 Arc::new(ServerLogger::default()),
2328 options(),
2329 );
2330
2331 memory_management.capture_begin();
2333 let first = memory_management.reserve(1024).unwrap();
2334 drop(first);
2335 drop(memory_management.capture_end());
2336
2337 memory_management.capture_begin();
2340 let second = memory_management.reserve(1024).unwrap();
2341 drop(second);
2342 let pins = memory_management.capture_end();
2343 assert_eq!(pins.len(), 1, "the reused slice must be retained");
2344
2345 let before = memory_management.memory_usage();
2347 let _other = memory_management.reserve(1024).unwrap();
2348 let after = memory_management.memory_usage();
2349 assert!(
2350 after.bytes_reserved > before.bytes_reserved,
2351 "a pinned slice was handed to a later allocation"
2352 );
2353 }
2354
2355 #[test_log::test]
2356 fn capture_pins_preexisting_slice_freed_and_reused_midwindow() {
2357 let mut memory_management = MemoryManagement::from_configuration(
2358 BytesStorage::default(),
2359 &DUMMY_MEM_PROPS,
2360 MemoryConfiguration::ExclusivePages,
2361 Arc::new(ServerLogger::default()),
2362 options(),
2363 );
2364
2365 memory_management.capture_begin();
2367 let live = memory_management.reserve(1024).unwrap();
2368 drop(memory_management.capture_end()); memory_management.capture_begin();
2376 drop(live);
2377 let reused = memory_management.reserve(1024).unwrap();
2378 drop(reused);
2379 let pins = memory_management.capture_end();
2380 assert_eq!(
2381 pins.len(),
2382 1,
2383 "a pre-existing slice freed and reused mid-window must be pinned"
2384 );
2385 }
2386
2387 #[test_log::test]
2388 fn capture_does_not_retain_untouched_free_slices() {
2389 let mut memory_management = MemoryManagement::from_configuration(
2390 BytesStorage::default(),
2391 &DUMMY_MEM_PROPS,
2392 MemoryConfiguration::ExclusivePages,
2393 Arc::new(ServerLogger::default()),
2394 options(),
2395 );
2396
2397 memory_management.capture_begin();
2399 let earlier = memory_management.reserve(1024).unwrap();
2400 drop(earlier);
2401 drop(memory_management.capture_end());
2402
2403 memory_management.capture_begin();
2407 let window = memory_management.reserve(2048).unwrap();
2408 drop(window);
2409 let pins = memory_management.capture_end();
2410 assert_eq!(
2411 pins.len(),
2412 1,
2413 "only the touched slice is retained, not the idle leftover"
2414 );
2415 }
2416
2417 #[test_log::test]
2418 fn capture_survives_explicit_cleanup() {
2419 let mut memory_management = MemoryManagement::from_configuration(
2420 BytesStorage::default(),
2421 &DUMMY_MEM_PROPS,
2422 MemoryConfiguration::ExclusivePages,
2423 Arc::new(ServerLogger::default()),
2424 options(),
2425 );
2426
2427 memory_management.capture_begin();
2428 let handle = memory_management.reserve(1024).unwrap();
2429 drop(handle);
2430 memory_management.cleanup(true);
2433 let pins = memory_management.capture_end();
2434 assert_eq!(pins.len(), 1, "pin lost across an explicit cleanup");
2435 }
2436
2437 #[test_log::test]
2438 fn capture_begin_is_reentrant() {
2439 let mut memory_management = MemoryManagement::from_configuration(
2440 BytesStorage::default(),
2441 &DUMMY_MEM_PROPS,
2442 MemoryConfiguration::ExclusivePages,
2443 Arc::new(ServerLogger::default()),
2444 options(),
2445 );
2446
2447 memory_management.capture_begin();
2448 let first = memory_management.reserve(1024).unwrap();
2449 memory_management.capture_begin();
2452 let second = memory_management.reserve(2048).unwrap();
2453 drop(first);
2454 drop(second);
2455 let pins = memory_management.capture_end();
2456 assert_eq!(pins.len(), 2, "pins from before the re-entrant begin lost");
2457 assert!(
2458 memory_management.capture_end().is_empty(),
2459 "capture must be fully disarmed"
2460 );
2461 }
2462
2463 #[test_log::test]
2464 fn capture_leaves_preexisting_buffers_alone() {
2465 let mut memory_management = MemoryManagement::from_configuration(
2466 BytesStorage::default(),
2467 &DUMMY_MEM_PROPS,
2468 MemoryConfiguration::ExclusivePages,
2469 Arc::new(ServerLogger::default()),
2470 options(),
2471 );
2472
2473 memory_management.capture_begin();
2476 let preexisting = memory_management.reserve(1024).unwrap();
2477 drop(memory_management.capture_end());
2478
2479 memory_management.capture_begin();
2480 let window = memory_management.reserve(2048).unwrap();
2481 drop(window);
2482 let pins = memory_management.capture_end();
2483
2484 assert_eq!(
2487 pins.len(),
2488 1,
2489 "only the window's slice belongs to the graph"
2490 );
2491 assert!(
2492 preexisting.can_mut(),
2493 "a capture must not claim pre-existing live buffers"
2494 );
2495 }
2496
2497 #[test_log::test]
2514 fn capture_priming_leaves_the_working_set_not_the_peak() {
2515 let mut memory_management = MemoryManagement::from_configuration(
2516 BytesStorage::default(),
2517 &DUMMY_MEM_PROPS,
2518 MemoryConfiguration::ExclusivePages,
2519 Arc::new(ServerLogger::default()),
2520 options(),
2521 );
2522
2523 memory_management.capture_begin();
2527 for _ in 0..3 {
2528 let scratch = memory_management.reserve(1024).unwrap();
2529 drop(scratch);
2530 }
2531 memory_management.capture_priming_end();
2534 let after_warmup = memory_management.memory_usage();
2535
2536 let recorded: Vec<_> = (0..3)
2540 .map(|_| memory_management.reserve(1024).unwrap())
2541 .collect();
2542 let after_window = memory_management.memory_usage();
2543
2544 assert_eq!(
2545 after_window.bytes_reserved, after_warmup.bytes_reserved,
2546 "the capture window grew the pool: warmup left only its transient \
2547 peak, so the recorded run had to allocate — which a capture records \
2548 as a memory node and makes the graph un-relaunchable"
2549 );
2550
2551 drop(recorded);
2552 drop(memory_management.capture_end());
2553 }
2554
2555 #[test_log::test]
2559 fn capture_priming_holds_dropped_slices_until_priming_ends() {
2560 let mut memory_management = MemoryManagement::from_configuration(
2561 BytesStorage::default(),
2562 &DUMMY_MEM_PROPS,
2563 MemoryConfiguration::ExclusivePages,
2564 Arc::new(ServerLogger::default()),
2565 options(),
2566 );
2567
2568 memory_management.capture_begin();
2569 let first = memory_management.reserve(1024).unwrap();
2570 drop(first);
2571
2572 let before_second = memory_management.memory_usage();
2575 let second = memory_management.reserve(1024).unwrap();
2576 let after_second = memory_management.memory_usage();
2577 assert!(
2578 after_second.bytes_reserved > before_second.bytes_reserved,
2579 "priming must retain a dropped slice instead of recycling it"
2580 );
2581 drop(second);
2582
2583 memory_management.capture_priming_end();
2585 let before_reuse = memory_management.memory_usage();
2586 let reused = memory_management.reserve(1024).unwrap();
2587 let after_reuse = memory_management.memory_usage();
2588 assert_eq!(
2589 after_reuse.bytes_reserved, before_reuse.bytes_reserved,
2590 "capture_priming_end must release the retained slices for reuse"
2591 );
2592
2593 drop(reused);
2594 drop(memory_management.capture_end());
2595 }
2596
2597 #[test_log::test]
2600 fn capture_end_releases_primed_slices_when_priming_never_ended() {
2601 let mut memory_management = MemoryManagement::from_configuration(
2602 BytesStorage::default(),
2603 &DUMMY_MEM_PROPS,
2604 MemoryConfiguration::ExclusivePages,
2605 Arc::new(ServerLogger::default()),
2606 options(),
2607 );
2608
2609 memory_management.capture_begin();
2610 let scratch = memory_management.reserve(1024).unwrap();
2611 drop(scratch);
2612 assert!(
2614 memory_management.memory_usage().bytes_in_use > 0,
2615 "priming should still be retaining the dropped slice"
2616 );
2617
2618 drop(memory_management.capture_end());
2621 assert_eq!(
2622 memory_management.memory_usage().bytes_in_use,
2623 0,
2624 "primed slices outlived the capture"
2625 );
2626 }
2627
2628 #[test_log::test]
2629 fn noslice_allocate_deallocate_reallocate() {
2630 let mut memory_management = MemoryManagement::from_configuration(
2631 BytesStorage::default(),
2632 &MemoryDeviceProperties {
2633 max_page_size: 128 * 1024 * 1024,
2634 alignment: 32,
2635 },
2636 MemoryConfiguration::ExclusivePages,
2637 Arc::new(ServerLogger::default()),
2638 options(),
2639 );
2640 let handles: Vec<_> = (0..5)
2642 .map(|i| memory_management.reserve(1000 * (i + 1)))
2643 .collect();
2644 let usage_before = memory_management.memory_usage();
2645 drop(handles);
2647 let _new_handles: Vec<_> = (0..5)
2649 .map(|i| memory_management.reserve(1000 * (i + 1)))
2650 .collect();
2651 let usage_after = memory_management.memory_usage();
2652 assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
2653 assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
2654 assert_eq!(usage_before.bytes_reserved, usage_after.bytes_reserved);
2655 }
2656}