use crate::error::{CoreError, CoreResult};
use crate::token::FrameToken;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FrameId(u32);
impl FrameId {
#[inline]
pub fn new(id: u32) -> Self {
FrameId(id)
}
#[inline]
pub fn value(&self) -> u32 {
self.0
}
}
impl From<u32> for FrameId {
fn from(id: u32) -> Self {
FrameId(id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FrameState {
Free = 0,
Allocated = 1,
InRxRing = 2,
Processing = 3,
InTxRing = 4,
InCompletionRing = 5,
Quarantine = 6,
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct FrameInfo {
id: u32,
physical_addr: u64,
virtual_addr: u64,
size: u32,
state: FrameState,
domain_id: u32,
generation: u64,
}
impl FrameInfo {
#[inline]
pub fn id(&self) -> FrameId {
FrameId(self.id)
}
#[inline]
pub fn physical_addr(&self) -> u64 {
self.physical_addr
}
#[inline]
pub fn virtual_addr(&self) -> u64 {
self.virtual_addr
}
#[inline]
pub fn size(&self) -> u32 {
self.size
}
#[inline]
pub fn state(&self) -> FrameState {
self.state
}
#[inline]
pub fn domain_id(&self) -> u32 {
self.domain_id
}
#[inline]
pub fn generation(&self) -> u64 {
self.generation
}
}
#[derive(Debug)]
pub struct FramePool {
name: String,
capacity: u32,
frame_size: u32,
free_stack: Vec<u32>,
frames: Vec<FrameInfo>,
allocated_count: u32,
quarantined_count: u32,
generation: u64,
epoch: u64,
}
impl FramePool {
pub fn try_new(name: impl Into<String>, capacity: u32, frame_size: u32) -> CoreResult<Self> {
if frame_size == 0 {
return Err(CoreError::invalid_config(
"frame_size",
"frame_size must be greater than 0",
));
}
if capacity == 0 {
return Err(CoreError::invalid_config(
"capacity",
"capacity must be greater than 0",
));
}
(capacity as u64)
.checked_mul(frame_size as u64)
.ok_or_else(|| {
CoreError::arithmetic_overflow("mul", capacity as u64, frame_size as u64)
})?;
let name = name.into();
let mut frames: Vec<FrameInfo> = Vec::with_capacity(capacity as usize);
let mut free_stack: Vec<u32> = Vec::with_capacity(capacity as usize);
for i in 0..capacity {
let offset = (i as u64).checked_mul(frame_size as u64).ok_or_else(|| {
CoreError::arithmetic_overflow("mul", i as u64, frame_size as u64)
})?;
frames.push(FrameInfo {
id: i,
physical_addr: offset,
virtual_addr: offset,
size: frame_size,
state: FrameState::Free,
domain_id: 0,
generation: 0,
});
free_stack.push(i);
}
Ok(Self {
name,
capacity,
frame_size,
free_stack,
frames,
allocated_count: 0,
quarantined_count: 0,
generation: 0,
epoch: 0,
})
}
pub fn new(name: impl Into<String>, capacity: u32, frame_size: u32) -> Self {
let name = name.into();
match Self::try_new(name.clone(), capacity, frame_size) {
Ok(pool) => pool,
Err(e) => {
tracing::error!(
pool = %name,
capacity,
frame_size,
error = %e,
"FramePool::new 参数非法,回退到 1x1 最小配置;\
需要显式错误处理的调用方应改用 FramePool::try_new"
);
Self {
name,
capacity: 1,
frame_size: 1,
free_stack: vec![0],
frames: vec![FrameInfo {
id: 0,
physical_addr: 0,
virtual_addr: 0,
size: 1,
state: FrameState::Free,
domain_id: 0,
generation: 0,
}],
allocated_count: 0,
quarantined_count: 0,
generation: 0,
epoch: 0,
}
}
}
}
#[inline]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
pub fn capacity(&self) -> u32 {
self.capacity
}
#[inline]
pub fn frame_size(&self) -> u32 {
self.frame_size
}
#[inline]
pub fn allocated_count(&self) -> u32 {
self.allocated_count
}
#[inline]
pub fn free_count(&self) -> u32 {
self.free_stack.len() as u32
}
#[inline]
pub fn quarantined_count(&self) -> u32 {
self.quarantined_count
}
#[inline]
pub fn allocate(&mut self, domain_id: u32) -> CoreResult<FrameToken> {
let frame_idx = *self
.free_stack
.last()
.ok_or_else(|| {
CoreError::quota_exceeded("frame", self.capacity as u64, 1)
})?;
let frame = self
.frames
.get_mut(frame_idx as usize)
.ok_or_else(|| CoreError::resource_not_found(frame_idx as u64, "frame"))?;
if frame.state != FrameState::Free {
return Err(CoreError::state_conflict(
format!("frame {} in state {:?}", frame_idx, frame.state),
"allocate",
));
}
frame.state = FrameState::Allocated;
frame.domain_id = domain_id;
frame.generation = self.generation;
let _ = self.free_stack.pop();
self.allocated_count = self
.allocated_count
.checked_add(1)
.ok_or_else(|| CoreError::arithmetic_overflow("add", self.allocated_count as u64, 1))?;
Ok(FrameToken::new(
FrameId(frame_idx),
domain_id,
self.generation,
self.epoch,
))
}
#[inline]
pub fn release(&mut self, token: FrameToken) -> CoreResult<()> {
self.do_release(token).map_err(|(_t, e)| e)
}
#[inline]
pub fn release_recoverable(&mut self, token: FrameToken) -> Result<(), (FrameToken, CoreError)> {
self.do_release(token)
}
fn do_release(&mut self, token: FrameToken) -> Result<(), (FrameToken, CoreError)> {
let frame_idx = token.frame_id().value();
let frame = match self.frames.get_mut(frame_idx as usize) {
Some(f) => f,
None => return Err((token, CoreError::resource_not_found(frame_idx as u64, "frame"))),
};
if let Err(e) = token.verify_ownership(frame.domain_id, frame.generation) {
return Err((token, e));
}
if frame.state != FrameState::Allocated {
return Err((
token,
CoreError::ownership_violation("allocated", "not_allocated"),
));
}
let new_count = match self.allocated_count.checked_sub(1) {
Some(c) => c,
None => {
return Err((
token,
CoreError::arithmetic_overflow("sub", self.allocated_count as u64, 1),
))
}
};
frame.state = FrameState::Free;
self.generation = self.generation.saturating_add(1);
frame.generation = self.generation;
self.free_stack.push(frame_idx);
self.allocated_count = new_count;
let _ = token;
Ok(())
}
pub fn quarantine(&mut self, token: FrameToken, reason: impl Into<String>) -> CoreResult<()> {
let frame_idx = token.frame_id().value();
let frame = self
.frames
.get_mut(frame_idx as usize)
.ok_or_else(|| CoreError::resource_not_found(frame_idx as u64, "frame"))?;
token.verify_ownership(frame.domain_id, frame.generation)?;
if frame.state != FrameState::Allocated {
return Err(CoreError::ownership_violation("allocated", "not_allocated"));
}
frame.state = FrameState::Quarantine;
self.generation = self.generation.saturating_add(1);
frame.generation = self.generation;
self.allocated_count = self
.allocated_count
.checked_sub(1)
.ok_or_else(|| CoreError::arithmetic_overflow("sub", self.allocated_count as u64, 1))?;
self.quarantined_count = self
.quarantined_count
.checked_add(1)
.ok_or_else(|| CoreError::arithmetic_overflow("add", self.quarantined_count as u64, 1))?;
tracing::debug!(
pool = %self.name,
frame_id = frame_idx,
reason = %reason.into(),
"frame quarantined"
);
let _ = token;
Ok(())
}
pub fn recover_from_quarantine(&mut self, frame_id: FrameId) -> CoreResult<()> {
let frame_idx = frame_id.value();
if (frame_idx as usize) >= self.frames.len() {
return Err(CoreError::resource_not_found(frame_idx as u64, "frame"));
}
let frame = &mut self.frames[frame_idx as usize];
if frame.state != FrameState::Quarantine {
return Err(CoreError::resource_not_found(frame_idx as u64, "frame"));
}
frame.state = FrameState::Free;
self.generation = self.generation.saturating_add(1);
frame.generation = self.generation;
self.free_stack.push(frame_idx);
self.quarantined_count = self
.quarantined_count
.checked_sub(1)
.ok_or_else(|| CoreError::arithmetic_overflow("sub", self.quarantined_count as u64, 1))?;
Ok(())
}
pub fn recover_from_quarantine_by_id(
&mut self,
frame_id: FrameId,
expected_generation: u64,
) -> CoreResult<()> {
let frame_idx = frame_id.value();
if (frame_idx as usize) >= self.frames.len() {
return Err(CoreError::resource_not_found(frame_idx as u64, "frame"));
}
let frame = &mut self.frames[frame_idx as usize];
if frame.state != FrameState::Quarantine {
return Err(CoreError::resource_not_found(frame_idx as u64, "frame"));
}
if frame.generation != expected_generation {
return Err(CoreError::ownership_violation(
"valid generation",
"invalid generation",
));
}
frame.state = FrameState::Free;
self.generation = self.generation.saturating_add(1);
frame.generation = self.generation;
self.free_stack.push(frame_idx);
self.quarantined_count = self
.quarantined_count
.checked_sub(1)
.ok_or_else(|| CoreError::arithmetic_overflow("sub", self.quarantined_count as u64, 1))?;
Ok(())
}
pub fn quarantine_by_id(
&mut self,
frame_id: FrameId,
expected_generation: u64,
reason: impl Into<String>,
) -> CoreResult<()> {
let frame_idx = frame_id.value();
if (frame_idx as usize) >= self.frames.len() {
return Err(CoreError::resource_not_found(frame_idx as u64, "frame"));
}
let frame = &mut self.frames[frame_idx as usize];
if frame.generation != expected_generation {
return Err(CoreError::ownership_violation(
"valid generation",
"invalid generation",
));
}
match frame.state {
FrameState::Allocated
| FrameState::Processing
| FrameState::InRxRing
| FrameState::InTxRing
| FrameState::InCompletionRing => {
self.allocated_count = self
.allocated_count
.checked_sub(1)
.ok_or_else(|| CoreError::arithmetic_overflow("sub", self.allocated_count as u64, 1))?;
}
_ => {
return Err(CoreError::state_conflict(
format!("frame {} in state {:?}", frame_idx, frame.state),
"quarantine",
));
}
}
frame.state = FrameState::Quarantine;
self.generation = self.generation.saturating_add(1);
frame.generation = self.generation;
self.quarantined_count = self
.quarantined_count
.checked_add(1)
.ok_or_else(|| CoreError::arithmetic_overflow("add", self.quarantined_count as u64, 1))?;
tracing::debug!(
pool = %self.name,
frame_id = frame_idx,
reason = %reason.into(),
"frame quarantined by id"
);
Ok(())
}
#[inline]
pub fn advance_generation(&mut self) -> u64 {
self.generation = self.generation.saturating_add(1);
self.generation
}
#[inline]
pub fn current_generation(&self) -> u64 {
self.generation
}
#[inline]
pub fn advance_epoch(&mut self) -> u64 {
self.epoch = self.epoch.saturating_add(1);
self.epoch
}
#[inline]
pub fn current_epoch(&self) -> u64 {
self.epoch
}
#[inline]
pub fn get_frame_info(&self, frame_id: FrameId) -> Option<FrameInfo> {
self.frames.get(frame_id.value() as usize).cloned()
}
pub fn allocate_batch(&mut self, domain_id: u32, count: u32) -> CoreResult<Vec<FrameToken>> {
if self.free_stack.len() < count as usize {
return Err(CoreError::quota_exceeded(
"frame",
self.free_stack.len() as u64,
count as u64,
));
}
let mut tokens = Vec::with_capacity(count as usize);
for _ in 0..count {
match self.allocate(domain_id) {
Ok(t) => tokens.push(t),
Err(e) => {
for t in tokens {
if let Err((_t, re)) = self.release_recoverable(t) {
tracing::error!(
pool = %self.name,
error = %re,
"allocate_batch 回滚归还帧失败(尽力回滚,帧可能泄漏)"
);
}
}
return Err(e);
}
}
}
Ok(tokens)
}
pub fn release_batch(&mut self, tokens: Vec<FrameToken>) -> CoreResult<()> {
let mut first_err: Option<CoreError> = None;
for token in tokens {
if let Err((_token, e)) = self.release_recoverable(token)
&& first_err.is_none()
{
first_err = Some(e);
}
}
match first_err {
Some(e) => Err(e),
None => Ok(()),
}
}
#[inline]
pub fn verify_conservation(&self) -> CoreResult<()> {
let free = self.free_stack.len() as u64;
let allocated = self.allocated_count as u64;
let quarantined = self.quarantined_count as u64;
let total = free
.checked_add(allocated)
.and_then(|v| v.checked_add(quarantined))
.ok_or_else(|| CoreError::arithmetic_overflow("add", free, allocated))?;
if total != self.capacity as u64 {
return Err(CoreError::internal(format!(
"conservation violation: total={}, capacity={}, free={}, allocated={}, quarantined={}",
total, self.capacity, free, allocated, quarantined
)));
}
Ok(())
}
#[inline]
pub fn release_by_id(&mut self, frame_id: FrameId, expected_generation: u64) -> CoreResult<()> {
let frame_idx = frame_id.value();
let frame = self
.frames
.get_mut(frame_idx as usize)
.ok_or_else(|| CoreError::resource_not_found(frame_idx as u64, "frame"))?;
if frame.generation != expected_generation {
return Err(CoreError::ownership_violation(
"valid generation",
"invalid generation",
));
}
if frame.state != FrameState::Allocated {
return Err(CoreError::ownership_violation("allocated", "not_allocated"));
}
frame.state = FrameState::Free;
self.generation = self.generation.saturating_add(1);
frame.generation = self.generation;
self.free_stack.push(frame_idx);
self.allocated_count = self
.allocated_count
.checked_sub(1)
.ok_or_else(|| CoreError::arithmetic_overflow("sub", self.allocated_count as u64, 1))?;
Ok(())
}
#[inline]
pub fn frames(&self) -> &[FrameInfo] {
&self.frames
}
#[inline]
pub fn remaining_free(&self) -> u32 {
self.free_stack.len() as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_CAPACITY: u32 = 100;
const TEST_FRAME_SIZE: u32 = 2048;
fn create_test_pool() -> FramePool {
FramePool::new("test_pool", TEST_CAPACITY, TEST_FRAME_SIZE)
}
#[test]
fn test_pool_creation() {
let pool = create_test_pool();
assert_eq!(pool.name(), "test_pool");
assert_eq!(pool.capacity(), TEST_CAPACITY);
assert_eq!(pool.frame_size(), TEST_FRAME_SIZE);
assert_eq!(pool.free_count(), TEST_CAPACITY);
assert_eq!(pool.allocated_count(), 0);
assert_eq!(pool.quarantined_count(), 0);
}
#[test]
fn test_allocate_and_release() {
let mut pool = create_test_pool();
let token = pool.allocate(0).unwrap();
assert_eq!(pool.allocated_count(), 1);
assert_eq!(pool.free_count(), TEST_CAPACITY - 1);
assert!(token.verify_ownership(0, 0).is_ok());
pool.release(token).unwrap();
assert_eq!(pool.allocated_count(), 0);
assert_eq!(pool.free_count(), TEST_CAPACITY);
}
#[test]
fn test_allocate_exhausted() {
let mut pool = FramePool::new("small_pool", 2, TEST_FRAME_SIZE);
let token1 = pool.allocate(0).unwrap();
let token2 = pool.allocate(0).unwrap();
let result = pool.allocate(0);
assert!(result.is_err());
pool.release(token1).unwrap();
let token3 = pool.allocate(0).unwrap();
assert_ne!(token3.generation(), 0);
assert!(token3.verify_ownership(0, 0).is_err());
assert!(token3.verify_ownership(0, token3.generation()).is_ok());
pool.release(token2).unwrap();
pool.release(token3).unwrap();
}
#[test]
fn test_quarantine_and_recover() {
let mut pool = create_test_pool();
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
pool.quarantine(token, "test reason").unwrap();
assert_eq!(pool.quarantined_count(), 1);
assert_eq!(pool.allocated_count(), 0);
assert_eq!(pool.free_count(), TEST_CAPACITY - 1);
pool.recover_from_quarantine(frame_id).unwrap();
assert_eq!(pool.quarantined_count(), 0);
assert_eq!(pool.free_count(), TEST_CAPACITY);
}
#[test]
fn test_quarantine_nonexistent() {
let mut pool = create_test_pool();
let result = pool.recover_from_quarantine(FrameId::new(999));
assert!(result.is_err());
}
#[test]
fn test_generation_management() {
let mut pool = create_test_pool();
assert_eq!(pool.current_generation(), 0);
let generation = pool.advance_generation();
assert_eq!(generation, 1);
assert_eq!(pool.current_generation(), 1);
}
#[test]
fn test_epoch_management() {
let mut pool = create_test_pool();
assert_eq!(pool.current_epoch(), 0);
let epoch = pool.advance_epoch();
assert_eq!(epoch, 1);
assert_eq!(pool.current_epoch(), 1);
}
#[test]
fn test_verify_conservation() {
let mut pool = create_test_pool();
assert!(pool.verify_conservation().is_ok());
let token = pool.allocate(0).unwrap();
assert!(pool.verify_conservation().is_ok());
pool.release(token).unwrap();
assert!(pool.verify_conservation().is_ok());
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
pool.quarantine(token, "test").unwrap();
assert!(pool.verify_conservation().is_ok());
pool.recover_from_quarantine(frame_id).unwrap();
assert!(pool.verify_conservation().is_ok());
}
#[test]
fn test_get_frame_info() {
let pool = create_test_pool();
let info = pool.get_frame_info(FrameId::new(0)).unwrap();
assert_eq!(info.id(), FrameId::new(0));
assert_eq!(info.size(), TEST_FRAME_SIZE);
assert_eq!(info.state(), FrameState::Free);
}
#[test]
fn test_allocate_batch() {
let mut pool = create_test_pool();
let tokens = pool.allocate_batch(0, 10).unwrap();
assert_eq!(tokens.len(), 10);
assert_eq!(pool.allocated_count(), 10);
assert_eq!(pool.free_count(), TEST_CAPACITY - 10);
pool.release_batch(tokens).unwrap();
assert_eq!(pool.allocated_count(), 0);
assert_eq!(pool.free_count(), TEST_CAPACITY);
}
#[test]
fn test_allocate_batch_exhausted() {
let mut pool = FramePool::new("tiny_pool", 5, TEST_FRAME_SIZE);
let result = pool.allocate_batch(0, 10);
assert!(result.is_err());
assert_eq!(pool.allocated_count(), 0);
assert_eq!(pool.free_count(), 5);
}
#[test]
fn test_release_wrong_state() {
let mut pool = create_test_pool();
let token = pool.allocate(0).unwrap();
let frame_idx = token.frame_id().value();
pool.release(token).unwrap();
let info = pool.get_frame_info(FrameId::new(frame_idx)).unwrap();
assert_eq!(info.state(), FrameState::Free);
}
#[test]
fn test_free_list_lifo_order() {
let mut pool = FramePool::new("lifo_test", 3, TEST_FRAME_SIZE);
let t0 = pool.allocate(0).unwrap(); let t1 = pool.allocate(0).unwrap(); let t2 = pool.allocate(0).unwrap();
assert_eq!(t0.frame_id().value(), 2);
assert_eq!(t1.frame_id().value(), 1);
assert_eq!(t2.frame_id().value(), 0);
pool.release(t0).unwrap(); let t3 = pool.allocate(0).unwrap(); assert_eq!(t3.frame_id().value(), 2);
pool.release(t1).unwrap();
pool.release(t2).unwrap();
pool.release(t3).unwrap();
}
#[test]
fn test_domain_isolation() {
let mut pool = create_test_pool();
let token0 = pool.allocate(0).unwrap();
assert_eq!(token0.domain_id(), 0);
assert!(token0.verify_ownership(0, 0).is_ok());
assert!(token0.verify_ownership(1, 0).is_err());
let token1 = pool.allocate(1).unwrap();
assert_eq!(token1.domain_id(), 1);
assert!(token1.verify_ownership(1, 0).is_ok());
assert!(token1.verify_ownership(0, 0).is_err());
pool.release(token0).unwrap();
pool.release(token1).unwrap();
}
#[test]
fn test_release_by_id() {
let mut pool = create_test_pool();
let token = pool.allocate(42).unwrap();
let frame_id = token.frame_id();
let token_gen = token.generation();
assert_eq!(pool.allocated_count(), 1);
pool.release_by_id(frame_id, token_gen).unwrap();
assert_eq!(pool.allocated_count(), 0);
assert!(pool.verify_conservation().is_ok());
let result = pool.release_by_id(frame_id, token_gen);
assert!(result.is_err());
}
#[test]
fn test_release_by_id_with_wrong_state() {
let mut pool = create_test_pool();
let result = pool.release_by_id(FrameId::new(0), pool.current_generation());
assert!(result.is_err());
}
#[test]
fn test_release_by_id_out_of_bounds() {
let mut pool = create_test_pool();
let result = pool.release_by_id(FrameId::new(TEST_CAPACITY), pool.current_generation());
assert!(result.is_err());
let result = pool.release_by_id(FrameId::new(u32::MAX), pool.current_generation());
assert!(result.is_err());
assert!(pool.verify_conservation().is_ok());
}
#[test]
fn test_release_by_id_aba_protection() {
let mut pool = FramePool::new("aba_test", 1, TEST_FRAME_SIZE);
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
let gen_v1 = token.generation();
let _ = token;
pool.release_by_id(frame_id, gen_v1).unwrap();
assert!(pool.release_by_id(frame_id, gen_v1).is_err());
let token2 = pool.allocate(0).unwrap();
let gen_v2 = token2.generation();
assert_ne!(gen_v1, gen_v2);
assert!(pool.release_by_id(frame_id, gen_v1).is_err());
pool.release_by_id(frame_id, gen_v2).unwrap();
assert!(pool.verify_conservation().is_ok());
}
#[test]
fn test_release_token_aba_protection() {
let mut pool = FramePool::new("aba_token", 1, TEST_FRAME_SIZE);
let token = pool.allocate(0).unwrap();
let gen_v1 = token.generation();
pool.release(token).unwrap();
let token2 = pool.allocate(0).unwrap();
assert_ne!(token2.generation(), gen_v1);
pool.release(token2).unwrap();
assert!(pool.verify_conservation().is_ok());
}
fn set_frame_state(pool: &mut FramePool, frame_id: u32, state: FrameState) {
let current_gen = pool.current_generation();
let frame = &mut pool.frames[frame_id as usize];
frame.state = state;
frame.generation = current_gen;
}
#[test]
fn test_quarantine_by_id_from_allocated() {
let mut pool = create_test_pool();
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
let token_gen = token.generation();
let _ = token;
assert_eq!(pool.allocated_count(), 1);
assert_eq!(pool.quarantined_count(), 0);
pool.quarantine_by_id(frame_id, token_gen, "test").unwrap();
assert_eq!(pool.allocated_count(), 0);
assert_eq!(pool.quarantined_count(), 1);
assert_eq!(pool.get_frame_info(frame_id).unwrap().state(), FrameState::Quarantine);
assert!(pool.verify_conservation().is_ok());
}
#[test]
fn test_quarantine_by_id_from_processing() {
let mut pool = create_test_pool();
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
let idx = frame_id.value();
let token_gen = token.generation();
let _ = token;
set_frame_state(&mut pool, idx, FrameState::Processing);
pool.quarantine_by_id(frame_id, token_gen, "test").unwrap();
assert_eq!(pool.quarantined_count(), 1);
assert_eq!(pool.get_frame_info(frame_id).unwrap().state(), FrameState::Quarantine);
}
#[test]
fn test_quarantine_by_id_processing_conservation() {
let mut pool = create_test_pool();
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
let idx = frame_id.value();
let token_gen = token.generation();
let _ = token;
set_frame_state(&mut pool, idx, FrameState::Processing);
assert!(pool.verify_conservation().is_ok());
pool.quarantine_by_id(frame_id, token_gen, "processing-conflict").unwrap();
assert_eq!(pool.quarantined_count(), 1);
assert_eq!(pool.allocated_count(), 0);
assert!(pool.verify_conservation().is_ok());
}
#[test]
fn test_recover_from_quarantine_by_id_generation_mismatch() {
let mut pool = create_test_pool();
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
pool.quarantine(token, "test").unwrap();
let correct_gen = pool.get_frame_info(frame_id).unwrap().generation();
assert!(pool
.recover_from_quarantine_by_id(frame_id, correct_gen + 1)
.is_err());
assert_eq!(pool.quarantined_count(), 1);
assert!(pool.verify_conservation().is_ok());
pool.recover_from_quarantine_by_id(frame_id, correct_gen).unwrap();
assert_eq!(pool.quarantined_count(), 0);
assert_eq!(pool.free_count(), TEST_CAPACITY);
assert!(pool.verify_conservation().is_ok());
}
#[test]
fn test_quarantine_by_id_from_in_rx_ring() {
let mut pool = create_test_pool();
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
let idx = frame_id.value();
let token_gen = token.generation();
let _ = token;
set_frame_state(&mut pool, idx, FrameState::InRxRing);
pool.quarantine_by_id(frame_id, token_gen, "test").unwrap();
assert_eq!(pool.quarantined_count(), 1);
assert_eq!(pool.get_frame_info(frame_id).unwrap().state(), FrameState::Quarantine);
}
#[test]
fn test_quarantine_by_id_from_in_tx_ring() {
let mut pool = create_test_pool();
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
let idx = frame_id.value();
let token_gen = token.generation();
let _ = token;
set_frame_state(&mut pool, idx, FrameState::InTxRing);
pool.quarantine_by_id(frame_id, token_gen, "test").unwrap();
assert_eq!(pool.quarantined_count(), 1);
assert_eq!(pool.get_frame_info(frame_id).unwrap().state(), FrameState::Quarantine);
}
#[test]
fn test_quarantine_by_id_from_in_completion_ring() {
let mut pool = create_test_pool();
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
let idx = frame_id.value();
let token_gen = token.generation();
let _ = token;
set_frame_state(&mut pool, idx, FrameState::InCompletionRing);
pool.quarantine_by_id(frame_id, token_gen, "test").unwrap();
assert_eq!(pool.quarantined_count(), 1);
assert_eq!(pool.get_frame_info(frame_id).unwrap().state(), FrameState::Quarantine);
}
#[test]
fn test_quarantine_by_id_from_free_fails() {
let mut pool = create_test_pool();
let result = pool.quarantine_by_id(FrameId::new(0), pool.current_generation(), "test");
assert!(result.is_err());
}
#[test]
fn test_quarantine_by_id_from_quarantine_fails() {
let mut pool = create_test_pool();
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
pool.quarantine(token, "first").unwrap();
let result = pool.quarantine_by_id(frame_id, pool.current_generation(), "test");
assert!(result.is_err());
}
#[test]
fn test_quarantine_by_id_out_of_bounds() {
let mut pool = create_test_pool();
let result = pool.quarantine_by_id(FrameId::new(9999), pool.current_generation(), "test");
assert!(result.is_err());
}
#[test]
fn test_frame_state_discriminant_values() {
assert_eq!(FrameState::Free as u8, 0);
assert_eq!(FrameState::Allocated as u8, 1);
assert_eq!(FrameState::InRxRing as u8, 2);
assert_eq!(FrameState::Processing as u8, 3);
assert_eq!(FrameState::InTxRing as u8, 4);
assert_eq!(FrameState::InCompletionRing as u8, 5);
assert_eq!(FrameState::Quarantine as u8, 6);
}
#[test]
fn test_frame_state_equality() {
assert_eq!(FrameState::Free, FrameState::Free);
assert_ne!(FrameState::Free, FrameState::Allocated);
assert_ne!(FrameState::Allocated, FrameState::Processing);
}
#[test]
fn test_frame_state_clone_copy() {
let s = FrameState::Processing;
let s2 = s;
assert_eq!(s, s2);
let s3 = s;
assert_eq!(s, s3);
}
#[test]
fn test_frame_state_debug() {
let s = format!("{:?}", FrameState::Quarantine);
assert_eq!(s, "Quarantine");
}
#[test]
fn test_pool_capacity_one() {
let mut pool = FramePool::new("single", 1, 4096);
assert_eq!(pool.capacity(), 1);
assert_eq!(pool.free_count(), 1);
assert_eq!(pool.allocated_count(), 0);
let token = pool.allocate(0).unwrap();
assert_eq!(pool.free_count(), 0);
assert_eq!(pool.allocated_count(), 1);
let result = pool.allocate(0);
assert!(result.is_err());
pool.release(token).unwrap();
assert_eq!(pool.free_count(), 1);
assert_eq!(pool.allocated_count(), 0);
assert!(pool.verify_conservation().is_ok());
}
#[test]
fn test_capacity_one_quarantine_and_recover() {
let mut pool = FramePool::new("single_q", 1, 4096);
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
pool.quarantine(token, "test").unwrap();
assert_eq!(pool.quarantined_count(), 1);
assert_eq!(pool.free_count(), 0);
pool.recover_from_quarantine(frame_id).unwrap();
assert_eq!(pool.quarantined_count(), 0);
assert_eq!(pool.free_count(), 1);
assert!(pool.verify_conservation().is_ok());
}
#[test]
fn test_frame_id_value_boundaries() {
let id_zero = FrameId::new(0);
assert_eq!(id_zero.value(), 0);
let id_max = FrameId::new(u32::MAX);
assert_eq!(id_max.value(), u32::MAX);
}
#[test]
fn test_get_frame_info_out_of_bounds() {
let pool = create_test_pool();
assert!(pool.get_frame_info(FrameId::new(TEST_CAPACITY)).is_none());
assert!(pool.get_frame_info(FrameId::new(u32::MAX)).is_none());
}
#[test]
fn test_get_frame_info_valid_boundary() {
let pool = create_test_pool();
assert!(pool.get_frame_info(FrameId::new(0)).is_some());
assert!(pool.get_frame_info(FrameId::new(TEST_CAPACITY - 1)).is_some());
}
#[test]
fn test_frame_id_from_u32() {
let id: FrameId = 42u32.into();
assert_eq!(id.value(), 42);
assert_eq!(id, FrameId::new(42));
}
#[test]
fn test_frame_info_fields() {
let pool = create_test_pool();
let info = pool.get_frame_info(FrameId::new(5)).unwrap();
assert_eq!(info.id(), FrameId::new(5));
assert_eq!(info.size(), TEST_FRAME_SIZE);
assert_eq!(info.state(), FrameState::Free);
assert_eq!(info.domain_id(), 0);
assert_eq!(info.generation(), 0);
assert_eq!(info.physical_addr(), 5 * TEST_FRAME_SIZE as u64);
assert_eq!(info.virtual_addr(), 5 * TEST_FRAME_SIZE as u64);
}
#[test]
fn test_allocate_release_no_panic() {
let mut pool = create_test_pool();
for _ in 0..1000 {
let token = pool.allocate(0).unwrap();
assert_eq!(token.domain_id(), 0);
pool.release(token).unwrap();
}
assert_eq!(pool.allocated_count(), 0);
assert_eq!(pool.free_count(), TEST_CAPACITY);
assert!(pool.verify_conservation().is_ok());
}
#[test]
fn test_recover_from_quarantine_advances_generation() {
let mut pool = FramePool::new("aba_recover", 1, TEST_FRAME_SIZE);
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
pool.quarantine(token, "test").unwrap();
let gen_quarantined = pool.get_frame_info(frame_id).unwrap().generation();
pool.recover_from_quarantine(frame_id).unwrap();
let gen_recovered = pool.get_frame_info(frame_id).unwrap().generation();
assert_ne!(gen_quarantined, gen_recovered);
}
#[test]
fn test_recover_from_quarantine_stale_handle_rejected() {
let mut pool = FramePool::new("aba_recover2", 1, TEST_FRAME_SIZE);
let token = pool.allocate(0).unwrap();
let frame_id = token.frame_id();
let gen_v1 = token.generation();
pool.quarantine(token, "test").unwrap();
let gen_quarantined = pool.get_frame_info(frame_id).unwrap().generation();
pool.recover_from_quarantine(frame_id).unwrap();
let token2 = pool.allocate(0).unwrap();
let gen_v2 = token2.generation();
let _ = token2;
assert_ne!(gen_v1, gen_quarantined);
assert_ne!(gen_quarantined, gen_v2);
assert_ne!(gen_v1, gen_v2);
assert!(pool.release_by_id(frame_id, gen_v1).is_err());
assert!(pool.quarantine_by_id(frame_id, gen_v1, "stale").is_err());
assert!(pool.quarantine_by_id(frame_id, gen_quarantined, "stale").is_err());
pool.release_by_id(frame_id, gen_v2).unwrap();
assert!(pool.verify_conservation().is_ok());
}
#[test]
fn test_new_invalid_params_fallback_no_panic() {
let mut pool = FramePool::new("fallback_cap", 0, TEST_FRAME_SIZE);
assert_eq!(pool.capacity(), 1);
assert_eq!(pool.frame_size(), 1);
let token = pool.allocate(0).unwrap();
pool.release(token).unwrap();
let pool = FramePool::new("fallback_size", TEST_CAPACITY, 0);
assert_eq!(pool.capacity(), 1);
assert_eq!(pool.frame_size(), 1);
assert!(FramePool::try_new("t", 0, 1).is_err());
assert!(FramePool::try_new("t", 1, 0).is_err());
}
}