git.lucas.co / cce-graph
node-based graph editor
git clone https://git.lucas.co/cce-graph.git

commit2cff563771b928f35b518c3d554f145272bf14bd
parent7e0c0d5735
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-02 13:17
fix(vault): single-instance on cce_ui::ipc::instance, bounded reads included

The vault mode's claim-or-forward was a copy that never got the bounded
read: its listener did a bare read_line with no timeout, so one client
that connected and said nothing wedged it for every later launch, and a
launch forwarding to a stuck instance waited forever. The shared
cce_ui::ipc::instance (cce-ui 40c66d4) bounds both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/vault.rs | 64 +++++++++++++++---------------------------------------------
 1 file changed, 16 insertions(+), 48 deletions(-)

diff --git a/src/vault.rs b/src/vault.rs
index 1d5f3a7..f0b7cda 100644
--- a/src/vault.rs
+++ b/src/vault.rs
@@ -13,9 +13,9 @@
 //! frames while nothing moves.
 
 use std::io::{BufRead, BufReader, Write};
-use std::os::unix::net::{UnixListener, UnixStream};
+use std::os::unix::net::UnixStream;
 use std::path::PathBuf;
-use std::sync::{Mutex, OnceLock};
+use std::sync::OnceLock;
 use std::time::{Duration, Instant};
 
 use cce_ui::engine::{Application, CursorIcon, EngineState, LogicalPosition, LogicalSize, WindowSettings};
@@ -77,10 +77,11 @@ impl Args {
 static ARGS: OnceLock<Args> = OnceLock::new();
 
 // ---- single instance -------------------------------------------------------
+//
+// The claim, the race it closes and the bounded listener reads are
+// `cce_ui::ipc::instance`'s; the lines below are this mode's protocol.
 
 const SOCKET: &str = "cce-graph-vault";
-static CLAIMED: Mutex<Option<UnixListener>> = Mutex::new(None);
-static OWNED: Mutex<Option<String>> = Mutex::new(None);
 
 /// The line a launch hands a running instance.
 fn launch_line(args: &Args) -> String {
@@ -93,50 +94,19 @@ fn launch_line(args: &Args) -> String {
 
 /// True when a running instance took this launch.
 fn forward_or_claim(args: &Args) -> bool {
-    let path = cce_ui::ipc::socket_path(SOCKET);
-    let forward = |path: &str| -> bool {
-        let Ok(mut s) = UnixStream::connect(path) else { return false };
-        if s.write_all(format!("{}\n", launch_line(args)).as_bytes()).is_err() {
-            return false;
-        }
-        let mut reply = String::new();
-        BufReader::new(s).read_line(&mut reply).is_ok()
-    };
-    if forward(&path) {
-        return true;
-    }
-    let _ = std::fs::remove_file(&path);
-    match UnixListener::bind(&path) {
-        Ok(l) => {
-            *CLAIMED.lock().unwrap() = Some(l);
-            *OWNED.lock().unwrap() = Some(path);
-            false
-        }
-        Err(_) => forward(&path),
-    }
+    cce_ui::ipc::instance::forward_or_claim(SOCKET, &launch_line(args))
 }
 
 fn spawn_listener(sender: calloop::channel::Sender<Message>) {
-    let Some(listener) = CLAIMED.lock().unwrap().take() else { return };
-    std::thread::spawn(move || {
-        for conn in listener.incoming().flatten() {
-            let mut reader = BufReader::new(conn);
-            let mut line = String::new();
-            if reader.read_line(&mut line).is_err() {
-                continue;
-            }
-            let line = line.trim();
-            let msg = match line.split_once(' ') {
-                Some(("local", note)) => Message::Mode { local: true, note: Some(note.to_string()) },
-                _ if line == "local" => Message::Mode { local: true, note: None },
-                _ if line == "global" => Message::Mode { local: false, note: None },
-                _ => continue,
-            };
-            if sender.send(msg).is_err() {
-                return;
-            }
-            let _ = reader.get_mut().write_all(b"ok\n");
-        }
+    cce_ui::ipc::instance::serve(move |line| {
+        let msg = match line.split_once(' ') {
+            Some(("local", note)) => Message::Mode { local: true, note: Some(note.to_string()) },
+            _ if line == "local" => Message::Mode { local: true, note: None },
+            _ if line == "global" => Message::Mode { local: false, note: None },
+            _ => return None,
+        };
+        sender.send(msg).ok()?;
+        Some("ok".into())
     });
 }
 
@@ -893,9 +863,7 @@ pub fn run(args: Args) {
     }
     let _ = ARGS.set(args);
     cce_ui::engine::run::<VaultApp>();
-    if let Some(p) = OWNED.lock().unwrap().take() {
-        let _ = std::fs::remove_file(p);
-    }
+    cce_ui::ipc::instance::cleanup();
 }
 
 #[cfg(test)]