use std::time::Duration;
use async_trait::async_trait;
use log::warn;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt};
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use tokio::net::TcpStream;
use tokio::sync::{watch, Mutex, MutexGuard};
use crate::errors::Error;
use crate::transport::common::validate_frame_length;
use crate::transport::r#async::ShutdownSignal;
use crate::transport::raw_capture::RawFrameTap;
#[async_trait]
pub(crate) trait AsyncIo {
async fn read_message(&self) -> Result<Vec<u8>, Error>;
async fn write_all(&self, buf: &[u8]) -> Result<(), Error>;
async fn close(&self);
}
#[async_trait]
pub(crate) trait AsyncReconnect {
async fn reconnect(&self) -> Result<(), Error>;
async fn sleep(&self, duration: Duration, shutdown: &ShutdownSignal);
}
pub(crate) trait AsyncStream: AsyncIo + AsyncReconnect + Send + Sync + 'static + std::fmt::Debug {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SocketState {
Live,
Broken,
Closed,
}
impl SocketState {
fn error(self) -> Option<Error> {
match self {
Self::Live => None,
Self::Broken => Some(Error::ConnectionReset),
Self::Closed => Some(Error::Shutdown),
}
}
}
#[derive(Debug)]
pub(crate) struct AsyncTcpSocket {
reader: Mutex<Option<OwnedReadHalf>>,
writer: Mutex<Option<OwnedWriteHalf>>,
state: watch::Sender<SocketState>,
connection_url: String,
tcp_no_delay: bool,
tap: RawFrameTap,
}
impl AsyncTcpSocket {
pub async fn connect(address: &str, tcp_no_delay: bool) -> Result<Self, Error> {
let stream = TcpStream::connect(address).await?;
stream.set_nodelay(tcp_no_delay)?;
let (read_half, write_half) = stream.into_split();
Ok(Self {
reader: Mutex::new(Some(read_half)),
writer: Mutex::new(Some(write_half)),
state: watch::Sender::new(SocketState::Live),
connection_url: address.to_string(),
tcp_no_delay,
tap: RawFrameTap::from_env(),
})
}
fn is_closed(&self) -> bool {
*self.state.borrow() == SocketState::Closed
}
fn set_state(&self, to: SocketState) {
self.state.send_if_modified(|state| {
let changed = *state != SocketState::Closed && *state != to;
if changed {
*state = to;
}
changed
});
}
async fn lock_halves(&self) -> (MutexGuard<'_, Option<OwnedReadHalf>>, MutexGuard<'_, Option<OwnedWriteHalf>>) {
let reader = self.reader.lock().await;
let writer = self.writer.lock().await;
(reader, writer)
}
fn open_half<'a, T>(&self, half: &'a mut Option<T>) -> Result<&'a mut T, Error> {
if let Some(err) = self.state.borrow().error() {
return Err(err);
}
half.as_mut().ok_or(Error::Shutdown)
}
async fn interrupted(&self) -> Error {
let mut rx = self.state.subscribe();
let state = rx.wait_for(|state| *state != SocketState::Live).await.map(|state| *state);
state.ok().and_then(SocketState::error).unwrap_or(Error::Shutdown)
}
}
struct FrameProgress<'a> {
socket: &'a AsyncTcpSocket,
len: usize,
remaining: &'a [u8],
}
impl Drop for FrameProgress<'_> {
fn drop(&mut self) {
let written = self.len - self.remaining.len();
if written > 0 && !self.remaining.is_empty() {
warn!("write stopped after {written} of {} bytes; resetting the connection", self.len);
self.socket.set_state(SocketState::Broken);
}
}
}
pub(crate) async fn read_framed_message<R>(reader: &mut R, tap: &RawFrameTap) -> Result<Vec<u8>, Error>
where
R: AsyncRead + Unpin + Send + ?Sized,
{
let mut length_bytes = [0u8; 4];
reader.read_exact(&mut length_bytes).await?;
tap.record_length_prefix(&length_bytes);
let message_length = validate_frame_length(u32::from_be_bytes(length_bytes) as usize)?;
let mut data = vec![0u8; message_length];
reader.read_exact(&mut data).await?;
tap.record_body(&data);
Ok(data)
}
#[async_trait]
impl AsyncIo for AsyncTcpSocket {
async fn read_message(&self) -> Result<Vec<u8>, Error> {
let mut reader = self.reader.lock().await;
let reader = self.open_half(&mut reader)?;
tokio::select! {
biased;
err = self.interrupted() => Err(err),
read = read_framed_message(reader, &self.tap) => read,
}
}
async fn write_all(&self, buf: &[u8]) -> Result<(), Error> {
let mut writer = self.writer.lock().await;
let writer = self.open_half(&mut writer)?;
let mut progress = FrameProgress {
socket: self,
len: buf.len(),
remaining: buf,
};
tokio::select! {
biased;
err = self.interrupted() => Err(err),
result = async {
writer.write_all_buf(&mut progress.remaining).await?;
writer.flush().await?;
Ok(())
} => result,
}
}
async fn close(&self) {
self.set_state(SocketState::Closed);
let (mut reader, mut writer) = self.lock_halves().await;
reader.take();
writer.take();
}
}
#[async_trait]
impl AsyncReconnect for AsyncTcpSocket {
async fn reconnect(&self) -> Result<(), Error> {
if self.is_closed() {
return Err(Error::Shutdown);
}
let stream = TcpStream::connect(&self.connection_url).await?;
stream.set_nodelay(self.tcp_no_delay)?;
let (new_reader, new_writer) = stream.into_split();
let (mut reader, mut writer) = self.lock_halves().await;
if self.is_closed() {
return Err(Error::Shutdown);
}
*reader = Some(new_reader);
*writer = Some(new_writer);
self.set_state(SocketState::Live);
self.tap.start_new_segment();
Ok(())
}
async fn sleep(&self, duration: Duration, shutdown: &ShutdownSignal) {
shutdown.sleep(duration).await
}
}
impl AsyncStream for AsyncTcpSocket {}
#[cfg(test)]
#[path = "io_tests.rs"]
pub(super) mod tests;