use std::io;
use std::path::PathBuf;
use std::time::{Duration, Instant};
use win_durable_ioring::windows_backend::Win32FileTarget;
use win_durable_ioring::{
DeliveryMode, Dioring, DurableRing, Entry, IoTargetKey, LineageHandle, OpIdOf, PushError,
PushRefusal, PushResult, Setup, SharedHandle, TargetSetup,
};
#[cfg(test)]
mod tests;
const SLICE: Duration = Duration::from_millis(50);
pub struct Blocked<R: DurableRing> {
pub refused: PushError<R::Ids, R::Buffer, R::Context>,
pub why: GaveUp,
}
#[derive(Debug)]
pub enum GaveUp {
NotRetryable,
TimedOut,
MakeRoom(io::Error),
}
pub fn push_blocking<R, A, M>(
ring: &mut R,
buffer: R::Buffer,
context: R::Context,
patience: Duration,
mut attempt: A,
mut make_room: M,
) -> Result<OpIdOf<R>, Blocked<R>>
where
R: DurableRing,
A: FnMut(&mut R, R::Buffer, R::Context) -> PushResult<R>,
M: FnMut(&mut R, Duration) -> io::Result<()>,
{
let deadline = Instant::now() + patience;
let (mut buffer, mut context) = (buffer, context);
loop {
let refused = match attempt(ring, buffer, context) {
Ok(id) => return Ok(id),
Err(refused) => refused,
};
let clears = matches!(&refused.reason, PushRefusal::Backend(cause) if cause.is_retryable());
if !clears || refused.buffer.is_none() {
return Err(Blocked {
refused,
why: GaveUp::NotRetryable,
});
}
let left = deadline.saturating_duration_since(Instant::now());
if left.is_zero() {
return Err(Blocked {
refused,
why: GaveUp::TimedOut,
});
}
if let Err(error) = make_room(ring, left.min(SLICE)) {
return Err(Blocked {
refused,
why: GaveUp::MakeRoom(error),
});
}
let PushError {
buffer: back,
context: back_context,
..
} = refused;
buffer = back.expect("checked above: an owned-buffer push hands its buffer back");
context = back_context;
}
}
type Ring = Dioring<Vec<u8>>;
const FILE: IoTargetKey = IoTargetKey(1);
#[derive(Clone, Copy, Debug)]
pub enum Delivery {
Pool,
Pumped,
Signalled,
}
impl Delivery {
fn mode(self) -> DeliveryMode<'static, 'static> {
match self {
Self::Pool => DeliveryMode::Pool(None),
Self::Pumped => DeliveryMode::Pumped,
Self::Signalled => DeliveryMode::Signalled(None),
}
}
fn make_room(self, ring: &mut Ring, slice: Duration) -> io::Result<()> {
match self {
Self::Pool => {
std::thread::sleep(slice.min(Duration::from_millis(1)));
Ok(())
}
Self::Pumped | Self::Signalled => ring.pump(Some(slice)).map(|_| ()),
}
}
}
pub struct Scratch(PathBuf);
impl Scratch {
pub fn new(tag: &str) -> Self {
let path = std::env::temp_dir().join(format!(
"dioring-blocking-push-{}-{tag}.bin",
std::process::id()
));
std::fs::write(&path, []).expect("a scratch file");
Self(path)
}
fn target(&self) -> TargetSetup {
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&self.0)
.expect("open the scratch file");
TargetSetup {
key: FILE,
target: Win32FileTarget::new(SharedHandle::new(file.into())),
}
}
}
impl Drop for Scratch {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.0);
}
}
pub fn build(delivery: Delivery, scratch: &Scratch, queue: u32) -> Ring {
Ring::new(Setup {
submission_queue_size: queue,
completion_queue_size: queue,
files: vec![scratch.target()],
buffers: Vec::new(),
provider: None,
diagnostics: None,
delivery: delivery.mode(),
})
.expect("build an instance")
.0
}
pub fn write_at<'a>(
lineage: &'a LineageHandle,
file: IoTargetKey,
offset: u64,
) -> impl FnMut(&mut Ring, Vec<u8>, ()) -> PushResult<Ring> + 'a {
move |ring, buffer, context| ring.write(file, offset, buffer, lineage.at(1_u64), context)
}
#[derive(Debug)]
pub struct Report {
pub pushed: usize,
pub made_room: usize,
pub completed: usize,
}
pub fn run(delivery: Delivery, writes: usize, queue: u32) -> Report {
let scratch = Scratch::new(&format!("{delivery:?}"));
let mut ring = build(delivery, &scratch, queue);
let lineage = ring.default_lineage();
let mut made_room = 0;
let mut pushed = 0;
for i in 0..writes {
let offset = u64::try_from(i).expect("a small index") * 8;
let result = push_blocking(
&mut ring,
vec![u8::try_from(i % 256).expect("a byte"); 8],
(),
Duration::from_secs(10),
write_at(&lineage, FILE, offset),
|ring, slice| {
made_room += 1;
delivery.make_room(ring, slice)
},
);
match result {
Ok(_) => pushed += 1,
Err(blocked) => panic!("a push gave up: {:?}", blocked.why),
}
}
let mut completed = 0;
let started = Instant::now();
while completed < writes {
assert!(
started.elapsed() < Duration::from_secs(10),
"completions never arrived"
);
match ring.pop().expect("pop") {
Some(Entry::Op(_)) => completed += 1,
Some(other) => panic!("expected a completion, got {other:?}"),
None => delivery.make_room(&mut ring, SLICE).expect("make room"),
}
}
Report {
pushed,
made_room,
completed,
}
}
fn main() {
for delivery in [Delivery::Pool, Delivery::Pumped, Delivery::Signalled] {
println!("{delivery:?}: {:?}", run(delivery, 100, 4));
}
}