Skip to main content

tenflowers_core/memory/
pools.rs

1//! Memory pool management for efficient GPU memory allocation
2//!
3//! This module provides memory pool allocators with reference counting,
4//! block management, and automatic defragmentation capabilities.
5
6use crate::{Device, Result, TensorError};
7use std::collections::{HashMap, VecDeque};
8use std::sync::{Arc, Mutex, RwLock};
9use std::time::{Duration, Instant};
10
11/// Memory pool statistics for monitoring
12#[derive(Debug, Clone)]
13pub struct MemoryPoolStats {
14    pub total_allocated: usize,
15    pub total_free: usize,
16    pub blocks_allocated: usize,
17    pub blocks_free: usize,
18    pub fragmentation_ratio: f32,
19    pub peak_allocated: usize,
20    pub allocation_count: u64,
21    pub deallocation_count: u64,
22    pub defragmentation_count: u64,
23    pub largest_free_block: usize,
24    pub average_block_size: f32,
25    pub memory_pressure: f32,
26}
27
28/// Allocation tracking for analytics
29#[derive(Debug, Clone)]
30pub struct AllocationTracker {
31    pub timestamp: Instant,
32    pub size: usize,
33    pub block_idx: usize,
34    pub lifetime_us: Option<u64>,
35    pub deallocated_at: Option<Instant>,
36}
37
38/// Memory pressure levels
39#[derive(Debug, Clone, PartialEq)]
40pub enum MemoryPressureLevel {
41    Low,      // < 50% usage
42    Medium,   // 50-80% usage
43    High,     // 80-95% usage
44    Critical, // > 95% usage
45}
46
47/// Memory block metadata
48#[derive(Debug, Clone)]
49pub(crate) struct MemoryBlock {
50    #[allow(dead_code)] // Used in GPU-feature-gated methods
51    pub offset: usize,
52    pub size: usize,
53    pub is_free: bool,
54    #[allow(dead_code)] // Used in GPU-feature-gated methods
55    pub ref_count: usize, // Reference count for shared buffer management
56}
57
58impl MemoryBlock {
59    /// Create a new free memory block
60    #[allow(dead_code)] // Used in GPU-feature-gated methods
61    pub fn new_free(offset: usize, size: usize) -> Self {
62        Self {
63            offset,
64            size,
65            is_free: true,
66            ref_count: 0,
67        }
68    }
69
70    /// Create a new allocated memory block with initial reference count
71    #[allow(dead_code)] // Used in GPU-feature-gated methods
72    pub fn new_allocated(offset: usize, size: usize) -> Self {
73        Self {
74            offset,
75            size,
76            is_free: false,
77            ref_count: 1, // Start with one reference
78        }
79    }
80
81    /// Increment reference count (for shared buffer access)
82    #[allow(dead_code)] // Used in GPU-feature-gated methods
83    pub fn add_ref(&mut self) {
84        assert!(!self.is_free, "Cannot add reference to free block");
85        self.ref_count += 1;
86    }
87
88    /// Decrement reference count and return true if should be freed
89    #[allow(dead_code)] // Used in GPU-feature-gated methods
90    pub fn release_ref(&mut self) -> bool {
91        assert!(!self.is_free, "Cannot release reference from free block");
92        assert!(self.ref_count > 0, "Reference count underflow");
93
94        self.ref_count -= 1;
95        self.ref_count == 0 // Return true if no more references
96    }
97
98    /// Check if block can be freed (no references remaining)
99    #[allow(dead_code)] // Used in GPU-feature-gated methods
100    pub fn can_free(&self) -> bool {
101        !self.is_free && self.ref_count == 0
102    }
103}
104
105/// Memory pool allocator for efficient GPU memory management
106#[derive(Debug)]
107pub struct MemoryPool {
108    #[allow(dead_code)]
109    device: Device,
110    #[cfg(feature = "gpu")]
111    gpu_device: Arc<wgpu::Device>,
112    #[cfg(feature = "gpu")]
113    gpu_queue: Arc<wgpu::Queue>,
114
115    // Memory pool data
116    #[allow(dead_code)]
117    pool_size: usize,
118    #[cfg(feature = "gpu")]
119    pool_buffer: wgpu::Buffer,
120
121    // Block management
122    #[allow(dead_code)]
123    blocks: Arc<RwLock<Vec<MemoryBlock>>>,
124    #[allow(dead_code)]
125    free_blocks: Arc<Mutex<VecDeque<usize>>>, // Indices of free blocks
126
127    // Statistics and analytics
128    stats: Arc<Mutex<MemoryPoolStats>>,
129    #[allow(dead_code)]
130    allocation_history: Arc<Mutex<HashMap<usize, AllocationTracker>>>,
131
132    // Defragmentation settings
133    #[allow(dead_code)]
134    auto_defrag_threshold: f32, // Trigger defragmentation when fragmentation > threshold
135    #[allow(dead_code)]
136    defrag_last_run: Arc<Mutex<Instant>>,
137    #[allow(dead_code)]
138    defrag_min_interval: Duration, // Minimum time between defragmentation runs
139}
140
141impl MemoryPool {
142    /// Create a new memory pool with specified size in bytes
143    #[cfg(feature = "gpu")]
144    pub fn new(device_id: usize, pool_size: usize) -> Result<Self> {
145        let gpu_ctx = crate::device::context::get_gpu_context(device_id)?;
146
147        // Create large buffer for memory pool
148        let pool_buffer = gpu_ctx.device.create_buffer(&wgpu::BufferDescriptor {
149            label: Some("memory_pool_buffer"),
150            size: pool_size as u64,
151            usage: wgpu::BufferUsages::STORAGE
152                | wgpu::BufferUsages::COPY_SRC
153                | wgpu::BufferUsages::COPY_DST,
154            mapped_at_creation: false,
155        });
156
157        // Initialize with single large free block
158        let blocks = vec![MemoryBlock::new_free(0, pool_size)];
159
160        let mut free_blocks = VecDeque::new();
161        free_blocks.push_back(0);
162
163        let stats = MemoryPoolStats {
164            total_allocated: 0,
165            total_free: pool_size,
166            blocks_allocated: 0,
167            blocks_free: 1,
168            fragmentation_ratio: 0.0,
169            peak_allocated: 0,
170            allocation_count: 0,
171            deallocation_count: 0,
172            defragmentation_count: 0,
173            largest_free_block: pool_size,
174            average_block_size: pool_size as f32,
175            memory_pressure: 0.0,
176        };
177
178        Ok(Self {
179            device: Device::Gpu(device_id),
180            gpu_device: gpu_ctx.device.clone(),
181            gpu_queue: gpu_ctx.queue.clone(),
182            pool_size,
183            pool_buffer,
184            blocks: Arc::new(RwLock::new(blocks)),
185            free_blocks: Arc::new(Mutex::new(free_blocks)),
186            stats: Arc::new(Mutex::new(stats)),
187            allocation_history: Arc::new(Mutex::new(HashMap::new())),
188            auto_defrag_threshold: 0.25, // Auto-defrag when 25% fragmented
189            defrag_last_run: Arc::new(Mutex::new(Instant::now())),
190            defrag_min_interval: Duration::from_secs(30), // Min 30 seconds between defrags
191        })
192    }
193
194    /// Allocate memory from the pool
195    #[cfg(feature = "gpu")]
196    pub fn allocate(&self, size: usize, alignment: usize) -> Result<PooledBuffer<'_>> {
197        let aligned_size = align_size(size, alignment);
198
199        let mut free_blocks = self
200            .free_blocks
201            .lock()
202            .map_err(|_| TensorError::invalid_operation_simple("pool lock poisoned".to_string()))?;
203        let mut blocks = self.blocks.write().map_err(|_| {
204            TensorError::invalid_operation_simple("pool write lock poisoned".to_string())
205        })?;
206
207        // Find suitable free block using best-fit strategy
208        let mut best_block_idx = None;
209        let mut best_size = usize::MAX;
210
211        for &block_idx in free_blocks.iter() {
212            let block = &blocks[block_idx];
213            if block.is_free && block.size >= aligned_size && block.size < best_size {
214                best_block_idx = Some(block_idx);
215                best_size = block.size;
216            }
217        }
218
219        if let Some(block_idx) = best_block_idx {
220            // Get information from the block before splitting
221            let (offset, block_size) = {
222                let block = &blocks[block_idx];
223                (block.offset, block.size)
224            };
225
226            // Split block if necessary
227            if block_size > aligned_size {
228                // Create new free block for remainder
229                let new_block =
230                    MemoryBlock::new_free(offset + aligned_size, block_size - aligned_size);
231                blocks.push(new_block);
232                free_blocks.push_back(blocks.len() - 1);
233            }
234
235            // Mark block as allocated using new constructor logic
236            blocks[block_idx] = MemoryBlock::new_allocated(offset, aligned_size);
237
238            // Remove from free blocks
239            free_blocks.retain(|&idx| idx != block_idx);
240
241            // Track allocation for analytics
242            let mut history = self.allocation_history.lock().map_err(|_| {
243                TensorError::invalid_operation_simple("pool lock poisoned".to_string())
244            })?;
245            history.insert(
246                block_idx,
247                AllocationTracker {
248                    timestamp: Instant::now(),
249                    size: aligned_size,
250                    block_idx,
251                    lifetime_us: None,
252                    deallocated_at: None,
253                },
254            );
255
256            // Update statistics
257            self.update_enhanced_stats(&blocks);
258
259            // Check if auto-defragmentation should be triggered
260            #[cfg(feature = "gpu")]
261            self.maybe_auto_defrag();
262
263            Ok(PooledBuffer {
264                pool: self,
265                block_idx,
266                offset,
267                size: aligned_size,
268            })
269        } else {
270            Err(TensorError::allocation_error_simple(format!(
271                "Cannot allocate {} bytes from memory pool",
272                aligned_size
273            )))
274        }
275    }
276
277    /// Deallocate memory back to the pool (reference counting aware)
278    #[cfg(feature = "gpu")]
279    pub(crate) fn deallocate(&self, block_idx: usize) -> Result<()> {
280        let mut blocks = self.blocks.write().map_err(|_| {
281            TensorError::invalid_operation_simple("pool write lock poisoned".to_string())
282        })?;
283        let mut free_blocks = self
284            .free_blocks
285            .lock()
286            .map_err(|_| TensorError::invalid_operation_simple("pool lock poisoned".to_string()))?;
287
288        let block = &mut blocks[block_idx];
289        if block.is_free {
290            return Err(TensorError::invalid_argument(
291                "Attempting to deallocate already free block".to_string(),
292            ));
293        }
294
295        // Decrement reference count and only free if no references remain
296        if block.release_ref() {
297            // No more references, free the block
298            block.is_free = true;
299            free_blocks.push_back(block_idx);
300        }
301
302        // Update allocation tracking with lifetime
303        let mut history = self
304            .allocation_history
305            .lock()
306            .map_err(|_| TensorError::invalid_operation_simple("pool lock poisoned".to_string()))?;
307        if let Some(_tracker) = history.remove(&block_idx) {
308            // Tracker removed from history - could store in a completed allocations log for further analysis
309        }
310
311        // Coalesce adjacent free blocks to reduce fragmentation
312        self.coalesce_blocks(&mut blocks, &mut free_blocks);
313
314        // Update statistics
315        self.update_enhanced_stats(&blocks);
316
317        Ok(())
318    }
319
320    /// Share a buffer by incrementing its reference count
321    /// Returns true if the buffer was successfully shared
322    #[cfg(feature = "gpu")]
323    pub fn share_buffer(&self, block_idx: usize) -> Result<bool> {
324        let mut blocks = self.blocks.write().map_err(|_| {
325            TensorError::invalid_operation_simple("pool write lock poisoned".to_string())
326        })?;
327
328        if block_idx >= blocks.len() {
329            return Err(TensorError::invalid_argument(format!(
330                "Invalid block index: {}",
331                block_idx
332            )));
333        }
334
335        let block = &mut blocks[block_idx];
336        if block.is_free {
337            return Err(TensorError::invalid_argument(
338                "Cannot share a free block".to_string(),
339            ));
340        }
341
342        block.add_ref();
343        Ok(true)
344    }
345
346    /// Release a reference to a shared buffer
347    /// Returns true if the buffer was actually freed (reference count reached 0)
348    #[cfg(feature = "gpu")]
349    pub fn release_buffer(&self, block_idx: usize) -> Result<bool> {
350        let mut blocks = self.blocks.write().map_err(|_| {
351            TensorError::invalid_operation_simple("pool write lock poisoned".to_string())
352        })?;
353        let mut free_blocks = self
354            .free_blocks
355            .lock()
356            .map_err(|_| TensorError::invalid_operation_simple("pool lock poisoned".to_string()))?;
357
358        if block_idx >= blocks.len() {
359            return Err(TensorError::invalid_argument(format!(
360                "Invalid block index: {}",
361                block_idx
362            )));
363        }
364
365        let block = &mut blocks[block_idx];
366        if block.is_free {
367            return Err(TensorError::invalid_argument(
368                "Cannot release reference to already free block".to_string(),
369            ));
370        }
371
372        if block.release_ref() {
373            // No more references, free the block
374            block.is_free = true;
375            free_blocks.push_back(block_idx);
376
377            // Update allocation tracking
378            let mut history = self.allocation_history.lock().map_err(|_| {
379                TensorError::invalid_operation_simple("pool lock poisoned".to_string())
380            })?;
381            if let Some(_tracker) = history.remove(&block_idx) {
382                // Tracker removed from history - could store in a separate history if needed for analysis
383            }
384
385            // Update statistics
386            self.update_enhanced_stats(&blocks);
387
388            Ok(true) // Buffer was freed
389        } else {
390            Ok(false) // Buffer still has references
391        }
392    }
393
394    /// Get the current reference count for a buffer
395    #[cfg(feature = "gpu")]
396    pub fn get_buffer_ref_count(&self, block_idx: usize) -> Result<usize> {
397        let blocks = self.blocks.read().map_err(|_| {
398            TensorError::invalid_operation_simple("pool read lock poisoned".to_string())
399        })?;
400
401        if block_idx >= blocks.len() {
402            return Err(TensorError::invalid_argument(format!(
403                "Invalid block index: {}",
404                block_idx
405            )));
406        }
407
408        let block = &blocks[block_idx];
409        if block.is_free {
410            Ok(0)
411        } else {
412            Ok(block.ref_count)
413        }
414    }
415
416    /// Coalesce adjacent free blocks to reduce fragmentation
417    #[cfg(feature = "gpu")]
418    fn coalesce_blocks(&self, blocks: &mut [MemoryBlock], free_blocks: &mut VecDeque<usize>) {
419        // Sort free blocks by offset
420        let mut free_indices: Vec<_> = free_blocks.iter().copied().collect();
421        free_indices.sort_by_key(|&idx| blocks[idx].offset);
422
423        let mut coalesced = Vec::new();
424        let mut i = 0;
425
426        while i < free_indices.len() {
427            let mut current_idx = free_indices[i];
428            let mut current_block = blocks[current_idx].clone();
429
430            // Look for adjacent blocks to coalesce
431            while i + 1 < free_indices.len() {
432                let next_idx = free_indices[i + 1];
433                let next_block = &blocks[next_idx];
434
435                // Check if blocks are adjacent
436                if current_block.offset + current_block.size == next_block.offset {
437                    // Coalesce blocks
438                    current_block.size += next_block.size;
439                    i += 1; // Skip next block as it's now coalesced
440                } else {
441                    break;
442                }
443            }
444
445            // Update the block
446            blocks[current_idx] = current_block;
447            coalesced.push(current_idx);
448            i += 1;
449        }
450
451        // Update free blocks queue
452        free_blocks.clear();
453        for idx in coalesced {
454            free_blocks.push_back(idx);
455        }
456    }
457
458    /// Enhanced statistics update with advanced analytics
459    #[allow(dead_code)]
460    fn update_enhanced_stats(&self, blocks: &[MemoryBlock]) {
461        let mut stats = self
462            .stats
463            .lock()
464            .unwrap_or_else(|poisoned| poisoned.into_inner());
465        stats.blocks_allocated = 0;
466        stats.blocks_free = 0;
467        stats.total_allocated = 0;
468        stats.total_free = 0;
469        stats.largest_free_block = 0;
470
471        let mut block_sizes = Vec::new();
472
473        for block in blocks {
474            block_sizes.push(block.size);
475            if block.is_free {
476                stats.blocks_free += 1;
477                stats.total_free += block.size;
478                stats.largest_free_block = stats.largest_free_block.max(block.size);
479            } else {
480                stats.blocks_allocated += 1;
481                stats.total_allocated += block.size;
482            }
483        }
484
485        // Update peak allocated
486        stats.peak_allocated = stats.peak_allocated.max(stats.total_allocated);
487
488        // Update counters
489        stats.allocation_count += 1;
490
491        // Calculate fragmentation ratio
492        if stats.total_free > 0 {
493            stats.fragmentation_ratio =
494                stats.blocks_free as f32 / (stats.total_free as f32 / 1024.0);
495        } else {
496            stats.fragmentation_ratio = 0.0;
497        }
498
499        // Calculate average block size
500        if !block_sizes.is_empty() {
501            stats.average_block_size =
502                block_sizes.iter().sum::<usize>() as f32 / block_sizes.len() as f32;
503        }
504
505        // Calculate memory pressure
506        let usage_ratio = stats.total_allocated as f32 / self.pool_size as f32;
507        stats.memory_pressure = usage_ratio;
508    }
509
510    /// Check if auto-defragmentation should be triggered
511    #[cfg(feature = "gpu")]
512    #[allow(dead_code)]
513    fn maybe_auto_defrag(&self) {
514        let stats = self
515            .stats
516            .lock()
517            .unwrap_or_else(|poisoned| poisoned.into_inner());
518        if stats.fragmentation_ratio > self.auto_defrag_threshold {
519            let mut last_run = self
520                .defrag_last_run
521                .lock()
522                .unwrap_or_else(|poisoned| poisoned.into_inner());
523            if last_run.elapsed() >= self.defrag_min_interval {
524                drop(stats); // Release lock before defragmentation
525                self.defragment();
526                *last_run = Instant::now();
527            }
528        }
529    }
530
531    /// Perform active defragmentation of memory pool
532    #[cfg(feature = "gpu")]
533    #[allow(dead_code)]
534    pub fn defragment(&self) {
535        let mut blocks = self
536            .blocks
537            .write()
538            .unwrap_or_else(|poisoned| poisoned.into_inner());
539        let mut free_blocks = self
540            .free_blocks
541            .lock()
542            .unwrap_or_else(|poisoned| poisoned.into_inner());
543
544        // Sort blocks by offset to enable merging
545        blocks.sort_by_key(|block| block.offset);
546
547        // Rebuild free blocks list based on sorted blocks
548        free_blocks.clear();
549        for (idx, block) in blocks.iter().enumerate() {
550            if block.is_free {
551                free_blocks.push_back(idx);
552            }
553        }
554
555        // Coalesce adjacent free blocks
556        self.coalesce_blocks(&mut blocks, &mut free_blocks);
557
558        // Update statistics
559        self.update_enhanced_stats(&blocks);
560        let mut stats = self
561            .stats
562            .lock()
563            .unwrap_or_else(|poisoned| poisoned.into_inner());
564        stats.defragmentation_count += 1;
565    }
566
567    /// Get current memory pressure level
568    #[allow(dead_code)]
569    pub fn memory_pressure_level(&self) -> MemoryPressureLevel {
570        let stats = self
571            .stats
572            .lock()
573            .unwrap_or_else(|poisoned| poisoned.into_inner());
574        match stats.memory_pressure {
575            p if p < 0.5 => MemoryPressureLevel::Low,
576            p if p < 0.8 => MemoryPressureLevel::Medium,
577            p if p < 0.95 => MemoryPressureLevel::High,
578            _ => MemoryPressureLevel::Critical,
579        }
580    }
581
582    /// Force cleanup of small free blocks (aggressive defragmentation)
583    #[cfg(feature = "gpu")]
584    #[allow(dead_code)]
585    pub fn aggressive_cleanup(&self, min_block_size: usize) -> Result<usize> {
586        let mut blocks = self.blocks.write().map_err(|_| {
587            TensorError::invalid_operation_simple("pool write lock poisoned".to_string())
588        })?;
589        let mut free_blocks = self
590            .free_blocks
591            .lock()
592            .map_err(|_| TensorError::invalid_operation_simple("pool lock poisoned".to_string()))?;
593
594        let mut removed_count = 0;
595
596        // Remove small free blocks and merge their space
597        let mut i = 0;
598        while i < blocks.len() {
599            if blocks[i].is_free && blocks[i].size < min_block_size {
600                blocks.remove(i);
601                removed_count += 1;
602            } else {
603                i += 1;
604            }
605        }
606
607        // Rebuild free blocks list
608        free_blocks.clear();
609        for (idx, block) in blocks.iter().enumerate() {
610            if block.is_free {
611                free_blocks.push_back(idx);
612            }
613        }
614
615        // Coalesce remaining blocks
616        self.coalesce_blocks(&mut blocks, &mut free_blocks);
617
618        // Update statistics
619        self.update_enhanced_stats(&blocks);
620
621        Ok(removed_count)
622    }
623
624    /// Get memory pool statistics
625    pub fn stats(&self) -> MemoryPoolStats {
626        self.stats
627            .lock()
628            .unwrap_or_else(|poisoned| poisoned.into_inner())
629            .clone()
630    }
631
632    /// Get pool buffer reference
633    #[cfg(feature = "gpu")]
634    pub fn buffer(&self) -> &wgpu::Buffer {
635        &self.pool_buffer
636    }
637
638    /// Get GPU device reference
639    #[cfg(feature = "gpu")]
640    pub fn device(&self) -> &wgpu::Device {
641        &self.gpu_device
642    }
643
644    /// Get GPU queue reference
645    #[cfg(feature = "gpu")]
646    pub fn queue(&self) -> &wgpu::Queue {
647        &self.gpu_queue
648    }
649}
650
651/// A buffer allocated from the memory pool
652#[derive(Debug)]
653pub struct PooledBuffer<'a> {
654    #[allow(dead_code)]
655    pool: &'a MemoryPool,
656    #[allow(dead_code)]
657    block_idx: usize,
658    offset: usize,
659    size: usize,
660}
661
662impl<'a> PooledBuffer<'a> {
663    /// Get the offset within the pool buffer
664    pub fn offset(&self) -> usize {
665        self.offset
666    }
667
668    /// Get the size of the allocated buffer
669    pub fn size(&self) -> usize {
670        self.size
671    }
672
673    /// Get reference to the underlying pool buffer
674    #[cfg(feature = "gpu")]
675    pub fn buffer(&self) -> &wgpu::Buffer {
676        self.pool.buffer()
677    }
678
679    /// Create a view of this buffer with offset and size
680    pub fn view(&'a self, offset: usize, size: usize) -> Result<BufferView<'a>> {
681        if offset + size > self.size {
682            return Err(TensorError::invalid_argument(format!(
683                "View out of bounds: offset={}, size={}, buffer_size={}",
684                offset, size, self.size
685            )));
686        }
687
688        Ok(BufferView {
689            buffer: self,
690            view_offset: offset,
691            view_size: size,
692        })
693    }
694}
695
696#[cfg(feature = "gpu")]
697impl<'a> Drop for PooledBuffer<'a> {
698    fn drop(&mut self) {
699        // Deallocate when buffer is dropped
700        let _ = self.pool.deallocate(self.block_idx);
701    }
702}
703
704/// A view into a pooled buffer for zero-copy operations
705pub struct BufferView<'a> {
706    buffer: &'a PooledBuffer<'a>,
707    view_offset: usize,
708    view_size: usize,
709}
710
711impl<'a> BufferView<'a> {
712    /// Get the absolute offset within the pool buffer
713    pub fn absolute_offset(&self) -> usize {
714        self.buffer.offset() + self.view_offset
715    }
716
717    /// Get the size of the view
718    pub fn size(&self) -> usize {
719        self.view_size
720    }
721
722    /// Get reference to the underlying pool buffer
723    #[cfg(feature = "gpu")]
724    pub fn buffer(&self) -> &wgpu::Buffer {
725        self.buffer.buffer()
726    }
727}
728
729/// Utility function to align size to boundary
730#[allow(dead_code)]
731pub fn align_size(size: usize, alignment: usize) -> usize {
732    (size + alignment - 1) & !(alignment - 1)
733}
734
735#[cfg(test)]
736mod tests {
737    use super::*;
738
739    #[test]
740    fn test_align_size() {
741        assert_eq!(align_size(13, 8), 16);
742        assert_eq!(align_size(16, 8), 16);
743        assert_eq!(align_size(17, 8), 24);
744    }
745
746    #[test]
747    fn test_memory_block() {
748        let block = MemoryBlock::new_free(0, 1024);
749        assert!(block.is_free);
750        assert_eq!(block.size, 1024);
751        assert_eq!(block.ref_count, 0);
752
753        let mut allocated_block = MemoryBlock::new_allocated(1024, 512);
754        assert!(!allocated_block.is_free);
755        assert_eq!(allocated_block.ref_count, 1);
756
757        allocated_block.add_ref();
758        assert_eq!(allocated_block.ref_count, 2);
759
760        assert!(!allocated_block.release_ref());
761        assert_eq!(allocated_block.ref_count, 1);
762
763        assert!(allocated_block.release_ref());
764        assert_eq!(allocated_block.ref_count, 0);
765    }
766
767    #[test]
768    fn test_memory_pressure_level() {
769        let pressure = MemoryPressureLevel::Low;
770        assert_eq!(pressure, MemoryPressureLevel::Low);
771
772        let high_pressure = MemoryPressureLevel::High;
773        assert_eq!(high_pressure, MemoryPressureLevel::High);
774    }
775}