git.lucas.co / cce-status-interface
status bar
git clone https://git.lucas.co/cce-status-interface.git

commitd35e2b5ea3c2d29b2735cf7cbde07d9e0a5d4804
parent2d052a081b
authorLucas Galante <[email protected]>
date2026-08-27 18:42
fix: back off reconnects to a status topic the compositor does not know

The listener retried every second flat. A compositor that does not
recognize a subscription drops it on sight without a word, so an
unrecognized topic became a permanent once-a-second reconnect from every
module process — nine of them, plus a log line each.

That is not hypothetical: `backdrop` lands in cce-fx and here as separate
commits to separate repos, and cce-fx only restarts at login, so a bar
installed first spends the rest of the session talking to a compositor
that has never heard of the topic.

Doubling to a 30s cap, reset by any line actually received — a topic that
works is unaffected, including one whose connection drops mid-session.

Co-Authored-By: Claude Opus 5 <[email protected]>

 src/listeners.rs | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/listeners.rs b/src/listeners.rs
index b39e463..483c0af 100644
--- a/src/listeners.rs
+++ b/src/listeners.rs
@@ -12,6 +12,14 @@ use crate::CustomEvent;
 pub(crate) async fn spawn_status_listener(sub: String, sender: calloop::channel::Sender<CustomEvent>) {
     use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
     use tokio::net::UnixStream;
+    // Retry delay, doubling while connections keep ending without ever
+    // delivering a line and reset the moment one does. A compositor that
+    // does not know this topic drops the subscription on sight, so the flat
+    // 1s retry turned an unrecognized topic into a permanent once-a-second
+    // reconnect from every module process — which is exactly what a bar
+    // running ahead of its compositor does with `backdrop` (the two halves
+    // deploy separately, and cce-fx only restarts at login).
+    let mut retry_s = 1u64;
     loop {
         let socket_path = match std::env::var("WAYLAND_DISPLAY") {
             Ok(display) => {
@@ -37,6 +45,8 @@ pub(crate) async fn spawn_status_listener(sub: String, sender: calloop::channel:
                 let mut reader = BufReader::new(stream);
                 let mut line = String::new();
                 while reader.read_line(&mut line).await.unwrap_or(0) > 0 {
+                    // Anything at all means the topic is understood.
+                    retry_s = 1;
                     let val = line.trim().to_string();
                     log::debug!("[status-listener] received '{}' update: '{}'", sub, val);
                     if !val.is_empty() {
@@ -60,7 +70,8 @@ pub(crate) async fn spawn_status_listener(sub: String, sender: calloop::channel:
                 }
             }
         }
-        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+        tokio::time::sleep(std::time::Duration::from_secs(retry_s)).await;
+        retry_s = (retry_s * 2).min(30);
     }
 }