use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use super::Token;
use crate::IoRing;
use crate::buf::IoBuf;
use crate::ring::Completion;
#[derive(Debug)]
struct DropTracking {
data: Vec<u8>,
dropped: Arc<AtomicBool>,
}
impl Drop for DropTracking {
fn drop(&mut self) {
self.dropped.store(true, Ordering::SeqCst);
}
}
unsafe impl IoBuf for DropTracking {
fn stable_ptr(&self) -> *const u8 {
self.data.as_ptr()
}
fn bytes_len(&self) -> usize {
self.data.len()
}
}
fn tracked_buffer() -> (DropTracking, Arc<AtomicBool>) {
let dropped = Arc::new(AtomicBool::new(false));
(
DropTracking {
data: vec![0_u8; 8],
dropped: dropped.clone(),
},
dropped,
)
}
fn settle(ring: &mut IoRing) {
while ring.outstanding() > 0 {
ring.record_completion();
}
}
#[test]
fn dropping_an_unclaimed_token_never_runs_the_buffers_destructor() {
let mut ring = IoRing::new(64, 128).expect("create ring");
let (buffer, dropped) = tracked_buffer();
let token = Token::new(&mut ring, buffer).expect("mint token");
drop(token);
assert!(
!dropped.load(Ordering::SeqCst),
"an unclaimed token's Drop must forget the buffer, not free it -- \
a real IoRing may still be writing through it"
);
settle(&mut ring);
drop(ring);
assert!(!dropped.load(Ordering::SeqCst));
}
#[test]
fn claiming_a_token_returns_the_buffer_for_normal_disposal() {
let mut ring = IoRing::new(64, 128).expect("create ring");
let (buffer, dropped) = tracked_buffer();
let token = Token::new(&mut ring, buffer).expect("mint token");
let id = token.id();
let completion = Completion::synthetic(id, 0, ring.ring_id());
let claimed = token.claim_if(&completion).expect("id matches itself");
assert!(
!dropped.load(Ordering::SeqCst),
"claiming must not itself drop the buffer"
);
drop(claimed);
assert!(
dropped.load(Ordering::SeqCst),
"the caller's own drop of the returned buffer must run normally"
);
settle(&mut ring);
}
#[test]
fn claim_if_rejects_a_mismatched_user_data_and_returns_the_token_unchanged() {
let mut ring = IoRing::new(64, 128).expect("create ring");
let (buffer, dropped) = tracked_buffer();
let token = Token::new(&mut ring, buffer).expect("mint token");
let real_id = token.id();
let mismatched = Completion::synthetic(real_id.wrapping_add(1), 0, ring.ring_id());
let token = token
.claim_if(&mismatched)
.expect_err("a stale id must not claim this token");
assert_eq!(
token.id(),
real_id,
"the rejected token is handed back unchanged"
);
assert!(!dropped.load(Ordering::SeqCst));
let matching = Completion::synthetic(real_id, 0, ring.ring_id());
let claimed = token.claim_if(&matching).expect("the real id still works");
drop(claimed);
assert!(dropped.load(Ordering::SeqCst));
settle(&mut ring);
}
#[test]
fn each_token_on_a_ring_gets_a_distinct_id() {
let mut ring = IoRing::new(64, 128).expect("create ring");
let (a, _) = tracked_buffer();
let (b, _) = tracked_buffer();
let token_a = Token::new(&mut ring, a).expect("mint token a");
let token_b = Token::new(&mut ring, b).expect("mint token b");
assert_ne!(token_a.id(), token_b.id());
drop(token_a);
drop(token_b);
settle(&mut ring);
}
#[test]
fn minting_a_token_increments_the_rings_outstanding_count() {
let mut ring = IoRing::new(64, 128).expect("create ring");
assert_eq!(ring.outstanding(), 0);
let (buffer, _dropped) = tracked_buffer();
let token = Token::new(&mut ring, buffer).expect("mint token");
assert_eq!(ring.outstanding(), 1);
drop(token);
assert_eq!(
ring.outstanding(),
1,
"outstanding tracks completions observed, not tokens dropped"
);
ring.record_completion();
assert_eq!(ring.outstanding(), 0);
}
#[test]
fn claim_if_rejects_a_matching_user_data_from_a_different_ring() {
let mut ring_a = IoRing::new(64, 128).expect("create ring a");
let mut ring_b = IoRing::new(64, 128).expect("create ring b");
let (buffer, dropped) = tracked_buffer();
let token = Token::new(&mut ring_a, buffer).expect("mint token on ring a");
let id = token.id();
let wrong_ring = Completion::synthetic(id, 0, ring_b.ring_id());
let token = token
.claim_if(&wrong_ring)
.expect_err("a completion from a different ring must not claim this token");
assert!(!dropped.load(Ordering::SeqCst));
let right_ring = Completion::synthetic(id, 0, ring_a.ring_id());
let claimed = token
.claim_if(&right_ring)
.expect("the same ring's completion still claims it");
drop(claimed);
assert!(dropped.load(Ordering::SeqCst));
settle(&mut ring_a);
settle(&mut ring_b);
}
#[test]
fn a_tokens_debug_names_its_operation() {
let mut ring = IoRing::new(8, 8).expect("create ring");
struct NotDebug(Vec<u8>);
unsafe impl IoBuf for NotDebug {
fn stable_ptr(&self) -> *const u8 {
self.0.as_ptr()
}
fn bytes_len(&self) -> usize {
self.0.len()
}
}
let token = Token::new(&mut ring, NotDebug(vec![0_u8; 8])).expect("mint a token");
let id = token.id();
let rendered = format!("{token:?}");
assert!(
rendered.contains(&id.to_string()),
"a token's Debug must name the operation it will claim, but was {rendered:?}"
);
assert!(
rendered.contains("Token"),
"a token's Debug must name its type, but was {rendered:?}"
);
drop(token);
settle(&mut ring);
}