1use super::{
2 DEDICATED_POOL_POS, ManagedMemoryBinding, ManagedMemoryDescriptor, ManagedMemoryHandle,
3 MemoryAllocationMode, MemoryConfiguration, MemoryLocation, MemoryPoolsReport,
4 PERSISTENT_POOL_POS, PageGuard,
5 memory_pool::{DirectPool, MemoryPool, PageMapping, PersistentPool},
6};
7use crate::{
8 config::{
9 CubeClRuntimeConfig, RuntimeConfig,
10 memory::{MemoryLogLevel, PersistentMemory},
11 },
12 logging::ServerLogger,
13 memory_management::{BytesFormat, DynamicMemory, ErrorGraph, FailureId, memory_pool::Slice},
14 server::IoError,
15 storage::{ComputeStorage, ManagedResource, StorageHandle},
16};
17
18use crate::memory_management::relocation::{CopyQueue, RelocationNeed, RelocationReason};
19use alloc::format;
20use alloc::string::{String, ToString};
21use alloc::vec::Vec;
22use core::ops::Range;
23use cubecl_environment::backtrace::BackTrace;
24use cubecl_environment::sync::Arc;
25use cubecl_ir::MemoryDeviceProperties;
26
27#[derive(Clone, Copy)]
30enum PoolPosition {
31 Persistent,
32 Dedicated,
33 Dynamic(u8),
34}
35
36impl PoolPosition {
37 fn new(pool: u8) -> Self {
38 match pool {
39 PERSISTENT_POOL_POS => PoolPosition::Persistent,
40 DEDICATED_POOL_POS => PoolPosition::Dedicated,
41 index => PoolPosition::Dynamic(index),
42 }
43 }
44
45 fn missing(index: u8) -> IoError {
47 IoError::NotFound {
48 backtrace: BackTrace::capture(),
49 reason: format!("Memory pool {index} doesn't exist").into(),
50 }
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum PageUpdate {
57 Allow,
60 AddOnly,
63 Forbidden,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum Cleanup {
70 Periodic,
74 Explicit,
76}
77
78pub struct MemoryManagement<Storage> {
80 name: String,
81 persistent: PersistentPool,
82 dedicated: DirectPool,
85 pools: DynamicMemory,
86 storage: Storage,
87 alloc_reserve_count: u64,
88 mode: MemoryAllocationMode,
89 base_mode: MemoryAllocationMode,
91 windows: Vec<MemoryAllocationMode>,
94 config: PersistentMemory,
95 logger: Arc<ServerLogger>,
96}
97
98#[derive(Debug)]
100pub struct MemoryManagementOptions {
101 name: String,
103 memory: MemoryAllocationOption,
105}
106
107impl MemoryManagementOptions {
108 pub fn new<S: Into<String>>(name: S) -> Self {
110 Self {
111 name: name.into(),
112 memory: MemoryAllocationOption::FromConfig,
113 }
114 }
115
116 pub fn mode(mut self, mode: MemoryAllocationMode) -> Self {
118 self.memory = MemoryAllocationOption::Provided(mode);
119 self
120 }
121}
122
123#[derive(Default, Debug)]
124enum MemoryAllocationOption {
126 #[default]
127 FromConfig,
129 Provided(MemoryAllocationMode),
131}
132
133impl<Storage: ComputeStorage> MemoryManagement<Storage> {
134 pub fn from_configuration(
136 storage: Storage,
137 properties: &MemoryDeviceProperties,
138 config: MemoryConfiguration,
139 logger: Arc<ServerLogger>,
140 options: MemoryManagementOptions,
141 ) -> Self {
142 let pools = DynamicMemory::new(properties, config, logger.clone(), options.name.clone());
143
144 let config = CubeClRuntimeConfig::get().memory.persistent_memory.clone();
145
146 let mode = match options.memory {
147 MemoryAllocationOption::Provided(mode) => mode,
148 MemoryAllocationOption::FromConfig => match config {
149 PersistentMemory::Enabled | PersistentMemory::SizeMatch => {
150 MemoryAllocationMode::Auto
151 }
152 PersistentMemory::Disabled => MemoryAllocationMode::Auto,
153 PersistentMemory::Enforced => MemoryAllocationMode::Persistent,
154 },
155 };
156
157 Self {
158 name: options.name,
159 persistent: PersistentPool::new(
160 properties.max_page_size,
161 properties.alignment,
162 PERSISTENT_POOL_POS,
163 ),
164 dedicated: DirectPool::new(properties.alignment, DEDICATED_POOL_POS, Some(0)),
166 pools,
167 storage,
168 alloc_reserve_count: 0,
169 mode,
170 base_mode: mode,
171 windows: Vec::new(),
172 config,
173 logger,
174 }
175 }
176
177 pub fn mode(&mut self, mode: MemoryAllocationMode) {
193 match mode {
194 MemoryAllocationMode::Auto => {
195 self.windows.pop();
196 }
197 MemoryAllocationMode::Persistent => self.windows.push(match self.config {
198 PersistentMemory::Enabled | PersistentMemory::SizeMatch => mode,
199 PersistentMemory::Disabled | PersistentMemory::Enforced => self.base_mode,
200 }),
201 MemoryAllocationMode::Dedicated => self.windows.push(mode),
202 }
203 let mode = self.windows.last().copied().unwrap_or(self.base_mode);
204
205 self.logger.log_memory(
206 |level| !matches!(level, MemoryLogLevel::Disabled),
207 || {
208 format!(
209 "[{}] Setting memory allocation mode: from {:?} => {mode:?}",
210 self.name, self.mode
211 )
212 },
213 );
214
215 self.mode = mode;
216 }
217
218 pub fn cleanup(&mut self, cleanup: Cleanup, failures: &mut ErrorGraph) {
220 self.logger.log_memory(
221 |level| !matches!(level, MemoryLogLevel::Disabled) && cleanup == Cleanup::Explicit,
222 || "Manual memory cleanup ...".to_string(),
223 );
224
225 self.persistent.cleanup(
226 &mut self.storage,
227 self.alloc_reserve_count,
228 cleanup,
229 failures,
230 );
231
232 if self.dedicated.reclaim(&mut self.storage, failures) {
234 self.storage.flush();
235 }
236
237 self.pools.cleanup(
238 &mut self.storage,
239 self.alloc_reserve_count,
240 cleanup,
241 failures,
242 );
243
244 if cleanup == Cleanup::Explicit {
248 self.storage.flush();
249 }
250 }
251
252 pub fn get_cursor(&self, binding: ManagedMemoryBinding) -> Result<u64, IoError> {
254 let slice = self.find(&binding)?;
255 Ok(slice.cursor)
256 }
257
258 fn find(&self, binding: &ManagedMemoryBinding) -> Result<&Slice, IoError> {
260 let slice = self.pool(allocated(binding.descriptor())?)?.find(binding)?;
261 owned_by(slice, binding)?;
262 Ok(slice)
263 }
264
265 fn find_mut(&mut self, binding: &ManagedMemoryBinding) -> Result<&mut Slice, IoError> {
268 let slice = self
269 .pool_mut(allocated(binding.descriptor())?)?
270 .find_mut(binding)?;
271 owned_by(slice, binding)?;
272 Ok(slice)
273 }
274
275 fn pool(&self, location: MemoryLocation) -> Result<&dyn MemoryPool, IoError> {
277 match PoolPosition::new(location.pool) {
278 PoolPosition::Persistent => Ok(&self.persistent),
279 PoolPosition::Dedicated => Ok(&self.dedicated),
280 PoolPosition::Dynamic(index) => self
281 .pools
282 .pool(index)
283 .ok_or_else(|| PoolPosition::missing(index)),
284 }
285 }
286
287 fn pool_mut(&mut self, location: MemoryLocation) -> Result<&mut dyn MemoryPool, IoError> {
289 match PoolPosition::new(location.pool) {
290 PoolPosition::Persistent => Ok(&mut self.persistent),
291 PoolPosition::Dedicated => Ok(&mut self.dedicated),
292 PoolPosition::Dynamic(index) => self
293 .pools
294 .pool_mut(index)
295 .ok_or_else(|| PoolPosition::missing(index)),
296 }
297 }
298
299 pub fn get_storage(&mut self, binding: ManagedMemoryBinding) -> Result<StorageHandle, IoError> {
306 self.materialize(&binding)?;
307 let slice = self.find(&binding)?;
308 Ok(slice.storage.clone())
309 }
310
311 pub fn bytes_allocated(&self) -> u64 {
314 self.storage.bytes_allocated()
315 }
316
317 pub fn relocation_need(&self) -> RelocationNeed {
322 self.pools.relocation_need()
323 }
324
325 pub fn has_outdated(&self) -> bool {
327 self.pools.has_outdated()
328 }
329
330 pub fn relocate(
334 &mut self,
335 copier: &mut dyn CopyQueue<Storage>,
336 reason: RelocationReason,
337 failures: &mut ErrorGraph,
338 ) {
339 self.pools
340 .relocate(&mut self.storage, copier, reason, failures);
341 }
342
343 pub fn guard(&mut self, location: MemoryLocation) -> Option<PageGuard> {
346 self.pool_mut(location).ok()?.guard(location)
347 }
348
349 fn materialize(&mut self, binding: &ManagedMemoryBinding) -> Result<(), IoError> {
353 let location = binding.descriptor().location();
354 if location.init == 0 {
355 return Ok(());
356 }
357 match PoolPosition::new(location.pool) {
358 PoolPosition::Persistent => self.persistent.materialize(&mut self.storage, binding),
359 PoolPosition::Dedicated => self.dedicated.materialize(&mut self.storage, binding),
360 PoolPosition::Dynamic(_) => self.pools.materialize(&mut self.storage, binding),
361 }
362 }
363
364 pub fn managed_resource(
369 &mut self,
370 binding: ManagedMemoryBinding,
371 offset_start: Option<u64>,
372 offset_end: Option<u64>,
373 ) -> Result<ManagedResource<Storage::Resource>, IoError> {
374 let resource = self.get_resource(binding.clone(), offset_start, offset_end)?;
375 Ok(ManagedResource::new(binding, resource))
376 }
377
378 pub fn get_resource(
380 &mut self,
381 binding: ManagedMemoryBinding,
382 offset_start: Option<u64>,
383 offset_end: Option<u64>,
384 ) -> Result<Storage::Resource, IoError> {
385 let handle = self.get_storage(binding)?;
386
387 let handle = match offset_start {
388 Some(offset) => handle.offset_start(offset),
389 None => handle,
390 };
391 let handle = match offset_end {
392 Some(offset) => handle.offset_end(offset),
393 None => handle,
394 };
395 self.storage().get(&handle)
396 }
397
398 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
407 pub fn reserve(
408 &mut self,
409 size: u64,
410 update: PageUpdate,
411 failures: &mut ErrorGraph,
412 ) -> Result<ManagedMemoryHandle, IoError> {
413 match update {
414 PageUpdate::Allow => {
415 self.cleanup(Cleanup::Periodic, failures);
422 self.reserve_adding(size, failures)
423 }
424 PageUpdate::AddOnly => self.reserve_adding(size, failures),
425 PageUpdate::Forbidden => {
426 self.reserve_held(size, failures)
427 .ok_or_else(|| IoError::PageUpdateForbidden {
428 size,
429 backtrace: BackTrace::capture(),
430 })
431 }
432 }
433 }
434
435 fn reserve_adding(
438 &mut self,
439 size: u64,
440 failures: &mut ErrorGraph,
441 ) -> Result<ManagedMemoryHandle, IoError> {
442 self.alloc_reserve_count += 1;
445
446 let mapping = PageMapping::current();
447
448 if matches!(self.mode, MemoryAllocationMode::Dedicated) {
449 return self
450 .dedicated
451 .alloc(&mut self.storage, size, mapping, failures);
452 }
453
454 let persistent_mode = matches!(self.mode, MemoryAllocationMode::Persistent);
461 let size_match = matches!(self.config, PersistentMemory::SizeMatch);
462
463 if (persistent_mode || size_match)
464 && let Some(val) = self.persistent.try_reserve(size, failures)
465 {
466 self.logger.log_memory(
467 |level| matches!(level, MemoryLogLevel::Full),
468 || {
469 format!(
470 "[{}] Reserved memory {size} using persistent memory",
471 self.name
472 )
473 },
474 );
475 return Ok(val);
476 }
477
478 if persistent_mode || (size_match && self.persistent.has_size(size)) {
479 let allocated = self
480 .persistent
481 .alloc(&mut self.storage, size, mapping, failures);
482
483 self.logger.log_memory(
484 |level| !matches!(level, MemoryLogLevel::Disabled),
485 || {
486 format!(
487 "[{}] Allocated a new memory page using persistent memory, \n{}",
488 self.name, self,
489 )
490 },
491 );
492 return allocated;
493 }
494
495 self.logger.log_memory(
496 |level| matches!(level, MemoryLogLevel::Full),
497 || {
498 format!(
499 "[{}] Reserved memory {} using dynamic pool",
500 self.name,
501 BytesFormat::new(size)
502 )
503 },
504 );
505
506 let reserved = self
507 .pools
508 .reserve(&mut self.storage, size, mapping, failures)?;
509
510 self.logger.log_memory(
511 |level| matches!(level, MemoryLogLevel::Full),
512 || {
513 format!(
514 "[{}], Allocated a new memory page, current usage: \n{}",
515 self.name, self
516 )
517 },
518 );
519
520 Ok(reserved)
521 }
522
523 fn reserve_held(
527 &mut self,
528 size: u64,
529 failures: &mut ErrorGraph,
530 ) -> Option<ManagedMemoryHandle> {
531 match self.mode {
532 MemoryAllocationMode::Dedicated => self.dedicated.try_reserve(size, failures),
533 MemoryAllocationMode::Persistent => self.persistent.try_reserve(size, failures),
534 MemoryAllocationMode::Auto => {
535 if matches!(self.config, PersistentMemory::SizeMatch)
536 && let Some(handle) = self.persistent.try_reserve(size, failures)
537 {
538 return Some(handle);
539 }
540 self.pools.try_reserve(size, failures)
541 }
542 }
543 }
544
545 pub fn storage(&mut self) -> &mut Storage {
556 &mut self.storage
557 }
558
559 pub fn memory_report(&self) -> MemoryPoolsReport {
563 MemoryPoolsReport {
564 dynamic: self.pools.report(),
565 persistent: self.persistent.report(),
566 dedicated: self.dedicated.report(),
567 }
568 }
569
570 pub fn print_memory_usage(&self) {
572 #[cfg(feature = "std")]
573 log::info!("{}", self.memory_report().usage());
574 }
575
576 pub fn bind(
578 &mut self,
579 reserved: ManagedMemoryHandle,
580 assigned: ManagedMemoryHandle,
581 cursor: u64,
582 failures: &mut ErrorGraph,
583 ) -> Result<(), IoError> {
584 let location = allocated(reserved.descriptor())?;
585 self.pool_mut(location)?
586 .bind(reserved, assigned, cursor, failures)
587 }
588
589 pub fn failure(&self, binding: &ManagedMemoryBinding, range: Range<u64>) -> Option<FailureId> {
597 self.find(binding)
598 .ok()
599 .and_then(|slice| slice.tainted.failure(&range))
600 }
601
602 pub fn taint(
607 &mut self,
608 binding: &ManagedMemoryBinding,
609 range: Range<u64>,
610 failure: FailureId,
611 failures: &mut ErrorGraph,
612 ) {
613 if let Ok(slice) = self.find_mut(binding) {
614 slice.tainted.taint(range, failure, failures);
615 }
616 }
617
618 pub fn written(
622 &mut self,
623 binding: &ManagedMemoryBinding,
624 range: Range<u64>,
625 failures: &mut ErrorGraph,
626 ) {
627 if let Ok(slice) = self.find_mut(binding) {
628 slice.tainted.written(range, failures);
629 }
630 }
631}
632
633fn allocated(descriptor: &ManagedMemoryDescriptor) -> Result<MemoryLocation, IoError> {
636 if !descriptor.is_allocated() {
637 return Err(IoError::NotFound {
638 backtrace: BackTrace::capture(),
639 reason: "Memory location was never initialized".into(),
640 });
641 }
642 Ok(descriptor.location())
643}
644
645fn owned_by(slice: &Slice, binding: &ManagedMemoryBinding) -> Result<(), IoError> {
649 if slice.handle.descriptor() != binding.descriptor() {
650 return Err(IoError::NotFound {
651 backtrace: BackTrace::capture(),
652 reason: "Memory location points to a different allocation".into(),
653 });
654 }
655 Ok(())
656}
657
658impl<Storage: ComputeStorage> core::fmt::Display for MemoryManagement<Storage> {
659 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
660 f.write_str("\n# MemoryManagement\n\n")?;
661 f.write_fmt(format_args!(" - name: {:?}\n", self.name))?;
662 f.write_fmt(format_args!("\n## Persistent\n\n{}", self.persistent))?;
663 f.write_str("\n## Dynamic\n\n")?;
664
665 f.write_fmt(format_args!("{}", self.pools))?;
666 let memory_usage = self.memory_report().usage();
667 f.write_fmt(format_args!("\n## Summary\n\n{memory_usage}"))?;
668
669 Ok(())
670 }
671}
672
673impl<Storage> core::fmt::Debug for MemoryManagement<Storage> {
674 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
675 f.write_str(
676 alloc::format!(
677 "DynamicMemoryManagement {:?}",
678 core::any::type_name::<Storage>(),
679 )
680 .as_str(),
681 )
682 }
683}
684
685#[cfg(test)]
686mod tests {
687 use super::*;
688 #[cfg(not(exclusive_memory_only))]
689 use crate::memory_management::MemoryPoolKind;
690 use crate::{memory_management::MemoryManagement, storage::BytesStorage};
691
692 const MIB: u64 = 1024 * 1024;
693 const DUMMY_MEM_PROPS: MemoryDeviceProperties = MemoryDeviceProperties::new(128 * MIB, 32);
694 #[cfg(not(exclusive_memory_only))]
696 const METADATA_PAGE: u64 = 8 * MIB;
697
698 fn options() -> MemoryManagementOptions {
699 MemoryManagementOptions {
700 name: "test".into(),
701 memory: MemoryAllocationOption::FromConfig,
702 }
703 }
704
705 #[test_log::test]
709 #[cfg(not(exclusive_memory_only))]
710 fn adaptive_preset_routes_near_page_sizes_to_the_adaptive_pool() {
711 let mut memory = MemoryManagement::from_configuration(
712 BytesStorage::default(),
713 &DUMMY_MEM_PROPS,
714 MemoryConfiguration::Adaptive,
715 Arc::new(ServerLogger::default()),
716 options(),
717 );
718 let _near = memory
719 .reserve(7 * MIB, PageUpdate::Allow, &mut ErrorGraph::default())
720 .unwrap();
721
722 let served = memory
723 .memory_report()
724 .dynamic
725 .into_iter()
726 .find(|pool| pool.largest_alloc == 7 * MIB)
727 .expect("some pool served the allocation");
728 assert!(
729 matches!(served.kind, MemoryPoolKind::Adaptive { .. }),
730 "served by {:?}",
731 served.kind
732 );
733 }
734
735 #[test_log::test]
737 #[cfg(not(exclusive_memory_only))]
738 fn test_handle_mutability() {
739 let mut memory_management = MemoryManagement::from_configuration(
740 BytesStorage::default(),
741 &DUMMY_MEM_PROPS,
742 MemoryConfiguration::Adaptive,
743 Arc::new(ServerLogger::default()),
744 options(),
745 );
746 let handle = memory_management
747 .reserve(10, PageUpdate::Allow, &mut ErrorGraph::default())
748 .unwrap();
749 let other_ref = handle.clone();
750 assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
751 drop(other_ref);
752 assert!(handle.can_mut(), "Handle should be mut when only one ref.");
753 }
754
755 #[test_log::test]
757 #[cfg(not(exclusive_memory_only))]
758 fn test_memory_usage() {
759 let mut memory_management = MemoryManagement::from_configuration(
760 BytesStorage::default(),
761 &DUMMY_MEM_PROPS,
762 MemoryConfiguration::Adaptive,
763 Arc::new(ServerLogger::default()),
764 options(),
765 );
766 let handle = memory_management.reserve(100, PageUpdate::Allow, &mut ErrorGraph::default());
767 let usage = memory_management.memory_report().usage();
768
769 assert_eq!(usage.bytes_in_use, 100);
770 assert_eq!(usage.bytes_reserved, METADATA_PAGE);
772
773 drop(handle);
775 let _handle = memory_management.reserve(100, PageUpdate::Allow, &mut ErrorGraph::default());
776 let usage_new = memory_management.memory_report().usage();
777 assert_eq!(usage, usage_new);
778 }
779
780 #[test_log::test]
781 #[cfg(not(exclusive_memory_only))]
782 fn find_uninit_binding_returns_not_found() {
783 let mut memory_management = MemoryManagement::from_configuration(
784 BytesStorage::default(),
785 &DUMMY_MEM_PROPS,
786 MemoryConfiguration::Adaptive,
787 Arc::new(ServerLogger::default()),
788 options(),
789 );
790
791 let _live = memory_management
794 .reserve(512, PageUpdate::Allow, &mut ErrorGraph::default())
795 .unwrap();
796
797 let binding = ManagedMemoryHandle::new().binding();
798 assert!(matches!(
799 memory_management.get_cursor(binding),
800 Err(IoError::NotFound { .. })
801 ));
802 }
803
804 #[test_log::test]
805 #[cfg(not(exclusive_memory_only))]
806 fn find_stale_descriptor_returns_not_found() {
807 let mut memory_management = MemoryManagement::from_configuration(
808 BytesStorage::default(),
809 &DUMMY_MEM_PROPS,
810 MemoryConfiguration::Adaptive,
811 Arc::new(ServerLogger::default()),
812 options(),
813 );
814
815 let reserved = memory_management
816 .reserve(512, PageUpdate::Allow, &mut ErrorGraph::default())
817 .unwrap();
818 let stale = reserved.clone();
819 let assigned = ManagedMemoryHandle::new();
820 let assigned_binding = assigned.clone().binding();
821
822 memory_management
823 .bind(reserved, assigned, 0, &mut ErrorGraph::default())
824 .unwrap();
825
826 assert!(matches!(
829 memory_management.get_cursor(stale.binding()),
830 Err(IoError::NotFound { .. })
831 ));
832 assert!(memory_management.get_cursor(assigned_binding).is_ok());
833 }
834
835 #[test_log::test]
836 fn held_binding_survives_explicit_cleanup_renumber() {
837 let mut memory_management = MemoryManagement::from_configuration(
840 BytesStorage::default(),
841 &DUMMY_MEM_PROPS,
842 MemoryConfiguration::ExclusivePages,
843 Arc::new(ServerLogger::default()),
844 options(),
845 );
846
847 let handle_a = memory_management
848 .reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
849 .unwrap();
850 let handle_b = memory_management
851 .reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
852 .unwrap();
853 let handle_c = memory_management
854 .reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
855 .unwrap();
856
857 let binding_b = handle_b.binding();
858 let reserved = memory_management.memory_report().usage().bytes_reserved;
859 drop(handle_a);
860 drop(handle_c);
861
862 memory_management.cleanup(Cleanup::Explicit, &mut ErrorGraph::default());
864
865 assert!(memory_management.get_cursor(binding_b.clone()).is_ok());
866 assert!(memory_management.get_storage(binding_b).is_ok());
867 assert!(
868 memory_management.memory_report().usage().bytes_reserved < reserved,
869 "the two freed pages must have gone back"
870 );
871 }
872
873 #[test_log::test]
878 #[cfg(not(exclusive_memory_only))]
879 fn persistent_windows_nest() {
880 let mut memory_management = MemoryManagement::from_configuration(
881 BytesStorage::default(),
882 &DUMMY_MEM_PROPS,
883 MemoryConfiguration::Adaptive,
884 Arc::new(ServerLogger::default()),
885 options(),
886 );
887
888 memory_management.mode(MemoryAllocationMode::Persistent); memory_management.mode(MemoryAllocationMode::Persistent); memory_management.mode(MemoryAllocationMode::Auto); let weight = memory_management
894 .reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
895 .unwrap();
896 let report = memory_management.memory_report();
897 assert_eq!(
898 report.persistent.usage.bytes_in_use, 1024,
899 "an allocation inside the outer window is persistent"
900 );
901 assert_eq!(
902 report
903 .dynamic
904 .iter()
905 .map(|pool| pool.pages_peak)
906 .sum::<u64>(),
907 0,
908 "nothing leaked into the dynamic pools"
909 );
910
911 memory_management.mode(MemoryAllocationMode::Auto);
913 let _transient = memory_management
914 .reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
915 .unwrap();
916 assert_eq!(
917 memory_management
918 .memory_report()
919 .dynamic
920 .iter()
921 .map(|pool| pool.pages_peak)
922 .sum::<u64>(),
923 1
924 );
925
926 drop(weight);
927 }
928
929 #[test_log::test]
930 #[cfg(not(exclusive_memory_only))]
931 fn alloc_two_chunks_on_one_page() {
932 let mut memory_management = MemoryManagement::from_configuration(
933 BytesStorage::default(),
934 &DUMMY_MEM_PROPS,
935 MemoryConfiguration::Adaptive,
936 Arc::new(ServerLogger::default()),
937 options(),
938 );
939
940 let alloc_size = 512;
941 let _handle =
942 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
943 let _new_handle =
944 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
945
946 let usage = memory_management.memory_report().usage();
947 assert_eq!(usage.number_allocs, 2);
948 assert_eq!(usage.bytes_in_use, alloc_size * 2);
949 assert_eq!(usage.bytes_reserved, METADATA_PAGE);
951 }
952
953 #[test_log::test]
954 #[cfg(not(exclusive_memory_only))]
955 fn alloc_reuses_storage() {
956 let mut memory_management = MemoryManagement::from_configuration(
958 BytesStorage::default(),
959 &DUMMY_MEM_PROPS,
960 MemoryConfiguration::Adaptive,
961 Arc::new(ServerLogger::default()),
962 options(),
963 );
964
965 let alloc_size = 512;
966 let _handle =
967 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
968 drop(_handle);
969 let _new_handle =
970 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
971
972 let usage = memory_management.memory_report().usage();
973 assert_eq!(usage.number_allocs, 1);
974 assert_eq!(usage.bytes_in_use, alloc_size);
975 assert_eq!(usage.bytes_reserved, METADATA_PAGE);
976 }
977
978 #[test_log::test]
979 #[cfg(not(exclusive_memory_only))]
980 fn alloc_allocs_new_storage() {
981 let mut memory_management = MemoryManagement::from_configuration(
982 BytesStorage::default(),
983 &DUMMY_MEM_PROPS,
984 MemoryConfiguration::Adaptive,
985 Arc::new(ServerLogger::default()),
986 options(),
987 );
988
989 let page_size = 2 * MIB;
992 let alloc_size = page_size / 4 * 3;
993 let _handle =
994 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
995 let _new_handle =
996 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
997
998 let usage = memory_management.memory_report().usage();
999 assert_eq!(usage.number_allocs, 2);
1000 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1001 assert_eq!(usage.bytes_reserved, page_size * 2);
1002 }
1003
1004 #[test_log::test]
1005 #[cfg(not(exclusive_memory_only))]
1006 fn alloc_respects_alignment_size() {
1007 let mut memory_management = MemoryManagement::from_configuration(
1008 BytesStorage::default(),
1009 &MemoryDeviceProperties::new(DUMMY_MEM_PROPS.max_page_size, 50),
1010 MemoryConfiguration::Adaptive,
1011 Arc::new(ServerLogger::default()),
1012 options(),
1013 );
1014 let alloc_size = 40;
1015 let _handle =
1016 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
1017 let _new_handle =
1018 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
1019 let usage = memory_management.memory_report().usage();
1020 assert_eq!(usage.bytes_padding, 10 * 2);
1022 }
1023
1024 #[test_log::test]
1027 #[cfg(not(exclusive_memory_only))]
1028 fn allocs_on_correct_page() {
1029 let mut memory_management = MemoryManagement::from_configuration(
1030 BytesStorage::default(),
1031 &DUMMY_MEM_PROPS,
1032 MemoryConfiguration::Adaptive,
1033 Arc::new(ServerLogger::default()),
1034 options(),
1035 );
1036
1037 let alloc_sizes = [4096, 4 * MIB];
1038 let handles = alloc_sizes.map(|size| {
1039 memory_management
1040 .reserve(size, PageUpdate::Allow, &mut ErrorGraph::default())
1041 .unwrap()
1042 });
1043
1044 assert_ne!(
1045 handles[0].descriptor().location().pool,
1046 handles[1].descriptor().location().pool,
1047 "metadata and the workload must not share a pool"
1048 );
1049
1050 let usage = memory_management.memory_report().usage();
1051 assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
1054 assert_eq!(usage.bytes_reserved, METADATA_PAGE + 5 * MIB);
1055 }
1056
1057 #[test_log::test]
1061 #[cfg(not(exclusive_memory_only))]
1062 fn single_arena_reuses_across_sizes() {
1063 let mut memory_management = MemoryManagement::from_configuration(
1064 BytesStorage::default(),
1065 &DUMMY_MEM_PROPS,
1066 MemoryConfiguration::Adaptive,
1067 Arc::new(ServerLogger::default()),
1068 options(),
1069 );
1070
1071 let small = memory_management
1073 .reserve(128 * 1024, PageUpdate::Allow, &mut ErrorGraph::default())
1074 .unwrap();
1075 drop(small);
1076 let large = memory_management
1078 .reserve(512 * 1024, PageUpdate::Allow, &mut ErrorGraph::default())
1079 .unwrap();
1080
1081 let usage = memory_management.memory_report().usage();
1082 assert_eq!(
1083 usage.bytes_reserved,
1084 2 * MIB,
1085 "both sizes must share a single arena page"
1086 );
1087 assert_eq!(usage.number_allocs, 1);
1088 drop(large);
1089 }
1090
1091 #[test_log::test]
1092 #[cfg(not(exclusive_memory_only))]
1093 fn allocate_deallocate_reallocate() {
1094 let mut memory_management = MemoryManagement::from_configuration(
1095 BytesStorage::default(),
1096 &DUMMY_MEM_PROPS,
1097 MemoryConfiguration::Adaptive,
1098 Arc::new(ServerLogger::default()),
1099 options(),
1100 );
1101 let handles: Vec<_> = (0..5)
1103 .map(|i| {
1104 memory_management.reserve(
1105 1000 * (i + 1),
1106 PageUpdate::Allow,
1107 &mut ErrorGraph::default(),
1108 )
1109 })
1110 .collect();
1111 let usage_before = memory_management.memory_report().usage();
1112 drop(handles);
1114 let _new_handles: Vec<_> = (0..5)
1116 .map(|i| {
1117 memory_management.reserve(
1118 1000 * (i + 1),
1119 PageUpdate::Allow,
1120 &mut ErrorGraph::default(),
1121 )
1122 })
1123 .collect();
1124 let usage_after = memory_management.memory_report().usage();
1125 assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
1126 assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
1127 assert!(usage_before.bytes_reserved >= usage_after.bytes_reserved);
1129 }
1130
1131 #[test_log::test]
1132 #[cfg(not(exclusive_memory_only))]
1133 fn test_fragmentation_resistance() {
1134 let mut memory_management = MemoryManagement::from_configuration(
1135 BytesStorage::default(),
1136 &DUMMY_MEM_PROPS,
1137 MemoryConfiguration::Adaptive,
1138 Arc::new(ServerLogger::default()),
1139 options(),
1140 );
1141 let sizes = [50, 1000, 100, 5000, 200, 10000, 300];
1143 let handles: Vec<_> = sizes
1144 .iter()
1145 .map(|&size| {
1146 memory_management
1147 .reserve(size, PageUpdate::Allow, &mut ErrorGraph::default())
1148 .unwrap()
1149 })
1150 .collect();
1151 let usage_before = memory_management.memory_report().usage();
1152 for i in (0..handles.len()).step_by(2) {
1154 drop(handles[i].clone());
1155 }
1156 for &size in &sizes[0..sizes.len() / 2] {
1158 memory_management
1159 .reserve(size, PageUpdate::Allow, &mut ErrorGraph::default())
1160 .unwrap();
1161 }
1162 let usage_after = memory_management.memory_report().usage();
1163 assert!(usage_after.bytes_reserved <= (usage_before.bytes_reserved as f64 * 1.1) as u64);
1165 }
1166
1167 #[test_log::test]
1169 fn noslice_test_handle_mutability() {
1170 let mut memory_management = MemoryManagement::from_configuration(
1171 BytesStorage::default(),
1172 &DUMMY_MEM_PROPS,
1173 MemoryConfiguration::ExclusivePages,
1174 Arc::new(ServerLogger::default()),
1175 options(),
1176 );
1177 let handle = memory_management
1178 .reserve(10, PageUpdate::Allow, &mut ErrorGraph::default())
1179 .unwrap();
1180 let other_ref = handle.clone();
1181 assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
1182 drop(other_ref);
1183 assert!(handle.can_mut(), "Handle should be mut when only one ref.");
1184 }
1185
1186 #[test_log::test]
1187 fn noslice_alloc_two_chunk() {
1188 let mut memory_management = MemoryManagement::from_configuration(
1189 BytesStorage::default(),
1190 &DUMMY_MEM_PROPS,
1191 MemoryConfiguration::ExclusivePages,
1192 Arc::new(ServerLogger::default()),
1193 options(),
1194 );
1195
1196 let alloc_size = 512;
1197 let _handle =
1198 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
1199 let _new_handle =
1200 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
1201
1202 let usage = memory_management.memory_report().usage();
1203 assert_eq!(usage.number_allocs, 2);
1204 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1205 assert!(usage.bytes_reserved >= alloc_size * 2);
1206 }
1207
1208 #[test_log::test]
1209 fn noslice_alloc_reuses_storage() {
1210 let mut memory_management = MemoryManagement::from_configuration(
1212 BytesStorage::default(),
1213 &DUMMY_MEM_PROPS,
1214 MemoryConfiguration::ExclusivePages,
1215 Arc::new(ServerLogger::default()),
1216 options(),
1217 );
1218
1219 let alloc_size = 512;
1220 let _handle =
1221 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
1222 drop(_handle);
1223 let _new_handle =
1224 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
1225
1226 let usage = memory_management.memory_report().usage();
1227 assert_eq!(usage.number_allocs, 1);
1228 assert_eq!(usage.bytes_in_use, alloc_size);
1229 assert!(usage.bytes_reserved >= alloc_size);
1230 }
1231
1232 #[test_log::test]
1233 fn noslice_alloc_allocs_new_storage() {
1234 let mut memory_management = MemoryManagement::from_configuration(
1235 BytesStorage::default(),
1236 &DUMMY_MEM_PROPS,
1237 MemoryConfiguration::ExclusivePages,
1238 Arc::new(ServerLogger::default()),
1239 options(),
1240 );
1241
1242 let alloc_size = 768;
1243 let _handle =
1244 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
1245 let _new_handle =
1246 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
1247 let usage = memory_management.memory_report().usage();
1248 assert_eq!(usage.number_allocs, 2);
1249 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1250 assert!(usage.bytes_reserved >= alloc_size * 2);
1251 }
1252
1253 #[test_log::test]
1254 fn noslice_alloc_respects_alignment_size() {
1255 let mut memory_management = MemoryManagement::from_configuration(
1256 BytesStorage::default(),
1257 &MemoryDeviceProperties::new(DUMMY_MEM_PROPS.max_page_size, 50),
1258 MemoryConfiguration::ExclusivePages,
1259 Arc::new(ServerLogger::default()),
1260 options(),
1261 );
1262 let alloc_size = 40;
1263 let _handle =
1264 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
1265 let _new_handle =
1266 memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
1267 let usage = memory_management.memory_report().usage();
1268 assert_eq!(usage.bytes_padding, 10 * 2);
1270 }
1271
1272 #[test_log::test]
1273 fn noslice_allocs_on_correct_page() {
1274 let mut memory_management = MemoryManagement::from_configuration(
1275 BytesStorage::default(),
1276 &MemoryDeviceProperties::new(DUMMY_MEM_PROPS.max_page_size, 10),
1277 MemoryConfiguration::ExclusivePages,
1278 Arc::new(ServerLogger::default()),
1279 options(),
1280 );
1281 let alloc_sizes = [50, 150, 250, 350];
1283 let _handles = alloc_sizes
1284 .map(|s| memory_management.reserve(s, PageUpdate::Allow, &mut ErrorGraph::default()));
1285 let usage = memory_management.memory_report().usage();
1286 assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
1288 }
1289
1290 #[test_log::test]
1291 fn a_guarded_allocation_is_never_handed_out_again() {
1292 let mut memory_management = MemoryManagement::from_configuration(
1293 BytesStorage::default(),
1294 &DUMMY_MEM_PROPS,
1295 MemoryConfiguration::ExclusivePages,
1296 Arc::new(ServerLogger::default()),
1297 options(),
1298 );
1299
1300 let recorded = memory_management
1301 .reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
1302 .unwrap();
1303 let guard = memory_management
1304 .guard(recorded.descriptor().location())
1305 .expect("the pool holds the page");
1306 drop(recorded);
1307
1308 let before = memory_management.memory_report().usage().bytes_reserved;
1311 let other = memory_management
1312 .reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
1313 .unwrap();
1314 let after = memory_management.memory_report().usage().bytes_reserved;
1315 assert!(after > before, "the guarded page was handed out again");
1316 memory_management.cleanup(Cleanup::Explicit, &mut ErrorGraph::default());
1317 assert_eq!(
1318 memory_management.memory_report().usage().bytes_reserved,
1319 after,
1320 "the guarded page was released"
1321 );
1322
1323 drop(guard);
1324 drop(other);
1325 memory_management.cleanup(Cleanup::Explicit, &mut ErrorGraph::default());
1326 assert_eq!(memory_management.memory_report().usage().bytes_reserved, 0);
1327 }
1328
1329 #[test_log::test]
1330 fn a_guard_leaves_in_place_updates_alone() {
1331 let mut memory_management = MemoryManagement::from_configuration(
1332 BytesStorage::default(),
1333 &DUMMY_MEM_PROPS,
1334 MemoryConfiguration::ExclusivePages,
1335 Arc::new(ServerLogger::default()),
1336 options(),
1337 );
1338
1339 let cache = memory_management
1340 .reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
1341 .unwrap();
1342 let _guard = memory_management.guard(cache.descriptor().location());
1343
1344 assert!(
1345 cache.can_mut(),
1346 "a graph replaying against a buffer must not stop it being updated in place"
1347 );
1348 }
1349
1350 #[test_log::test]
1351 fn noslice_allocate_deallocate_reallocate() {
1352 let mut memory_management = MemoryManagement::from_configuration(
1353 BytesStorage::default(),
1354 &DUMMY_MEM_PROPS,
1355 MemoryConfiguration::ExclusivePages,
1356 Arc::new(ServerLogger::default()),
1357 options(),
1358 );
1359 let handles: Vec<_> = (0..5)
1361 .map(|i| {
1362 memory_management.reserve(
1363 1000 * (i + 1),
1364 PageUpdate::Allow,
1365 &mut ErrorGraph::default(),
1366 )
1367 })
1368 .collect();
1369 let usage_before = memory_management.memory_report().usage();
1370 drop(handles);
1372 let _new_handles: Vec<_> = (0..5)
1374 .map(|i| {
1375 memory_management.reserve(
1376 1000 * (i + 1),
1377 PageUpdate::Allow,
1378 &mut ErrorGraph::default(),
1379 )
1380 })
1381 .collect();
1382 let usage_after = memory_management.memory_report().usage();
1383 assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
1384 assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
1385 assert_eq!(usage_before.bytes_reserved, usage_after.bytes_reserved);
1386 }
1387}