daemon: update monitor: implements ping/pong (required server changes)
This commit is contained in:
@@ -103,6 +103,11 @@
|
||||
value="Emitted when the list of messages is updated."/>
|
||||
</signal>
|
||||
|
||||
<signal name="UpdateStreamReconnected">
|
||||
<annotation name="org.freedesktop.DBus.DocString"
|
||||
value="Emitted when the update stream is reconnected after a timeout or configuration change."/>
|
||||
</signal>
|
||||
|
||||
<!-- Attachments -->
|
||||
|
||||
<method name="GetAttachmentInfo">
|
||||
|
||||
@@ -26,6 +26,9 @@ pub enum Event {
|
||||
/// Asynchronous event for syncing a single conversation with the server.
|
||||
SyncConversation(String, Reply<()>),
|
||||
|
||||
/// Sent when the update stream is reconnected after a timeout or configuration change.
|
||||
UpdateStreamReconnected,
|
||||
|
||||
/// Returns all known conversations from the database.
|
||||
/// Parameters:
|
||||
/// - limit: The maximum number of conversations to return. (-1 for no limit)
|
||||
|
||||
@@ -163,6 +163,17 @@ impl Daemon {
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_conversation_list_sync(&mut self) {
|
||||
let mut db_clone = self.database.clone();
|
||||
let signal_sender = self.signal_sender.clone();
|
||||
self.runtime.spawn(async move {
|
||||
let result = Self::sync_conversation_list(&mut db_clone, &signal_sender).await;
|
||||
if let Err(e) = result {
|
||||
log::error!(target: target::SYNC, "Error handling sync event: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn handle_event(&mut self, event: Event) {
|
||||
match event {
|
||||
Event::GetVersion(reply) => {
|
||||
@@ -170,14 +181,7 @@ impl Daemon {
|
||||
}
|
||||
|
||||
Event::SyncConversationList(reply) => {
|
||||
let mut db_clone = self.database.clone();
|
||||
let signal_sender = self.signal_sender.clone();
|
||||
self.runtime.spawn(async move {
|
||||
let result = Self::sync_conversation_list(&mut db_clone, &signal_sender).await;
|
||||
if let Err(e) = result {
|
||||
log::error!(target: target::SYNC, "Error handling sync event: {}", e);
|
||||
}
|
||||
});
|
||||
self.spawn_conversation_list_sync();
|
||||
|
||||
// This is a background operation, so return right away.
|
||||
reply.send(()).unwrap();
|
||||
@@ -216,6 +220,19 @@ impl Daemon {
|
||||
reply.send(()).unwrap();
|
||||
}
|
||||
|
||||
Event::UpdateStreamReconnected => {
|
||||
log::info!(target: target::UPDATES, "Update stream reconnected");
|
||||
|
||||
// The ui client will respond differently, but we'll almost certainly want to do a sync-list in response to this.
|
||||
self.spawn_conversation_list_sync();
|
||||
|
||||
// Send signal to the client that the update stream has been reconnected.
|
||||
self.signal_sender
|
||||
.send(Signal::UpdateStreamReconnected)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Event::GetAllConversations(limit, offset, reply) => {
|
||||
let conversations = self.get_conversations_limit_offset(limit, offset).await;
|
||||
reply.send(conversations).unwrap();
|
||||
|
||||
@@ -18,4 +18,7 @@ pub enum Signal {
|
||||
/// - upload_guid: The GUID of the upload.
|
||||
/// - attachment_guid: The GUID of the attachment on the server.
|
||||
AttachmentUploaded(String, String),
|
||||
|
||||
/// Emitted when the update stream is reconnected after a timeout or configuration change.
|
||||
UpdateStreamReconnected,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@ pub mod interface {
|
||||
pub use crate::interface::NetBuzzertKordophoneRepositoryMessagesUpdated as MessagesUpdated;
|
||||
pub use crate::interface::NetBuzzertKordophoneRepositoryAttachmentDownloadCompleted as AttachmentDownloadCompleted;
|
||||
pub use crate::interface::NetBuzzertKordophoneRepositoryAttachmentUploadCompleted as AttachmentUploadCompleted;
|
||||
pub use crate::interface::NetBuzzertKordophoneRepositoryUpdateStreamReconnected as UpdateStreamReconnected;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,16 @@ async fn main() {
|
||||
0
|
||||
});
|
||||
}
|
||||
|
||||
Signal::UpdateStreamReconnected => {
|
||||
log::debug!("Sending signal: UpdateStreamReconnected");
|
||||
dbus_registry
|
||||
.send_signal(interface::OBJECT_PATH, DbusSignals::UpdateStreamReconnected {})
|
||||
.unwrap_or_else(|_| {
|
||||
log::error!("Failed to send signal");
|
||||
0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user