use std::collections::HashMap;
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Violation {
UnexpectedCompletion {
user_data: usize,
},
DuplicateCompletion {
user_data: usize,
},
Outstanding {
user_data: usize,
},
LeakedToken {
user_data: usize,
},
BufferStillInUse {
index: u32,
outstanding: usize,
},
}
impl fmt::Display for Violation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnexpectedCompletion { user_data } => write!(
f,
"completion for user_data {user_data:#x} matches no observed push: either it \
completed twice, or a push was not reported to the contract"
),
Self::DuplicateCompletion { user_data } => write!(
f,
"user_data {user_data:#x} completed more than once, but every queued SQE \
produces exactly one completion"
),
Self::Outstanding { user_data } => {
write!(f, "user_data {user_data:#x} was pushed and never completed")
}
Self::LeakedToken { user_data } => write!(
f,
"the token for user_data {user_data:#x} was dropped unclaimed, leaking whatever \
it held for the life of the process"
),
Self::BufferStillInUse { index, outstanding } => write!(
f,
"registered buffer {index} still has {outstanding} operation(s) outstanding at \
quiescence"
),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum State {
Pushed,
PushedTokenless,
Completed,
Leaked,
DeliberatelyLeaked,
}
#[derive(Debug, Default)]
pub struct RingContract {
operations: HashMap<usize, State>,
buffers: HashMap<u32, usize>,
violations: Vec<Violation>,
}
impl RingContract {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn observe_push(&mut self, user_data: usize) {
self.operations.insert(user_data, State::Pushed);
}
pub fn observe_tokenless_push(&mut self, user_data: usize) {
self.operations.insert(user_data, State::PushedTokenless);
}
pub fn observe_completion(&mut self, user_data: usize) {
match self.operations.get(&user_data) {
None => self
.violations
.push(Violation::UnexpectedCompletion { user_data }),
Some(State::Pushed) => {
self.operations.insert(user_data, State::Leaked);
}
Some(State::PushedTokenless) => {
self.operations.insert(user_data, State::Completed);
}
Some(_) => self
.violations
.push(Violation::DuplicateCompletion { user_data }),
}
}
pub fn observe_claim(&mut self, user_data: usize) {
if let Some(state) = self.operations.get_mut(&user_data) {
*state = State::Completed;
}
}
pub fn observe_deliberate_leak(&mut self, user_data: usize) {
self.operations.insert(user_data, State::DeliberatelyLeaked);
}
pub fn observe_buffer(&mut self, index: u32, outstanding: usize) {
self.buffers.insert(index, outstanding);
}
#[must_use]
pub fn violations(&self) -> &[Violation] {
&self.violations
}
#[must_use]
pub fn check_quiescent(&self) -> Vec<Violation> {
let mut all = self.violations.clone();
let mut pending: Vec<_> = self
.operations
.iter()
.filter_map(|(user_data, state)| match state {
State::Pushed | State::PushedTokenless => Some(Violation::Outstanding {
user_data: *user_data,
}),
State::Leaked => Some(Violation::LeakedToken {
user_data: *user_data,
}),
State::Completed | State::DeliberatelyLeaked => None,
})
.collect();
pending.sort_by_key(|violation| match violation {
Violation::Outstanding { user_data } | Violation::LeakedToken { user_data } => {
*user_data
}
_ => 0,
});
all.extend(pending);
let mut busy: Vec<_> = self
.buffers
.iter()
.filter(|(_, outstanding)| **outstanding > 0)
.map(|(index, outstanding)| Violation::BufferStillInUse {
index: *index,
outstanding: *outstanding,
})
.collect();
busy.sort_by_key(|violation| match violation {
Violation::BufferStillInUse { index, .. } => *index,
_ => 0,
});
all.extend(busy);
all
}
pub fn assert_quiescent(&self) {
let violations = self.check_quiescent();
assert!(
violations.is_empty(),
"the ring contract was broken:\n{}",
violations
.iter()
.map(|violation| format!(" - {violation}"))
.collect::<Vec<_>>()
.join("\n")
);
}
}
#[cfg(test)]
mod tests;