use std::collections::VecDeque;
use std::sync::{Arc, Condvar, Mutex};
use rudb_common::{Error, Result};
#[derive(Debug)]
pub struct Request {
offset: u64,
buf: Vec<u8>,
}
impl Request {
#[must_use]
pub fn new(offset: u64, len: usize) -> Self {
Self { offset, buf: vec![0; len] }
}
#[must_use]
pub fn reusing(offset: u64, buf: Vec<u8>) -> Self {
Self { offset, buf }
}
#[must_use]
pub fn offset(&self) -> u64 {
self.offset
}
#[must_use]
pub fn len(&self) -> usize {
self.buf.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
#[must_use]
pub fn end(&self) -> u64 {
self.offset + self.buf.len() as u64
}
#[must_use]
pub fn into_buffer(self) -> Vec<u8> {
self.buf
}
}
#[derive(Debug)]
pub struct Response {
index: usize,
offset: u64,
read: usize,
buf: Vec<u8>,
}
impl Response {
#[must_use]
pub fn new(index: usize, offset: u64, read: usize, buf: Vec<u8>) -> Self {
Self { index, offset, read, buf }
}
#[must_use]
pub fn index(&self) -> usize {
self.index
}
#[must_use]
pub fn offset(&self) -> u64 {
self.offset
}
#[must_use]
pub fn read(&self) -> usize {
self.read
}
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.buf[..self.read]
}
#[must_use]
pub fn is_short(&self) -> bool {
self.read < self.buf.len()
}
#[must_use]
pub fn into_buffer(self) -> Vec<u8> {
self.buf
}
}
#[derive(Debug)]
struct State {
slots: Vec<Option<Result<Response>>>,
ready: VecDeque<usize>,
outstanding: usize,
fillers: usize,
}
#[derive(Debug)]
struct Shared {
state: Mutex<State>,
wake: Condvar,
}
impl Shared {
fn lock(&self) -> std::sync::MutexGuard<'_, State> {
self.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
}
}
#[derive(Debug)]
pub struct Filler {
shared: Arc<Shared>,
}
impl Clone for Filler {
fn clone(&self) -> Self {
self.shared.lock().fillers += 1;
Self { shared: Arc::clone(&self.shared) }
}
}
impl Filler {
pub fn finish(&self, index: usize, outcome: Result<Response>) {
let mut state = self.shared.lock();
if state.slots.get(index).is_none_or(Option::is_some) {
return;
}
state.slots[index] = Some(outcome);
state.ready.push_back(index);
state.outstanding -= 1;
drop(state);
self.shared.wake.notify_all();
}
}
impl Drop for Filler {
fn drop(&mut self) {
let mut state = self.shared.lock();
state.fillers -= 1;
if state.fillers > 0 || state.outstanding == 0 {
return;
}
for index in 0..state.slots.len() {
if state.slots[index].is_some() {
continue;
}
state.slots[index] =
Some(Err(Error::io("the I/O backend stopped before this read finished")));
state.ready.push_back(index);
}
state.outstanding = 0;
drop(state);
self.shared.wake.notify_all();
}
}
#[derive(Debug)]
pub struct Completion {
shared: Arc<Shared>,
taken: usize,
}
impl Completion {
#[must_use]
pub fn pending(count: usize) -> (Self, Filler) {
let mut slots = Vec::with_capacity(count);
slots.resize_with(count, || None);
let shared = Arc::new(Shared {
state: Mutex::new(State {
slots,
ready: VecDeque::with_capacity(count),
outstanding: count,
fillers: 1,
}),
wake: Condvar::new(),
});
(Self { shared: Arc::clone(&shared), taken: 0 }, Filler { shared })
}
#[must_use]
pub fn ready(responses: Vec<Result<Response>>) -> Self {
let (completion, filler) = Self::pending(responses.len());
for (index, outcome) in responses.into_iter().enumerate() {
filler.finish(index, outcome);
}
completion
}
#[must_use]
pub fn len(&self) -> usize {
self.shared.lock().slots.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn ready_count(&self) -> usize {
self.shared.lock().ready.len()
}
#[must_use]
pub fn is_done(&self) -> bool {
self.shared.lock().outstanding == 0
}
pub fn take(&mut self) -> Option<Result<Response>> {
let mut state = self.shared.lock();
loop {
if let Some(index) = state.ready.pop_front() {
self.taken += 1;
return state.slots[index].take();
}
if state.outstanding == 0 {
return None;
}
state = self.shared.wake.wait(state).unwrap_or_else(std::sync::PoisonError::into_inner);
}
}
pub fn wait(mut self) -> Result<Vec<Response>> {
let mut state = self.shared.lock();
while state.outstanding > 0 {
state = self.shared.wake.wait(state).unwrap_or_else(std::sync::PoisonError::into_inner);
}
state.ready.clear();
self.taken = state.slots.len();
let mut out = Vec::with_capacity(state.slots.len());
let mut failure = None;
for slot in &mut state.slots {
match slot.take() {
Some(Ok(response)) => out.push(response),
Some(Err(error)) => failure = failure.or(Some(error)),
None => {}
}
}
match failure {
Some(error) => Err(error),
None => Ok(out),
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use rudb_common::Error;
use super::{Completion, Request, Response};
fn response(index: usize, bytes: &[u8]) -> Response {
Response::new(index, index as u64, bytes.len(), bytes.to_vec())
}
#[test]
fn a_request_states_its_length_through_its_buffer() {
let request = Request::new(64, 8);
assert_eq!(request.offset(), 64);
assert_eq!(request.len(), 8);
assert_eq!(request.end(), 72);
assert!(!request.is_empty());
assert_eq!(Request::reusing(0, vec![1, 2, 3]).len(), 3);
}
#[test]
fn wait_returns_responses_in_submission_order_however_they_finished() {
let (completion, filler) = Completion::pending(3);
filler.finish(2, Ok(response(2, b"cc")));
filler.finish(0, Ok(response(0, b"a")));
filler.finish(1, Ok(response(1, b"bbb")));
let responses = completion.wait().unwrap();
assert_eq!(responses.iter().map(Response::index).collect::<Vec<_>>(), [0, 1, 2]);
assert_eq!(responses[1].bytes(), b"bbb");
}
#[test]
fn take_returns_responses_in_completion_order_and_then_stops() {
let (mut completion, filler) = Completion::pending(2);
filler.finish(1, Ok(response(1, b"second")));
assert_eq!(completion.ready_count(), 1);
assert!(!completion.is_done());
assert_eq!(completion.take().unwrap().unwrap().index(), 1);
filler.finish(0, Ok(response(0, b"first")));
assert!(completion.is_done());
assert_eq!(completion.take().unwrap().unwrap().index(), 0);
assert!(completion.take().is_none());
}
#[test]
fn wait_reports_the_first_failure_by_position_not_by_arrival() {
let (completion, filler) = Completion::pending(3);
filler.finish(2, Err(Error::io("late")));
filler.finish(1, Err(Error::io("early")));
filler.finish(0, Ok(response(0, b"fine")));
let error = completion.wait().unwrap_err();
assert!(error.to_string().contains("early"), "{error}");
}
#[test]
fn a_short_read_is_a_response_and_not_an_error() {
let completion = Completion::ready(vec![Ok(Response::new(0, 0, 2, vec![7, 7, 0, 0]))]);
let responses = completion.wait().unwrap();
assert!(responses[0].is_short());
assert_eq!(responses[0].bytes(), &[7, 7]);
assert_eq!(responses[0].read(), 2);
}
#[test]
fn a_buffer_comes_back_out_of_the_response_to_be_used_again() {
let request = Request::reusing(0, vec![0; 4]);
let buf = request.into_buffer();
let response = Response::new(0, 0, 4, buf);
assert_eq!(response.into_buffer().len(), 4);
}
#[test]
fn a_backend_that_goes_away_wakes_the_waiter_instead_of_hanging_it() {
let (completion, filler) = Completion::pending(2);
filler.finish(0, Ok(response(0, b"one")));
drop(filler);
let error = completion.wait().unwrap_err();
assert!(error.to_string().contains("stopped before"), "{error}");
}
#[test]
fn the_last_filler_out_is_the_one_that_wakes_the_waiter() {
let (mut completion, filler) = Completion::pending(2);
let second = filler.clone();
drop(filler);
assert!(!completion.is_done());
second.finish(0, Ok(response(0, b"one")));
drop(second);
assert_eq!(completion.take().unwrap().unwrap().index(), 0);
assert!(completion.take().unwrap().is_err());
assert!(completion.take().is_none());
}
#[test]
fn filling_a_slot_twice_leaves_the_first_answer_in_place() {
let (mut completion, filler) = Completion::pending(1);
filler.finish(0, Ok(response(0, b"kept")));
filler.finish(0, Err(Error::io("ignored")));
assert_eq!(completion.take().unwrap().unwrap().bytes(), b"kept");
}
#[test]
fn a_waiter_blocks_until_another_thread_fills_the_last_slot() {
let (completion, filler) = Completion::pending(2);
let filled = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&filled);
let worker = std::thread::spawn(move || {
for index in 0..2 {
std::thread::sleep(std::time::Duration::from_millis(5));
counter.fetch_add(1, Ordering::SeqCst);
filler.finish(index, Ok(response(index, b"x")));
}
});
assert_eq!(completion.wait().unwrap().len(), 2);
assert_eq!(filled.load(Ordering::SeqCst), 2);
worker.join().unwrap();
}
}