use super::server_lifecycle::ServerControl;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DisconnectCause {
PeerDisconnect,
StreamReset,
ServerShutdown,
Completed,
}
#[derive(Debug)]
struct DisconnectState {
cause: OnceLock<DisconnectCause>,
notify: tokio::sync::Notify,
}
impl DisconnectState {
fn new() -> Arc<Self> {
Arc::new(Self {
cause: OnceLock::new(),
notify: tokio::sync::Notify::new(),
})
}
fn resolve(&self, cause: DisconnectCause) {
match self.cause.set(cause) {
Ok(()) => self.notify.notify_waiters(),
Err(_) => {}
}
}
async fn cancelled(&self) -> DisconnectCause {
loop {
let notified = self.notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
match self.cause.get() {
Some(cause) => return *cause,
None => notified.await,
}
}
}
}
#[derive(Clone, Debug)]
pub struct DisconnectSignal {
state: Arc<DisconnectState>,
}
impl DisconnectSignal {
pub(super) fn detached() -> Self {
Self {
state: DisconnectState::new(),
}
}
pub async fn cancelled(&self) -> DisconnectCause {
self.state.cancelled().await
}
#[cfg(any(feature = "grpc", feature = "ws"))]
pub(super) fn complete(&self) {
self.state.resolve(DisconnectCause::Completed);
}
}
enum ShutdownPredicate {
Latch(Arc<AtomicBool>),
Control(tokio::sync::watch::Receiver<ServerControl>),
}
impl ShutdownPredicate {
fn is_shutting_down(&self) -> bool {
match self {
Self::Latch(flag) => flag.load(Ordering::Acquire),
Self::Control(control) => !matches!(*control.borrow(), ServerControl::Running),
}
}
}
pub(super) struct ConnectionLiveness {
terminating: AtomicBool,
shutdown: ShutdownPredicate,
}
impl ConnectionLiveness {
pub(super) fn latched(flag: Arc<AtomicBool>) -> Arc<Self> {
Self::with_predicate(ShutdownPredicate::Latch(flag))
}
pub(super) fn controlled(control: tokio::sync::watch::Receiver<ServerControl>) -> Arc<Self> {
Self::with_predicate(ShutdownPredicate::Control(control))
}
fn with_predicate(shutdown: ShutdownPredicate) -> Arc<Self> {
Arc::new(Self {
terminating: AtomicBool::new(false),
shutdown,
})
}
pub(super) fn wrap<S>(self: &Arc<Self>, stream: S) -> LivenessStream<S> {
LivenessStream {
inner: stream,
connection: Arc::clone(self),
}
}
pub(super) fn begin_response(self: Arc<Self>) -> (DisconnectSignal, ResponseGuard) {
let state = DisconnectState::new();
let guard = ResponseGuard {
state: Arc::clone(&state),
connection: self,
};
(DisconnectSignal { state }, guard)
}
}
pub(super) struct ResponseGuard {
state: Arc<DisconnectState>,
connection: Arc<ConnectionLiveness>,
}
impl ResponseGuard {
pub(super) fn complete(&self) {
self.state.resolve(DisconnectCause::Completed);
}
fn cause(&self) -> DisconnectCause {
match (
self.connection.shutdown.is_shutting_down(),
self.connection.terminating.load(Ordering::Acquire),
) {
(true, _) => DisconnectCause::ServerShutdown,
(false, true) => DisconnectCause::PeerDisconnect,
(false, false) => DisconnectCause::StreamReset,
}
}
}
impl Drop for ResponseGuard {
fn drop(&mut self) {
match self.state.cause.get() {
Some(_) => {}
None => self.state.resolve(self.cause()),
}
}
}
pub(super) struct LivenessStream<S> {
inner: S,
connection: Arc<ConnectionLiveness>,
}
impl<S> LivenessStream<S> {
fn mark_terminating(&self) {
self.connection.terminating.store(true, Ordering::Release);
}
fn observe_read(&self, outcome: &std::task::Poll<std::io::Result<()>>, reached_eof: bool) {
match (outcome, reached_eof) {
(std::task::Poll::Ready(Err(_)), _) | (std::task::Poll::Ready(Ok(())), true) => {
self.mark_terminating();
}
_ => {}
}
}
fn observe_write<T>(&self, outcome: &std::task::Poll<std::io::Result<T>>) {
match outcome {
std::task::Poll::Ready(Err(_)) => self.mark_terminating(),
_ => {}
}
}
}
impl<S> tokio::io::AsyncRead for LivenessStream<S>
where
S: tokio::io::AsyncRead + Unpin,
{
fn poll_read(
self: std::pin::Pin<&mut Self>,
context: &mut std::task::Context<'_>,
buffer: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
let this = self.get_mut();
let before = buffer.filled().len();
let had_room = buffer.remaining() > 0;
let outcome = std::pin::Pin::new(&mut this.inner).poll_read(context, buffer);
let reached_eof = had_room && buffer.filled().len() == before;
this.observe_read(&outcome, reached_eof);
outcome
}
}
impl<S> tokio::io::AsyncWrite for LivenessStream<S>
where
S: tokio::io::AsyncWrite + Unpin,
{
fn poll_write(
self: std::pin::Pin<&mut Self>,
context: &mut std::task::Context<'_>,
buffer: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
let this = self.get_mut();
let outcome = std::pin::Pin::new(&mut this.inner).poll_write(context, buffer);
this.observe_write(&outcome);
outcome
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
context: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
let this = self.get_mut();
let outcome = std::pin::Pin::new(&mut this.inner).poll_flush(context);
this.observe_write(&outcome);
outcome
}
fn poll_shutdown(
self: std::pin::Pin<&mut Self>,
context: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
let this = self.get_mut();
let outcome = std::pin::Pin::new(&mut this.inner).poll_shutdown(context);
this.observe_write(&outcome);
outcome
}
fn poll_write_vectored(
self: std::pin::Pin<&mut Self>,
context: &mut std::task::Context<'_>,
buffers: &[std::io::IoSlice<'_>],
) -> std::task::Poll<std::io::Result<usize>> {
let this = self.get_mut();
let outcome = std::pin::Pin::new(&mut this.inner).poll_write_vectored(context, buffers);
this.observe_write(&outcome);
outcome
}
fn is_write_vectored(&self) -> bool {
self.inner.is_write_vectored()
}
}