Coverage Report

Created: 2025-09-19 09:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/gh-runner/action-runner3/_work/feoxdb/feoxdb/src/utils/allocator.rs
Line
Count
Source
1
#[cfg(unix)]
2
use nix::sys::mman::{mmap_anonymous, munmap, MapFlags, ProtFlags};
3
use std::alloc::{alloc, dealloc, Layout};
4
#[cfg(unix)]
5
use std::os::raw::c_void;
6
use std::ptr::NonNull;
7
use std::sync::atomic::{AtomicUsize, Ordering};
8
9
use crate::constants::*;
10
use crate::error::{FeoxError, Result};
11
12
static TOTAL_ALLOCATED: AtomicUsize = AtomicUsize::new(0);
13
14
pub struct FeoxAllocator;
15
16
impl FeoxAllocator {
17
0
    pub fn allocate(size: usize) -> Result<NonNull<u8>> {
18
0
        let ptr = if size <= KMALLOC_LIMIT {
19
0
            Self::allocate_small(size)?
20
        } else {
21
0
            Self::allocate_large(size)?
22
        };
23
24
0
        TOTAL_ALLOCATED.fetch_add(size, Ordering::AcqRel);
25
0
        Ok(ptr)
26
0
    }
27
28
    /// Allocate memory with specific alignment (for O_DIRECT)
29
2.11M
    pub fn allocate_aligned(size: usize, alignment: usize) -> Result<NonNull<u8>> {
30
        #[cfg(unix)]
31
        unsafe {
32
2.11M
            let mut ptr: *mut libc::c_void = std::ptr::null_mut();
33
2.11M
            let result = libc::posix_memalign(&mut ptr, alignment, size);
34
2.11M
            if result != 0 {
35
0
                return Err(FeoxError::AllocationFailed);
36
2.11M
            }
37
2.11M
            TOTAL_ALLOCATED.fetch_add(size, Ordering::AcqRel);
38
2.11M
            NonNull::new(ptr as *mut u8).ok_or(FeoxError::AllocationFailed)
39
        }
40
41
        #[cfg(not(unix))]
42
        {
43
            // For non-Unix, use standard aligned allocation
44
            let layout = Layout::from_size_align(size, alignment)
45
                .map_err(|_| FeoxError::AllocationFailed)?;
46
            unsafe {
47
                let ptr = alloc(layout);
48
                TOTAL_ALLOCATED.fetch_add(size, Ordering::AcqRel);
49
                NonNull::new(ptr).ok_or(FeoxError::AllocationFailed)
50
            }
51
        }
52
2.11M
    }
53
54
0
    pub fn deallocate(ptr: NonNull<u8>, size: usize) {
55
0
        if size <= KMALLOC_LIMIT {
56
0
            Self::deallocate_small(ptr, size);
57
0
        } else {
58
0
            Self::deallocate_large(ptr, size);
59
0
        }
60
61
0
        TOTAL_ALLOCATED.fetch_sub(size, Ordering::AcqRel);
62
0
    }
63
64
    /// Deallocate aligned memory
65
2.11M
    pub fn deallocate_aligned(ptr: NonNull<u8>, size: usize, alignment: usize) {
66
        #[cfg(unix)]
67
        {
68
2.11M
            let _ = alignment; // Not needed on Unix, posix_memalign memory is freed with free()
69
2.11M
            unsafe {
70
2.11M
                libc::free(ptr.as_ptr() as *mut libc::c_void);
71
2.11M
            }
72
        }
73
74
        #[cfg(not(unix))]
75
        {
76
            // For non-Unix, must use dealloc with the same layout used for allocation
77
            let layout = Layout::from_size_align(size, alignment).unwrap();
78
            unsafe {
79
                dealloc(ptr.as_ptr(), layout);
80
            }
81
        }
82
83
2.11M
        TOTAL_ALLOCATED.fetch_sub(size, Ordering::AcqRel);
84
2.11M
    }
85
86
0
    fn allocate_small(size: usize) -> Result<NonNull<u8>> {
87
0
        let layout = Layout::from_size_align(size, 8).map_err(|_| FeoxError::AllocationFailed)?;
88
89
        unsafe {
90
0
            let ptr = alloc(layout);
91
0
            NonNull::new(ptr).ok_or(FeoxError::AllocationFailed)
92
        }
93
0
    }
94
95
0
    fn allocate_large(size: usize) -> Result<NonNull<u8>> {
96
0
        let aligned_size = (size + PAGE_MASK) & !PAGE_MASK;
97
98
        #[cfg(unix)]
99
        unsafe {
100
            // aligned_size is guaranteed to be non-zero (rounded up from size)
101
0
            let non_zero_size = std::num::NonZeroUsize::new_unchecked(aligned_size);
102
0
            let ptr = mmap_anonymous(
103
0
                None,
104
0
                non_zero_size,
105
0
                ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
106
                MapFlags::MAP_PRIVATE,
107
            )
108
0
            .map_err(|_| FeoxError::AllocationFailed)?;
109
110
0
            Ok(ptr.cast())
111
        }
112
113
        #[cfg(not(unix))]
114
        {
115
            // Use aligned allocation for large buffers on non-Unix
116
            let layout = Layout::from_size_align(aligned_size, PAGE_SIZE)
117
                .map_err(|_| FeoxError::AllocationFailed)?;
118
119
            unsafe {
120
                let ptr = alloc(layout);
121
                NonNull::new(ptr).ok_or(FeoxError::AllocationFailed)
122
            }
123
        }
124
0
    }
125
126
0
    fn deallocate_small(ptr: NonNull<u8>, size: usize) {
127
0
        let layout = Layout::from_size_align(size, 8).unwrap();
128
0
        unsafe {
129
0
            dealloc(ptr.as_ptr(), layout);
130
0
        }
131
0
    }
132
133
0
    fn deallocate_large(ptr: NonNull<u8>, size: usize) {
134
0
        let aligned_size = (size + PAGE_MASK) & !PAGE_MASK;
135
136
        #[cfg(unix)]
137
0
        unsafe {
138
0
            let ptr_void = ptr.cast::<c_void>();
139
0
            let _ = munmap(ptr_void, aligned_size);
140
0
        }
141
142
        #[cfg(not(unix))]
143
        {
144
            let layout = Layout::from_size_align(aligned_size, PAGE_SIZE).unwrap();
145
            unsafe {
146
                dealloc(ptr.as_ptr(), layout);
147
            }
148
        }
149
0
    }
150
151
0
    pub fn get_allocated() -> usize {
152
0
        TOTAL_ALLOCATED.load(Ordering::Acquire)
153
0
    }
154
}
155
156
pub struct AlignedBuffer {
157
    ptr: NonNull<u8>,
158
    size: usize,
159
    capacity: usize,
160
    is_aligned: bool,
161
    alignment: usize,
162
}
163
164
impl AlignedBuffer {
165
2.11M
    pub fn new(capacity: usize) -> Result<Self> {
166
        // For O_DIRECT, we need alignment to FEOX_BLOCK_SIZE (4096)
167
        // Round up capacity to block size multiple
168
2.11M
        let alignment = FEOX_BLOCK_SIZE;
169
2.11M
        let aligned_capacity = capacity.div_ceil(alignment) * alignment;
170
171
        // Use posix_memalign for proper alignment
172
2.11M
        let ptr = FeoxAllocator::allocate_aligned(aligned_capacity, alignment)
?0
;
173
174
2.11M
        Ok(Self {
175
2.11M
            ptr,
176
2.11M
            size: 0,
177
2.11M
            capacity: aligned_capacity,
178
2.11M
            is_aligned: true,
179
2.11M
            alignment,
180
2.11M
        })
181
2.11M
    }
182
183
20.8k
    pub fn as_ptr(&self) -> *const u8 {
184
20.8k
        self.ptr.as_ptr()
185
20.8k
    }
186
187
2.09M
    pub fn as_mut_ptr(&mut self) -> *mut u8 {
188
2.09M
        self.ptr.as_ptr()
189
2.09M
    }
190
191
2.09M
    pub fn as_slice(&self) -> &[u8] {
192
2.09M
        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.size) }
193
2.09M
    }
194
195
20.8k
    pub fn as_mut_slice(&mut self) -> &mut [u8] {
196
20.8k
        unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size) }
197
20.8k
    }
198
199
20.8k
    pub fn len(&self) -> usize {
200
20.8k
        self.size
201
20.8k
    }
202
203
0
    pub fn is_empty(&self) -> bool {
204
0
        self.size == 0
205
0
    }
206
207
4
    pub fn capacity(&self) -> usize {
208
4
        self.capacity
209
4
    }
210
211
2.11M
    pub fn set_len(&mut self, new_len: usize) {
212
2.11M
        assert!(new_len <= self.capacity);
213
2.11M
        self.size = new_len;
214
2.11M
    }
215
216
11
    pub fn clear(&mut self) {
217
11
        self.size = 0;
218
11
    }
219
}
220
221
impl Drop for AlignedBuffer {
222
2.11M
    fn drop(&mut self) {
223
2.11M
        if self.is_aligned {
224
2.11M
            FeoxAllocator::deallocate_aligned(self.ptr, self.capacity, self.alignment);
225
2.11M
        } else {
226
0
            FeoxAllocator::deallocate(self.ptr, self.capacity);
227
0
        }
228
2.11M
    }
229
}