← Commit history

v0.11.3: self-diagnosing serve — [startup] milestones around the bind in the child log; parent fails LOUDLY on stdout (child exit status, /proc state + wchan on timeout, log tail) and reaps a stuck child instead of leaving an alive-but-not-listening process (cold-verify #3)

John Lauer ·83c21fed3f ·1mo ago ·parent 9ef7e39
5 files changed +49−7
Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "adom-project-manager"-version = "0.11.2"+version = "0.11.3" dependencies = [  "altium-codec",  "anyhow",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-project-manager"-version = "0.11.2"+version = "0.11.3" edition = "2021" description = "Adom Project Manager — the central hub for navigating wiki boards, molecules, and libraries into Hydrogen: browse your wiki boards/projects, view symbol + footprint + 3D and the footprint layer stackup, add components to your KiCad/Altium libraries, and deliver self-contained KiCad projects. Built on adom-lbr." 
bin/adom-project-manager
⋯ 1 unchanged line ⋯
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-project-manager",-  "version": "0.11.2",+  "version": "0.11.3",   "type": "app",   "description": "Adom Project Manager — the central hub for navigating wiki boards, molecules and libraries into Hydrogen: browse your wiki boards/projects (Personal / Organization / Public), view symbol + footprint + 3D and the per-EDA footprint layer stackup, explore the board and schematic in INTERACTIVE cross-probing viewers (click a net, hover a part for stock and price), add components to your KiCad/Altium libraries, and deliver self-contained KiCad projects. Built on adom-lbr. (Formerly Adom Project Manager / adom-project-manager.)",   "dependencies": {
src/main.rs+46−4
@@ -207,6 +207,7 @@ fn serve_cmd(port_flag: Option<u16>) -> i32 {     }      // Spawn the actual server detached, logging to ~/.adom.+    let _ = std::fs::create_dir_all(home_container_path(".adom"));     let log = std::fs::OpenOptions::new()         .create(true)         .append(true)@@ -239,10 +240,12 @@ fn serve_cmd(port_flag: Option<u16>) -> i32 {             cmd.stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null());         }     }-    let child = match cmd.spawn() {+    let mut child = match cmd.spawn() {         Ok(c) => c,         Err(e) => {-            eprintln!("ERROR: could not start server: {e}");+            // stdout on purpose: dock consoles show stdout; a stderr-only error+            // reads as a silent hang.+            println!("ERROR: could not start server: {e}");             return 1;         }     };@@ -255,13 +258,47 @@ fn serve_cmd(port_flag: Option<u16>) -> i32 {             finish_serve(port, my_version, true);             return 0;         }+        if let Ok(Some(status)) = child.try_wait() {+            println!("ERROR: server exited during startup ({status}) — its log follows:");+            print_serve_log_tail();+            return 1;+        }         std::thread::sleep(std::time::Duration::from_millis(250));     }-    eprintln!("ERROR: server did not become ready on port {port} within 30s");-    eprintln!("Hint: tail {}", home_container_path(".adom/project-manager-serve.log"));+    // FAIL LOUDLY, on stdout, with everything a remote verifier needs: the+    // child's kernel state (R/S/T/D + wchan tells apart stuck, stopped, and+    // io-blocked) and the log tail — then reap the child so a dead card never+    // leaves an alive-but-useless process behind.+    println!("ERROR: server did not become ready on port {port} within 30s");+    let pid = child.id();+    if let Ok(stat) = std::fs::read_to_string(format!("/proc/{pid}/stat")) {+        let f: Vec<&str> = stat.split_whitespace().collect();+        let wchan = std::fs::read_to_string(format!("/proc/{pid}/wchan")).unwrap_or_default();+        println!("child pid {pid}: state {} wchan {}", f.get(2).unwrap_or(&"?"), wchan.trim());+    } else {+        println!("child pid {pid}: /proc entry gone (exited)");+    }+    println!("its log ({}):", home_container_path(".adom/project-manager-serve.log"));+    print_serve_log_tail();+    unsafe { libc_kill(pid as i32, 15) };+    std::thread::sleep(std::time::Duration::from_millis(500));+    let _ = child.try_wait();+    unsafe { libc_kill(pid as i32, 9) };+    let _ = child.wait();     1 } +fn print_serve_log_tail() {+    let log = std::fs::read_to_string(home_container_path(".adom/project-manager-serve.log")).unwrap_or_default();+    let lines: Vec<&str> = log.lines().collect();+    for l in lines.iter().rev().take(20).rev() {+        println!("  | {l}");+    }+    if lines.is_empty() {+        println!("  | (log is empty — the server produced no output at all)");+    }+}+ extern "C" {     #[link_name = "kill"]     fn libc_kill(pid: i32, sig: i32) -> i32;@@ -454,8 +491,13 @@ pub fn run(port: u16) -> Result<()> {     // user's machine via the bridge, so it must not be LAN-reachable.     // ADOM_LBR_BIND=0.0.0.0 opts back in (e.g. Hydrogen Desktop port-forward).     let bind = std::env::var("ADOM_LBR_BIND").unwrap_or_else(|_| "127.0.0.1".into());+    // Milestone lines around the bind: when a cold box's card spins, the serve+    // log must show exactly how far the server got (ConfRoomROG cold-verify #3+    // found a child alive for minutes with nothing listening and no clue why).+    eprintln!("[startup] pid {} binding {bind}:{port} …", std::process::id());     let server = Server::http(format!("{bind}:{port}"))         .map_err(|e| anyhow::anyhow!("bind {bind}:{port}: {e}"))?;+    eprintln!("[startup] bound {bind}:{port}, serving");     println!("OK: Adom Project Manager on http://localhost:{port}");     println!("Hint: open it in a browser, or `adom-desktop desktop_open_url {{\"url\":\"http://localhost:{port}\"}}`.");     println!("Hint: the component list is fetched from {WIKI}; click a row to open the viewer.");