use std::{
collections::VecDeque,
sync::{Arc, Mutex},
};
use tokio::sync::Notify;
pub fn create<T>(size: usize) -> (Sender<T>, Receiver<T>) {
assert!(size > 0, "Ring buffer size must be greater than 0");
let buffer = Arc::new(Mutex::new(Buffer::new(size)));
let notifier = Arc::new(Notify::new());
let tx = Sender::new(Arc::clone(&buffer), Arc::clone(¬ifier));
let rx = Receiver::new(buffer, notifier);
(tx, rx)
}
#[derive(Debug)]
pub struct Sender<T> {
inner: Arc<Mutex<Buffer<T>>>,
notifier: Arc<Notify>,
}
impl<T> Sender<T> {
fn new(buffer: Arc<Mutex<Buffer<T>>>, notifier: Arc<Notify>) -> Self {
Self {
inner: buffer,
notifier,
}
}
#[expect(clippy::missing_panics_doc, reason = "not handling poisoned mutex")]
pub fn send(&self, value: T) -> Result<(), SendError<T>> {
let mut guard = self.inner.lock().unwrap();
if guard.receiver_dropped {
return Err(BufferClosedError.into());
}
if let Some(Side::Receiver) = guard.disruption_handled {
return Err(SendError::Disrupted(value));
}
guard.push(value);
drop(guard);
self.notifier.notify_one();
Ok(())
}
pub fn handle_disruption(&self) {
#[expect(clippy::missing_panics_doc, reason = "not handling poisoned mutex")]
self.inner.lock().unwrap().handle_disruption_sender();
}
#[expect(clippy::missing_panics_doc, reason = "not handling poisoned mutex")]
pub fn is_empty(&self) -> bool {
self.inner.lock().unwrap().is_empty()
}
#[expect(clippy::missing_panics_doc, reason = "not handling poisoned mutex")]
pub fn is_closed(&self) -> bool {
self.inner.lock().unwrap().receiver_dropped
}
}
impl<T> Drop for Sender<T> {
fn drop(&mut self) {
{
let mut guard = self.inner.lock().unwrap();
guard.sender_dropped = true;
}
self.notifier.notify_one();
}
}
#[derive(Debug)]
pub struct Receiver<T> {
inner: Arc<Mutex<Buffer<T>>>,
notifiee: Arc<Notify>,
}
impl<T> Receiver<T> {
fn new(buffer: Arc<Mutex<Buffer<T>>>, notifiee: Arc<Notify>) -> Self {
Self {
inner: buffer,
notifiee,
}
}
#[expect(clippy::missing_panics_doc, reason = "not handling poisoned mutex")]
pub async fn recv(&self) -> Result<T, BufferClosedError> {
loop {
{
let mut guard = self.inner.lock().unwrap();
if guard.sender_dropped {
return Err(BufferClosedError);
}
if let Some(Side::Sender) = guard.disruption_handled {
tracing::debug!("Receiving when sender handled disruption.");
}
if let Some(value) = guard.pop() {
return Ok(value);
}
}
self.notifiee.notified().await;
}
}
pub fn handle_disruption(&self) {
#[expect(clippy::missing_panics_doc, reason = "not handling poisoned mutex")]
self.inner.lock().unwrap().handle_disruption_receiver();
}
#[expect(clippy::missing_panics_doc, reason = "not handling poisoned mutex")]
pub fn is_closed(&self) -> bool {
self.inner.lock().unwrap().sender_dropped
}
}
impl<T> Drop for Receiver<T> {
fn drop(&mut self) {
let mut guard = self.inner.lock().unwrap();
guard.receiver_dropped = true;
}
}
#[derive(Debug)]
struct Buffer<T> {
data: VecDeque<T>,
capacity: usize,
sender_dropped: bool,
receiver_dropped: bool,
disruption_handled: Option<Side>,
}
impl<T> Buffer<T> {
fn new(capacity: usize) -> Self {
Self {
data: VecDeque::with_capacity(capacity),
capacity,
sender_dropped: false,
receiver_dropped: false,
disruption_handled: None,
}
}
fn push(&mut self, value: T) {
if self.data.len() == self.capacity {
self.data.pop_front();
}
self.data.push_back(value);
}
fn pop(&mut self) -> Option<T> {
self.data.pop_front()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
fn handle_disruption_sender(&mut self) {
match self.disruption_handled {
None => {
self.data.clear();
self.disruption_handled = Some(Side::Sender);
}
Some(Side::Sender) => tracing::warn!("Handle disruption sender called multiple times."),
Some(Side::Receiver) => {
self.disruption_handled = None;
}
}
}
fn handle_disruption_receiver(&mut self) {
match self.disruption_handled {
None => {
self.data.clear();
self.disruption_handled = Some(Side::Receiver);
}
Some(Side::Sender) => {
self.disruption_handled = None;
}
Some(Side::Receiver) => {
tracing::warn!("Handle disruption receiver called multiple times.");
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Side {
Sender,
Receiver,
}
#[derive(Debug, thiserror::Error)]
pub enum SendError<T> {
#[error(transparent)]
BufferClosed(#[from] BufferClosedError),
#[error("Send when disrupted")]
Disrupted(T),
}
#[derive(Debug, thiserror::Error)]
#[error("Buffer has been closed")]
pub struct BufferClosedError;
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn create_buffer() {
let (tx, _rx) = create::<usize>(5);
assert!(tx.is_empty());
}
#[tokio::test]
async fn basic_send_receive() {
let (tx, rx) = create(2);
tx.send(1).unwrap();
tx.send(2).unwrap();
assert_eq!(rx.recv().await.unwrap(), 1);
assert_eq!(rx.recv().await.unwrap(), 2);
}
#[tokio::test]
async fn buffer_overflow() {
let (tx, rx) = create(2);
tx.send(1).unwrap();
tx.send(2).unwrap();
tx.send(3).unwrap();
assert_eq!(rx.recv().await.unwrap(), 2); assert_eq!(rx.recv().await.unwrap(), 3);
}
#[tokio::test]
async fn sender_drop() {
let (tx, rx) = create::<i32>(2);
tx.send(1).unwrap();
drop(tx);
assert!(rx.is_closed());
assert!(rx.recv().await.is_err());
}
#[tokio::test]
async fn receiver_drop() {
let (tx, rx) = create::<i32>(2);
drop(rx);
assert!(tx.is_closed());
assert!(tx.send(1).is_err());
}
#[tokio::test]
async fn empty_buffer() {
let (tx, _rx) = create::<i32>(2);
assert!(tx.is_empty());
tx.send(1).unwrap();
assert!(!tx.is_empty());
}
#[tokio::test]
async fn concurrent_send_receive() {
let (tx, rx) = create(3);
let tx_notified = Arc::new(Notify::new());
let rx_notified = Arc::clone(&tx_notified);
let handle = tokio::spawn(async move {
for i in 0..5 {
tx.send(i).unwrap();
tx_notified.notified().await;
}
});
let mut received = Vec::new();
for _ in 0..5 {
if let Ok(value) = rx.recv().await {
received.push(value);
rx_notified.notify_one();
}
}
assert_eq!(received.len(), 5);
for i in 1..received.len() {
assert_eq!(received[i], i);
}
handle.await.unwrap();
}
#[tokio::test]
async fn cancel_safety() {
let (tx, rx) = create(2);
tx.send(1).unwrap();
tokio::select! {
biased;
_ = async {} => {
}
_ = rx.recv() => {
panic!("This branch should not complete first");
}
}
assert_eq!(rx.recv().await.unwrap(), 1);
}
#[tokio::test]
async fn handle_disruption_sender_first() {
let (tx, rx) = create(2);
tx.send(1).unwrap();
tx.handle_disruption();
{
let inner = tx.inner.lock().unwrap();
assert!(inner.data.is_empty());
assert_eq!(inner.disruption_handled, Some(Side::Sender));
}
rx.handle_disruption();
let inner = rx.inner.lock().unwrap();
assert!(inner.data.is_empty());
assert_eq!(inner.disruption_handled, None);
}
#[tokio::test]
async fn handle_disruption_receiver_first() {
let (tx, rx) = create(2);
tx.send(1).unwrap();
rx.handle_disruption();
{
let inner = rx.inner.lock().unwrap();
assert!(inner.data.is_empty());
assert_eq!(inner.disruption_handled, Some(Side::Receiver));
}
tx.handle_disruption();
let inner = tx.inner.lock().unwrap();
assert!(inner.data.is_empty());
assert_eq!(inner.disruption_handled, None);
}
#[tokio::test]
async fn handle_disruption_send_after_receive_handles() {
let (tx, rx) = create(2);
tx.send(1).unwrap();
rx.handle_disruption();
let res = tx.send(42);
let Err(SendError::Disrupted(val)) = &res else {
panic!("Expected send to be disrupted {res:?}");
};
assert_eq!(*val, 42);
}
#[tokio::test]
async fn sender_handles_disruption_while_recv() {
let (tx, rx) = create(2);
let recv_fut = rx.recv();
tokio::select! {
biased;
_ = recv_fut => {
panic!("This branch should not complete first");
}
_ = async {
tx.handle_disruption();
tx.send(5).unwrap();
} => {
}
}
let received = rx.recv().await.unwrap();
assert_eq!(received, 5);
rx.handle_disruption();
}
}