use std::alloc::{alloc, dealloc, Layout};
use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicU64, AtomicU8, Ordering};
use std::time::SystemTime;
const SLOT_FREE: u8 = 0;
const SLOT_WRITING: u8 = 1;
const SLOT_ACTIVE: u8 = 2;
const SLOT_DELETED: u8 = 3;
struct Slot {
state: AtomicU8, timestamp: AtomicU64, data: NonNull<u8>, data_len: usize, }
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone)]
struct TTLHeapEntry {
expire_time: u64, slot_idx: usize, logical_idx: usize, }
pub struct TTLCircularBuffer {
buffer: *mut Slot, mask: usize, current_read: usize, current_write: usize, capacity: usize, ttl_heap: BinaryHeap<Reverse<TTLHeapEntry>>, }
impl TTLCircularBuffer {
pub fn new(capacity: usize) -> Self {
let actual_capacity = capacity.next_power_of_two();
let mask = actual_capacity - 1;
let buffer = unsafe {
let layout = Layout::array::<Slot>(actual_capacity).expect("failed to create layout");
let ptr = alloc(layout) as *mut Slot;
for i in 0..actual_capacity {
let slot_ptr = ptr.add(i);
std::ptr::write(
slot_ptr,
Slot {
state: AtomicU8::new(SLOT_FREE),
timestamp: AtomicU64::new(0),
data: NonNull::dangling(),
data_len: 0,
},
);
}
ptr
};
Self {
buffer,
mask,
current_read: 0,
current_write: 0,
capacity: actual_capacity,
ttl_heap: BinaryHeap::new(),
}
}
pub fn write(&mut self, data: &[u8], ttl_ms: u64) -> bool {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(std::time::Duration::ZERO)
.as_millis() as u64;
let expire_time = now + ttl_ms;
let write_idx = self.current_write;
let slot_idx = write_idx & self.mask;
let slot = unsafe { &mut *self.buffer.add(slot_idx) };
let state = slot.state.load(Ordering::Acquire);
if state != SLOT_FREE && state != SLOT_DELETED {
if !self.evict_expired(now) {
return false; }
let state = slot.state.load(Ordering::Acquire);
if state != SLOT_FREE && state != SLOT_DELETED {
return false; }
}
if !slot
.state
.compare_exchange(SLOT_FREE, SLOT_WRITING, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return false;
}
let data_len = data.len();
let layout = Layout::from_size_align(data_len, 1).expect("failed to create layout");
let data_ptr = unsafe {
let ptr = alloc(layout) as *mut u8;
core::ptr::copy_nonoverlapping(data.as_ptr(), ptr, data_len);
NonNull::new_unchecked(ptr)
};
slot.data = data_ptr;
slot.data_len = data_len;
slot.timestamp.store(expire_time, Ordering::Release);
slot.state.store(SLOT_ACTIVE, Ordering::Release);
self.ttl_heap.push(Reverse(TTLHeapEntry {
expire_time,
slot_idx,
logical_idx: write_idx,
}));
self.current_write += 1;
true
}
pub fn read(&mut self, buffer: &mut [u8]) -> Option<usize> {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(std::time::Duration::ZERO)
.as_millis() as u64;
self.evict_expired(now);
if self.current_read == self.current_write {
return None;
}
let read_idx = self.current_read;
let slot_idx = read_idx & self.mask;
let slot = unsafe { &mut *self.buffer.add(slot_idx) };
let state = slot.state.load(Ordering::Acquire);
if state != SLOT_ACTIVE {
self.current_read += 1;
return self.read(buffer);
}
let expire_time = slot.timestamp.load(Ordering::Acquire);
if expire_time <= now {
unsafe {
let data_len = slot.data_len;
let layout = Layout::from_size_align(data_len, 1).expect("failed to create layout");
dealloc(slot.data.as_ptr(), layout);
slot.state.store(SLOT_DELETED, Ordering::Release);
}
self.current_read += 1;
return self.read(buffer);
}
let data_len = core::cmp::min(slot.data_len, buffer.len());
unsafe {
core::ptr::copy_nonoverlapping(slot.data.as_ptr(), buffer.as_mut_ptr(), data_len);
let layout =
Layout::from_size_align(slot.data_len, 1).expect("failed to create layout");
dealloc(slot.data.as_ptr(), layout);
}
slot.state.store(SLOT_DELETED, Ordering::Release);
self.current_read += 1;
Some(data_len)
}
pub fn evict_expired(&mut self, now: u64) -> bool {
while let Some(Reverse(entry)) = self.ttl_heap.peek() {
if entry.expire_time > now {
break;
}
let slot = unsafe { &mut *self.buffer.add(entry.slot_idx) };
let state = slot.state.load(Ordering::Acquire);
let current_expire_time = slot.timestamp.load(Ordering::Acquire);
let current_slot_idx = self.current_write & self.mask;
let slot_logical_idx = if entry.slot_idx > current_slot_idx {
self.current_write - (self.capacity - entry.slot_idx + current_slot_idx)
} else {
self.current_write - (current_slot_idx - entry.slot_idx)
};
if state == SLOT_ACTIVE {
if slot_logical_idx == entry.logical_idx && current_expire_time == entry.expire_time
{
unsafe {
let data_len = slot.data_len;
let layout =
Layout::from_size_align(data_len, 1).expect("failed to create layout");
dealloc(slot.data.as_ptr(), layout);
slot.state.store(SLOT_DELETED, Ordering::Release);
}
}
}
self.ttl_heap.pop();
}
true
}
pub fn evict_shortest_ttl(&mut self, now: u64) -> bool {
self.evict_expired(now);
let mut target_slot = None;
let mut heap_copy = self.ttl_heap.clone();
while let Some(Reverse(entry)) = heap_copy.pop() {
let slot = unsafe { &*self.buffer.add(entry.slot_idx) };
let state = slot.state.load(Ordering::Acquire);
if state == SLOT_ACTIVE {
let current_slot_idx = self.current_write & self.mask;
let slot_logical_idx = if entry.slot_idx > current_slot_idx {
self.current_write - (self.capacity - entry.slot_idx + current_slot_idx)
} else {
self.current_write - (current_slot_idx - entry.slot_idx)
};
let current_expire_time = slot.timestamp.load(Ordering::Acquire);
if slot_logical_idx == entry.logical_idx && current_expire_time == entry.expire_time
{
target_slot = Some(entry.slot_idx);
break;
}
}
}
if let Some(slot_idx) = target_slot {
unsafe {
let slot = &mut *self.buffer.add(slot_idx);
let data_len = slot.data_len;
let layout = Layout::from_size_align(data_len, 1).expect("failed to create layout");
dealloc(slot.data.as_ptr(), layout);
slot.state.store(SLOT_DELETED, Ordering::Release);
}
return true;
}
self.evict_shortest_ttl_fallback(now)
}
fn evict_shortest_ttl_fallback(&self, now: u64) -> bool {
let mut shortest_ttl = u64::MAX;
let mut target_slot_idx = None;
for logical_idx in self.current_read..self.current_write {
let slot_idx = logical_idx & self.mask;
let slot = unsafe { &*self.buffer.add(slot_idx) };
let state = slot.state.load(Ordering::Acquire);
if state == SLOT_ACTIVE {
let expire_time = slot.timestamp.load(Ordering::Acquire);
let remaining_ttl = expire_time.saturating_sub(now);
if remaining_ttl < shortest_ttl {
shortest_ttl = remaining_ttl;
target_slot_idx = Some(slot_idx);
}
}
}
if let Some(slot_idx) = target_slot_idx {
unsafe {
let slot = &mut *self.buffer.add(slot_idx);
let data_len = slot.data_len;
let layout = Layout::from_size_align(data_len, 1).expect("failed to create layout");
dealloc(slot.data.as_ptr(), layout);
slot.state.store(SLOT_DELETED, Ordering::Release);
}
return true;
}
false
}
pub fn available_space(&self) -> usize {
self.capacity - (self.current_write - self.current_read)
}
pub fn used_space(&self) -> usize {
self.current_write - self.current_read
}
}
impl Drop for TTLCircularBuffer {
fn drop(&mut self) {
unsafe {
for i in 0..self.capacity {
let slot = &mut *self.buffer.add(i);
let state = slot.state.load(Ordering::Acquire);
if state == SLOT_ACTIVE {
let data_len = slot.data_len;
let layout =
Layout::from_size_align(data_len, 1).expect("failed to create layout");
dealloc(slot.data.as_ptr(), layout);
}
std::ptr::drop_in_place(self.buffer.add(i));
}
let layout = Layout::array::<Slot>(self.capacity).expect("failed to create layout");
dealloc(self.buffer as *mut u8, layout);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, Instant};
#[test]
fn test_buffer_creation() {
let buffer = TTLCircularBuffer::new(8);
assert_eq!(buffer.capacity, 8);
let buffer = TTLCircularBuffer::new(10);
assert_eq!(buffer.capacity, 16); }
#[test]
fn test_write_read() {
let mut buffer = TTLCircularBuffer::new(8);
let data = b"test data";
let success = buffer.write(data, 1000);
assert!(success);
let mut read_buf = vec![0; 100];
let read_len = buffer.read(&mut read_buf);
assert_eq!(read_len, Some(data.len()));
assert_eq!(&read_buf[..data.len()], data);
let read_len = buffer.read(&mut read_buf);
assert_eq!(read_len, None);
}
#[test]
fn test_expired_data() {
let mut buffer = TTLCircularBuffer::new(8);
let data = b"test data";
let success = buffer.write(data, 1);
assert!(success);
std::thread::sleep(Duration::from_millis(2));
let mut read_buf = vec![0; 100];
let read_len = buffer.read(&mut read_buf);
assert_eq!(read_len, None);
}
#[test]
fn test_evict_expired() {
let mut buffer = TTLCircularBuffer::new(8);
let data1 = b"data1";
let data2 = b"data2";
let success = buffer.write(data1, 1);
assert!(success);
std::thread::sleep(Duration::from_millis(2));
let now = Instant::now().elapsed().as_millis() as u64;
buffer.evict_expired(now);
let success = buffer.write(data2, 1000);
assert!(success);
let mut read_buf = vec![0; 100];
let read_len = buffer.read(&mut read_buf);
assert_eq!(read_len, Some(data2.len()));
assert_eq!(&read_buf[..data2.len()], data2);
}
#[test]
fn test_evict_shortest_ttl() {
let mut buffer = TTLCircularBuffer::new(8);
let data1 = b"short ttl";
let data2 = b"long ttl";
let now = Instant::now().elapsed().as_millis() as u64;
let success = buffer.write(data1, 100);
assert!(success);
let success = buffer.write(data2, 1000);
assert!(success);
let result = buffer.evict_shortest_ttl(now);
assert!(result);
let mut read_buf = vec![0; 100];
let read_len = buffer.read(&mut read_buf);
assert_eq!(read_len, Some(data2.len()));
assert_eq!(&read_buf[..data2.len()], data2);
let read_len = buffer.read(&mut read_buf);
assert_eq!(read_len, None);
}
#[test]
fn test_buffer_full() {
let mut buffer = TTLCircularBuffer::new(2); let data1 = b"data1";
let data2 = b"data2";
let data3 = b"data3";
let success1 = buffer.write(data1, 1000);
let success2 = buffer.write(data2, 1000);
assert!(success1);
assert!(success2);
let success3 = buffer.write(data3, 1000);
assert!(!success3);
}
#[test]
fn test_available_used_space() {
let mut buffer = TTLCircularBuffer::new(8);
let data = b"test";
assert_eq!(buffer.available_space(), 8);
assert_eq!(buffer.used_space(), 0);
let success = buffer.write(data, 1000);
assert!(success);
assert_eq!(buffer.available_space(), 7);
assert_eq!(buffer.used_space(), 1);
let mut read_buf = vec![0; 100];
let read_len = buffer.read(&mut read_buf);
assert_eq!(read_len, Some(data.len()));
assert_eq!(buffer.available_space(), 8);
assert_eq!(buffer.used_space(), 0);
}
}