Private
Public Access
1
0

daemon: update monitor: implements ping/pong (required server changes)

This commit is contained in:
2025-06-13 16:45:28 -07:00
parent 4f40be205d
commit dece6f1abc
12 changed files with 202 additions and 55 deletions

View File

@@ -3,7 +3,8 @@ use crate::daemon::{
target, Daemon, DaemonResult,
};
use kordophone::api::event_socket::EventSocket;
use futures_util::SinkExt;
use kordophone::api::event_socket::{EventSocket, SinkMessage};
use kordophone::model::event::Event as UpdateEvent;
use kordophone::model::event::EventData as UpdateEventData;
use kordophone::APIInterface;
@@ -22,6 +23,7 @@ pub struct UpdateMonitor {
event_sender: Sender<Event>,
last_sync_times: HashMap<String, Instant>,
update_seq: Option<u64>,
first_connection: bool,
}
impl UpdateMonitor {
@@ -31,6 +33,7 @@ impl UpdateMonitor {
event_sender,
last_sync_times: HashMap::new(),
update_seq: None,
first_connection: false, // optimistic assumption that we're not reconnecting the first time.
}
}
@@ -48,8 +51,6 @@ impl UpdateMonitor {
}
async fn handle_update(&mut self, update: UpdateEvent) {
self.update_seq = Some(update.update_seq);
match update.data {
UpdateEventData::ConversationChanged(conversation) => {
log::info!(target: target::UPDATES, "Conversation changed: {:?}", conversation);
@@ -134,24 +135,42 @@ impl UpdateMonitor {
};
log::debug!(target: target::UPDATES, "Starting event stream");
let mut event_stream = socket.events().await;
let (mut event_stream, mut sink) = socket.events().await;
// We won't know if the websocket is dead until we try to send a message, so time out waiting for
// a message every 30 seconds.
let mut timeout = tokio::time::interval(Duration::from_secs(30));
let mut timeout = tokio::time::interval(Duration::from_secs(10));
timeout.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
// First tick will happen immediately
timeout.tick().await;
// Track when the last ping was sent so we know when to give up
// waiting for the corresponding pong.
let mut ping_sent_at: Option<Instant> = None;
loop {
tokio::select! {
Some(result) = event_stream.next() => {
match result {
Ok(event) => {
self.handle_update(event).await;
Ok(socket_event) => {
match socket_event {
kordophone::api::event_socket::SocketEvent::Update(event) => {
self.handle_update(event).await;
}
// Reset the timeout since we got a message
kordophone::api::event_socket::SocketEvent::Pong => {
log::debug!(target: target::UPDATES, "Received websocket pong");
}
}
if self.first_connection {
self.event_sender.send(Event::UpdateStreamReconnected).await.unwrap();
self.first_connection = false;
}
// Any successfully handled message (update or pong) keeps the connection alive.
ping_sent_at = None;
timeout.reset();
}
Err(e) => {
@@ -160,9 +179,27 @@ impl UpdateMonitor {
}
}
}
_ = timeout.tick() => {
log::warn!("No messages received for 30 seconds, reconnecting...");
break; // Break inner loop to reconnect
// If we previously sent a ping and haven't heard back since the timeout, we'll assume the connection is dead.
if let Some(_) = ping_sent_at {
log::error!(target: target::UPDATES, "Ping timed out. Restarting stream.");
self.first_connection = true;
break;
}
log::debug!("Sending websocket ping on timer");
match sink.send(SinkMessage::Ping).await {
Ok(_) => {
ping_sent_at = Some(Instant::now());
}
Err(e) => {
log::error!(target: target::UPDATES, "Error writing ping to event socket: {}, restarting stream.", e);
self.first_connection = true;
break;
}
}
}
}
}