use super::WebSocketConnectionProcess;
use crate::server::connection::outbound::OutboundError;
use beamr::timer::TimerRef;
use std::time::{Duration, Instant};
use tungstenite::Message;
#[derive(Debug)]
pub(super) struct KeepaliveSchedule {
pub(super) interval: Duration,
pub(super) next_due: Instant,
pub(super) armed: Option<(TimerRef, Instant)>,
}
impl KeepaliveSchedule {
pub(super) fn new(interval: Duration) -> Option<Self> {
Instant::now().checked_add(interval).map(|next_due| Self {
interval,
next_due,
armed: None,
})
}
}
impl WebSocketConnectionProcess {
pub(super) fn service_keepalive(&mut self, pid: u64) -> Result<(), OutboundError> {
let Some(interval) = self.keepalive_interval else {
return Ok(());
};
if self.keepalive.is_none() {
let Some(schedule) = KeepaliveSchedule::new(interval) else {
return Err(OutboundError::Write(std::io::Error::other(
"websocket.ping_interval_ms overflows the monotonic clock",
)));
};
self.keepalive = Some(schedule);
}
let Some(keepalive) = self.keepalive.as_mut() else {
return Ok(());
};
let now = Instant::now();
if now < keepalive.next_due {
return Ok(());
}
let Some(next_due) = now.checked_add(keepalive.interval) else {
return Err(OutboundError::Write(std::io::Error::other(
"websocket.ping_interval_ms overflows the monotonic clock",
)));
};
keepalive.next_due = next_due;
let Some(socket) = self.socket.as_mut() else {
return Ok(());
};
match socket.write(Message::Ping(tungstenite::Bytes::new())) {
Ok(()) => {
self.outbound.note_transport_write_pending();
Ok(())
}
Err(tungstenite::Error::Io(error))
if error.kind() == std::io::ErrorKind::WouldBlock
|| error.kind() == std::io::ErrorKind::Interrupted =>
{
self.outbound.note_transport_write_pending();
Ok(())
}
Err(tungstenite::Error::WriteBufferFull(_)) => {
tracing::debug!(
connection_pid = pid,
"keepalive ping skipped: transport write buffer is full"
);
Ok(())
}
Err(tungstenite::Error::Io(error)) => Err(OutboundError::Write(error)),
Err(other) => Err(OutboundError::Write(std::io::Error::other(
other.to_string(),
))),
}
}
}