1use std::fs::{File, OpenOptions};
21use std::path::Path;
22use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
23
24use memmap2::{MmapMut, MmapOptions};
25
26use crate::shared_ring::RingError;
27
28pub const FRAME_REGION_MAGIC: u64 = 0x4652_474e_0000_0001;
30
31const NIL: u32 = u32::MAX;
33
34pub const MIN_BLOCK_SIZE: usize = 8;
36
37#[inline]
38fn pack(counter: u32, index: u32) -> u64 {
39 ((counter as u64) << 32) | (index as u64)
40}
41#[inline]
42fn unpack(v: u64) -> (u32, u32) {
43 ((v >> 32) as u32, v as u32)
44}
45
46#[repr(C, align(64))]
50struct FrameRegionHeader {
51 magic: u64,
52 block_size: u64,
53 block_count: u64,
54 _pad_meta: [u8; 64 - 24],
55 bump_next: AtomicU32,
57 _pad_bump: [u8; 64 - 4],
58 free_head: AtomicU64,
60 _pad_free: [u8; 64 - 8],
61}
62
63pub const fn frame_region_file_size(block_size: usize, block_count: usize) -> usize {
65 std::mem::size_of::<FrameRegionHeader>() + block_size * block_count
66}
67
68#[allow(dead_code)]
69enum RegionBacking {
70 Anon(MmapMut),
71 File(File, MmapMut),
72 Shm(crate::shm_file::ShmFile),
73}
74
75pub struct FrameRegion {
78 _backing: RegionBacking,
79 raw_ptr: *mut u8,
80 block_size: usize,
81 block_count: usize,
82 blocks_base: usize,
83}
84
85unsafe impl Send for FrameRegion {}
86unsafe impl Sync for FrameRegion {}
87
88fn validate(block_size: usize, block_count: usize) -> Result<(), RingError> {
89 if block_size < MIN_BLOCK_SIZE || !block_size.is_multiple_of(8) {
90 return Err(RingError::LayoutMismatch);
91 }
92 if block_count < 1 || block_count >= NIL as usize {
93 return Err(RingError::LayoutMismatch);
94 }
95 Ok(())
96}
97
98unsafe fn init_region(ptr: *mut u8, block_size: usize, block_count: usize) {
99 unsafe {
100 std::ptr::write(ptr as *mut FrameRegionHeader, FrameRegionHeader {
101 magic: FRAME_REGION_MAGIC,
102 block_size: block_size as u64,
103 block_count: block_count as u64,
104 _pad_meta: [0; 64 - 24],
105 bump_next: AtomicU32::new(0),
106 _pad_bump: [0; 64 - 4],
107 free_head: AtomicU64::new(pack(0, NIL)),
108 _pad_free: [0; 64 - 8],
109 });
110 }
111}
112
113impl FrameRegion {
114 pub fn create_anon(block_size: usize, block_count: usize) -> Result<Self, RingError> {
116 validate(block_size, block_count)?;
117 let total = frame_region_file_size(block_size, block_count);
118 let mut mmap = MmapOptions::new().len(total).map_anon()?;
119 unsafe { init_region(mmap.as_mut_ptr(), block_size, block_count) };
120 let raw_ptr = mmap.as_mut_ptr();
121 Ok(Self::from_parts(RegionBacking::Anon(mmap), raw_ptr, block_size, block_count))
122 }
123
124 pub fn create(
126 path: impl AsRef<Path>, block_size: usize, block_count: usize,
127 ) -> Result<Self, RingError> {
128 validate(block_size, block_count)?;
129 let total = frame_region_file_size(block_size, block_count);
130 let file = OpenOptions::new()
131 .read(true).write(true).create(true).truncate(true)
132 .open(path.as_ref())?;
133 file.set_len(total as u64)?;
134 let mut mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
135 unsafe { init_region(mmap.as_mut_ptr(), block_size, block_count) };
136 let raw_ptr = mmap.as_mut_ptr();
137 Ok(Self::from_parts(RegionBacking::File(file, mmap), raw_ptr, block_size, block_count))
138 }
139
140 pub fn open(
142 path: impl AsRef<Path>, block_size: usize, block_count: usize,
143 ) -> Result<Self, RingError> {
144 validate(block_size, block_count)?;
145 let total = frame_region_file_size(block_size, block_count);
146 let file = OpenOptions::new().read(true).write(true).open(path.as_ref())?;
147 if (file.metadata()?.len() as usize) < total {
148 return Err(RingError::LayoutMismatch);
149 }
150 let mut mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
151 Self::check_header(mmap.as_ptr(), block_size, block_count)?;
152 let raw_ptr = mmap.as_mut_ptr();
153 Ok(Self::from_parts(RegionBacking::File(file, mmap), raw_ptr, block_size, block_count))
154 }
155
156 pub fn create_from_shm(
158 mut shm: crate::shm_file::ShmFile, block_size: usize, block_count: usize,
159 ) -> Result<Self, RingError> {
160 validate(block_size, block_count)?;
161 if shm.len() < frame_region_file_size(block_size, block_count) {
162 return Err(RingError::LayoutMismatch);
163 }
164 let raw_ptr = shm.as_mut_slice().as_mut_ptr();
165 unsafe { init_region(raw_ptr, block_size, block_count) };
166 Ok(Self::from_parts(RegionBacking::Shm(shm), raw_ptr, block_size, block_count))
167 }
168
169 pub fn open_from_shm(
171 mut shm: crate::shm_file::ShmFile, block_size: usize, block_count: usize,
172 ) -> Result<Self, RingError> {
173 validate(block_size, block_count)?;
174 if shm.len() < frame_region_file_size(block_size, block_count) {
175 return Err(RingError::LayoutMismatch);
176 }
177 let raw_ptr = shm.as_mut_slice().as_mut_ptr();
178 Self::check_header(raw_ptr, block_size, block_count)?;
179 Ok(Self::from_parts(RegionBacking::Shm(shm), raw_ptr, block_size, block_count))
180 }
181
182 fn from_parts(
183 backing: RegionBacking, raw_ptr: *mut u8, block_size: usize, block_count: usize,
184 ) -> Self {
185 Self {
186 _backing: backing, raw_ptr, block_size, block_count,
187 blocks_base: std::mem::size_of::<FrameRegionHeader>(),
188 }
189 }
190
191 fn check_header(ptr: *const u8, block_size: usize, block_count: usize) -> Result<(), RingError> {
192 let h = unsafe { &*(ptr as *const FrameRegionHeader) };
193 if h.magic != FRAME_REGION_MAGIC
194 || h.block_size != block_size as u64
195 || h.block_count != block_count as u64
196 {
197 return Err(RingError::LayoutMismatch);
198 }
199 Ok(())
200 }
201
202 pub fn block_size(&self) -> usize { self.block_size }
204 pub fn block_count(&self) -> usize { self.block_count }
206
207 fn header(&self) -> &FrameRegionHeader {
208 unsafe { &*(self.raw_ptr as *const FrameRegionHeader) }
209 }
210
211 fn block_ptr(&self, idx: u32) -> *mut u8 {
212 unsafe { self.raw_ptr.add(self.blocks_base + idx as usize * self.block_size) }
213 }
214
215 fn next_link(&self, idx: u32) -> &AtomicU32 {
218 unsafe { &*(self.block_ptr(idx) as *const AtomicU32) }
219 }
220
221 pub fn alloc(&self) -> Option<u32> {
223 loop {
224 let head = self.header().free_head.load(Ordering::Acquire);
225 let (counter, idx) = unpack(head);
226 if idx == NIL {
227 break;
228 }
229 let next = self.next_link(idx).load(Ordering::Acquire);
230 let new_head = pack(counter.wrapping_add(1), next);
231 if self.header().free_head.compare_exchange(
232 head, new_head, Ordering::AcqRel, Ordering::Acquire,
233 ).is_ok() {
234 return Some(idx);
235 }
236 }
237 let idx = self.header().bump_next.fetch_add(1, Ordering::AcqRel);
238 if idx >= self.block_count as u32 {
239 self.header().bump_next.fetch_sub(1, Ordering::AcqRel);
240 return None;
241 }
242 Some(idx)
243 }
244
245 pub fn free(&self, idx: u32) {
247 if idx as usize >= self.block_count {
248 return;
249 }
250 loop {
251 let head = self.header().free_head.load(Ordering::Acquire);
252 let (counter, old_top) = unpack(head);
253 self.next_link(idx).store(old_top, Ordering::Release);
254 let new_head = pack(counter.wrapping_add(1), idx);
255 if self.header().free_head.compare_exchange(
256 head, new_head, Ordering::AcqRel, Ordering::Acquire,
257 ).is_ok() {
258 return;
259 }
260 }
261 }
262
263 pub fn write_block(&self, idx: u32, payload: &[u8]) {
266 debug_assert!(payload.len() <= self.block_size);
267 unsafe {
268 std::ptr::copy_nonoverlapping(
269 payload.as_ptr(), self.block_ptr(idx), payload.len(),
270 );
271 }
272 }
273
274 pub fn read_block_into(&self, idx: u32, len: usize, out: &mut Vec<u8>) {
276 debug_assert!(len <= self.block_size);
277 out.reserve(len);
278 unsafe {
279 std::ptr::copy_nonoverlapping(
280 self.block_ptr(idx),
281 out.spare_capacity_mut().as_mut_ptr() as *mut u8,
282 len,
283 );
284 let new_len = out.len() + len;
285 out.set_len(new_len);
286 }
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use std::sync::Arc;
294 use std::sync::atomic::AtomicUsize;
295 use std::thread;
296
297 #[test]
298 fn alloc_write_read_free_cycle() {
299 let r = FrameRegion::create_anon(256, 8).unwrap();
300 let idx = r.alloc().unwrap();
301 let payload = vec![0xABu8; 200];
302 r.write_block(idx, &payload);
303 let mut out = Vec::new();
304 r.read_block_into(idx, 200, &mut out);
305 assert_eq!(out, payload);
306 r.free(idx);
307 let idx2 = r.alloc().unwrap();
309 assert_eq!(idx2, idx, "freed block returns to the stack");
310 }
311
312 #[test]
313 fn exhausts_then_full() {
314 let r = FrameRegion::create_anon(64, 4).unwrap();
315 let a: Vec<u32> = (0..4).map(|_| r.alloc().unwrap()).collect();
316 assert_eq!(a.len(), 4);
317 assert!(r.alloc().is_none(), "region full");
318 r.free(a[1]);
319 assert!(r.alloc().is_some(), "freeing reopens a block");
320 }
321
322 #[test]
323 fn concurrent_alloc_free_no_double_issue() {
324 let r = Arc::new(FrameRegion::create_anon(64, 64).unwrap());
327 let held: Arc<Vec<AtomicUsize>> =
328 Arc::new((0..64).map(|_| AtomicUsize::new(0)).collect());
329 let mut handles = Vec::new();
330 for _ in 0..8 {
331 let r = r.clone();
332 let held = held.clone();
333 handles.push(thread::spawn(move || {
334 for _ in 0..20_000 {
335 if let Some(idx) = r.alloc() {
336 let prev = held[idx as usize].fetch_add(1, Ordering::AcqRel);
337 assert_eq!(prev, 0, "block {idx} double-issued");
338 held[idx as usize].fetch_sub(1, Ordering::AcqRel);
339 r.free(idx);
340 }
341 }
342 }));
343 }
344 for h in handles {
345 h.join().unwrap();
346 }
347 }
348
349 #[test]
350 fn shm_cross_handle() {
351 use crate::shm_file::ShmFile;
352 let nonce = std::time::SystemTime::now()
353 .duration_since(std::time::UNIX_EPOCH)
354 .map(|d| d.as_nanos())
355 .unwrap_or(0);
356 let name = format!("frame_region_{}_{}", std::process::id(), nonce);
357 let (bs, bc) = (256usize, 8usize);
358 let size = frame_region_file_size(bs, bc);
359 let a = FrameRegion::create_from_shm(
360 ShmFile::create_or_open_named(&name, size).unwrap(), bs, bc).unwrap();
361 let b = FrameRegion::open_from_shm(
362 ShmFile::create_or_open_named(&name, size).unwrap(), bs, bc).unwrap();
363 let idx = a.alloc().unwrap();
364 a.write_block(idx, b"shared across handles");
365 let mut out = Vec::new();
366 b.read_block_into(idx, 21, &mut out);
367 assert_eq!(out, b"shared across handles");
368 }
369
370 #[test]
371 fn rejects_bad_params() {
372 assert!(matches!(FrameRegion::create_anon(7, 8), Err(RingError::LayoutMismatch)));
373 assert!(matches!(FrameRegion::create_anon(64, 0), Err(RingError::LayoutMismatch)));
374 }
375}