diff --git a/src/attach.rs b/src/attach.rs index 74f1725..6e5fa95 100644 --- a/src/attach.rs +++ b/src/attach.rs @@ -1475,9 +1475,23 @@ fn run_one_session( // overlay communicates state; handshake errors here are bubbled // up so the M5.8d loop can decide whether to retry. attach_debug("waiting for Hello from remote daemon bridge"); + let hello_wait_done = Arc::new(AtomicBool::new(false)); + if attach_debug_enabled() { + let done = Arc::clone(&hello_wait_done); + thread::spawn(move || { + thread::sleep(Duration::from_secs(5)); + if !done.load(Ordering::SeqCst) { + eprintln!( + "pmacs attach debug: still waiting for Hello after 5s; \ + remote bridge connected but no daemon bytes reached local stdout" + ); + } + }); + } let hello: Hello = match read_message(&mut child_stdout) { Ok(h) => h, Err(e) => { + hello_wait_done.store(true, Ordering::SeqCst); attach_debug(format!("failed reading Hello: {e}")); return Err(handshake_error_with_child( child, @@ -1487,6 +1501,7 @@ fn run_one_session( )); } }; + hello_wait_done.store(true, Ordering::SeqCst); attach_debug(format!( "received Hello: protocol_version={}, assigned_frontend_id={}", hello.protocol_version, hello.assigned_frontend_id.0 diff --git a/src/daemon.rs b/src/daemon.rs index fbddb51..3e21ae4 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -70,6 +70,21 @@ use crate::protocol::{ use crate::socket_path::{SocketPathError, ensure_runtime_subdir}; use crate::transport::{read_message, write_message}; +/// Shared debug switch with the SSH attach path. When set before +/// daemon startup, emits stderr breadcrumbs for accept/handshake +/// progress without changing the wire protocol. +const PMACS_ATTACH_DEBUG: &str = "PMACS_ATTACH_DEBUG"; + +fn daemon_debug_enabled() -> bool { + std::env::var_os(PMACS_ATTACH_DEBUG).is_some_and(|v| !v.is_empty() && v != "0") +} + +fn daemon_debug(msg: impl AsRef) { + if daemon_debug_enabled() { + eprintln!("pmacs daemon debug: {}", msg.as_ref()); + } +} + /// T M10.8 — events the dispatcher thread processes. /// /// The dispatcher is the single thread that owns the editor; all @@ -507,9 +522,11 @@ fn accept_loop( dispatcher_tx: mpsc::Sender, shutdown: &Arc, ) -> Result<(), DaemonError> { + daemon_debug("accept loop started"); while !shutdown.load(Ordering::SeqCst) { match listener.accept() { Ok((stream, _)) => { + daemon_debug("accepted frontend socket; spawning per-attach thread"); let daemon_state = Arc::clone(daemon_state); let tx = dispatcher_tx.clone(); thread::spawn(move || per_attach_thread(stream, daemon_state, tx)); @@ -601,7 +618,9 @@ fn per_attach_thread( daemon_state: Arc, dispatcher_tx: mpsc::Sender, ) { + daemon_debug("per-attach thread started"); let frontend_id = FrontendId(daemon_state.next_frontend_id.fetch_add(1, Ordering::SeqCst)); + daemon_debug(format!("assigned {frontend_id:?}; preparing Hello")); // Send Hello immediately on accept. The instance capabilities // advertised here (and used for negotiation below) come from the @@ -618,8 +637,10 @@ fn per_attach_thread( eprintln!("pmacs: send Hello failed: {e}"); return; } + daemon_debug(format!("sent Hello to {frontend_id:?}")); // Read AttachRequest. + daemon_debug(format!("waiting for AttachRequest from {frontend_id:?}")); let req: AttachRequest = match read_message(&mut stream) { Ok(r) => r, Err(e) => { @@ -627,6 +648,7 @@ fn per_attach_thread( return; } }; + daemon_debug(format!("received AttachRequest from {frontend_id:?}")); // T M10.5 version check. if !crate::protocol::is_supported_protocol_version(req.protocol_version) {