use crate::error::TransportError;
use crate::packet::Packet;
use std::time::Duration;
use tokio::sync::mpsc::{self, error::SendTimeoutError};
pub(crate) const SEND_QUEUE_CAPACITY: usize = 2048;
pub(crate) const SEND_QUEUE_WAIT: Duration = Duration::from_millis(100);
pub(crate) async fn send_bounded(
queue: &mpsc::Sender<Packet>,
packet: Packet,
queue_name: &'static str,
closed_msg: &'static str,
) -> Result<(), TransportError> {
match queue.send_timeout(packet, SEND_QUEUE_WAIT).await {
Ok(()) => Ok(()),
Err(SendTimeoutError::Timeout(_)) => Err(TransportError::resource_error(
queue_name,
queue.max_capacity() - queue.capacity(),
queue.max_capacity(),
)),
Err(SendTimeoutError::Closed(_)) => {
Err(TransportError::connection_error(closed_msg, false))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn packet(id: u32) -> Packet {
Packet::one_way(id, vec![0u8; 4])
}
#[tokio::test]
async fn sends_immediately_when_queue_has_room() {
let (tx, _rx) = mpsc::channel(2);
assert!(send_bounded(&tx, packet(1), "q", "closed").await.is_ok());
}
#[tokio::test]
async fn absorbs_transient_burst_instead_of_failing() {
let (tx, mut rx) = mpsc::channel(1);
tx.send(packet(1)).await.unwrap();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(20)).await;
let _ = rx.recv().await;
tokio::time::sleep(Duration::from_secs(1)).await;
});
assert!(send_bounded(&tx, packet(2), "q", "closed").await.is_ok());
}
#[tokio::test]
async fn reports_real_depth_when_queue_stays_full() {
let (tx, _rx) = mpsc::channel(2);
tx.send(packet(1)).await.unwrap();
tx.send(packet(2)).await.unwrap();
let error = send_bounded(&tx, packet(3), "tcp_outbound_queue", "closed")
.await
.unwrap_err();
assert!(
matches!(
error,
TransportError::Resource { ref resource, current: 2, limit: 2 }
if resource == "tcp_outbound_queue"
),
"unexpected error: {error:?}"
);
}
#[tokio::test]
async fn reports_connection_closed_when_peer_is_gone() {
let (tx, rx) = mpsc::channel(1);
drop(rx);
let error = send_bounded(&tx, packet(1), "q", "TCP connection closed")
.await
.unwrap_err();
assert!(
matches!(error, TransportError::Connection { .. }),
"unexpected error: {error:?}"
);
}
}