use std::sync::{Condvar, Mutex};
const DRAIN_CAP_BYTES: usize = 64 * 1024;
#[derive(Default)]
pub(super) struct ReplyQueue {
state: Mutex<State>,
changed: Condvar,
}
#[derive(Default)]
struct State {
pending: Option<Vec<u8>>,
next_ticket: u64,
next_send: u64,
draining: bool,
overflowed: bool,
closed: bool,
}
#[must_use]
pub(super) struct Reply {
ticket: u64,
bytes: Vec<u8>,
}
#[must_use]
#[derive(Debug, PartialEq, Eq)]
pub(super) enum Sent {
Queued,
Closed,
Dropped { bytes: usize, overflow_began: bool },
}
impl ReplyQueue {
pub(super) fn prepare(&self, bytes: Vec<u8>) -> Reply {
let mut state = self.state.lock().unwrap();
let ticket = state.next_ticket;
state.next_ticket = ticket
.checked_add(1)
.expect("terminal reply ticket overflow");
Reply { ticket, bytes }
}
pub(super) fn send(&self, reply: Reply) -> Sent {
let mut state = self.state.lock().unwrap();
while !state.closed
&& (reply.ticket != state.next_send || (!state.draining && state.pending.is_some()))
{
state = self.changed.wait(state).unwrap();
}
if state.closed {
return Sent::Closed;
}
state.next_send += 1;
let bytes = reply.bytes.len();
let held = state.pending.as_ref().map(Vec::len);
let sent = match held {
None => {
state.pending = Some(reply.bytes);
Sent::Queued
}
Some(_) if state.overflowed => Sent::Dropped {
bytes,
overflow_began: false,
},
Some(held) if held + bytes <= DRAIN_CAP_BYTES => {
if let Some(pending) = state.pending.as_mut() {
pending.extend_from_slice(&reply.bytes);
}
Sent::Queued
}
Some(_) => {
state.overflowed = true;
Sent::Dropped {
bytes,
overflow_began: true,
}
}
};
self.changed.notify_all();
sent
}
pub(super) fn recv(&self) -> Option<Vec<u8>> {
let mut state = self.state.lock().unwrap();
while !state.closed && state.pending.is_none() {
state = self.changed.wait(state).unwrap();
}
let bytes = state.pending.take();
state.overflowed = false;
self.changed.notify_all();
bytes
}
pub(super) fn stop_waiting(&self) {
let mut state = self.state.lock().unwrap();
state.draining = true;
self.changed.notify_all();
}
pub(super) fn close(&self) {
let mut state = self.state.lock().unwrap();
state.closed = true;
self.changed.notify_all();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, mpsc};
use std::thread;
use std::time::Duration;
#[test]
fn preserves_model_order_when_producers_arrive_out_of_order() {
let queue = Arc::new(ReplyQueue::default());
let first = queue.prepare(b"first".to_vec());
let second = queue.prepare(b"second".to_vec());
let later = {
let queue = queue.clone();
thread::spawn(move || queue.send(second))
};
assert_eq!(queue.send(first), Sent::Queued);
assert_eq!(queue.recv(), Some(b"first".to_vec()));
assert_eq!(queue.recv(), Some(b"second".to_vec()));
assert_eq!(later.join().unwrap(), Sent::Queued);
queue.close();
assert_eq!(queue.recv(), None);
}
#[test]
fn closing_a_full_queue_releases_waiting_producers() {
let queue = Arc::new(ReplyQueue::default());
assert_eq!(queue.send(queue.prepare(b"queued".to_vec())), Sent::Queued);
let reply = queue.prepare(b"waiting".to_vec());
let (done, received) = mpsc::channel();
let sender = {
let queue = queue.clone();
thread::spawn(move || done.send(queue.send(reply)).unwrap())
};
assert!(received.recv_timeout(Duration::from_millis(20)).is_err());
queue.close();
assert_eq!(
received.recv_timeout(Duration::from_secs(5)).unwrap(),
Sent::Closed
);
sender.join().unwrap();
assert_eq!(queue.recv(), Some(b"queued".to_vec()));
assert_eq!(queue.recv(), None);
}
#[test]
fn draining_releases_a_parked_producer_into_the_batch_in_order() {
let queue = Arc::new(ReplyQueue::default());
assert_eq!(queue.send(queue.prepare(b"queued".to_vec())), Sent::Queued);
let reply = queue.prepare(b"parked".to_vec());
let (done, received) = mpsc::channel();
let sender = {
let queue = queue.clone();
thread::spawn(move || done.send(queue.send(reply)).unwrap())
};
assert!(received.recv_timeout(Duration::from_millis(20)).is_err());
queue.stop_waiting();
assert_eq!(
received.recv_timeout(Duration::from_secs(5)).unwrap(),
Sent::Queued
);
sender.join().unwrap();
assert_eq!(queue.send(queue.prepare(b"later".to_vec())), Sent::Queued);
assert_eq!(queue.recv(), Some(b"queuedparkedlater".to_vec()));
queue.close();
assert_eq!(queue.recv(), None);
}
#[test]
fn a_draining_batch_drops_past_its_cap_and_never_leaves_a_gap() {
let queue = ReplyQueue::default();
queue.stop_waiting();
let filler = vec![b'x'; DRAIN_CAP_BYTES - 4];
assert_eq!(queue.send(queue.prepare(filler)), Sent::Queued);
assert_eq!(queue.send(queue.prepare(b"abc".to_vec())), Sent::Queued);
assert_eq!(
queue.send(queue.prepare(b"over".to_vec())),
Sent::Dropped {
bytes: 4,
overflow_began: true
}
);
assert_eq!(
queue.send(queue.prepare(b"z".to_vec())),
Sent::Dropped {
bytes: 1,
overflow_began: false
}
);
let batch = queue.recv().unwrap();
assert_eq!(batch.len(), DRAIN_CAP_BYTES - 1);
assert!(batch.ends_with(b"abc"));
assert_eq!(queue.send(queue.prepare(b"next".to_vec())), Sent::Queued);
assert_eq!(queue.recv(), Some(b"next".to_vec()));
}
}