From 5d34df7afb6a55f03ecd3e6b031e7713ba607f28 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sun, 1 Nov 2026 03:21:41 +0100 Subject: [PATCH] fix(cli): print lldb by its path, and report a held app as held Tried on the example with the app's sandbox, two things were wrong: - `debugger` printed `flatpak enter lldb -p `, and flatpak enter runs a command without the sandbox's PATH, which /app/bin is on: "Failed to execute child process lldb". It now prints the path it found, /app/bin/lldb, which attached, listed threads and detached. - With the app held by that debugger, the handshake times out, and the client said "cannot reach basic on port ..." and exited 3, no instance, though its lock said it lived. It now says the app is alive but not answering, stopped in a debugger or hung, prints how to attach, check and end it, and exits 5. Environment::haveLldb becomes lldb, the path or empty. ShouldServeOverQuic covers the second with an instance whose lock is held and whose port is silent. Co-Authored-By: Claude Opus 5.5 --- implementations/Crafter.Debug.Cli-Client.cpp | 14 ++++++++++---- implementations/main.cpp | 7 ++++++- interfaces/Crafter.Debug.Cli-Client.cppm | 6 ++++-- tests/ShouldRunClientCommands/main.cpp | 2 +- tests/ShouldServeOverQuic/main.cpp | 15 +++++++++++++-- 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/implementations/Crafter.Debug.Cli-Client.cpp b/implementations/Crafter.Debug.Cli-Client.cpp index f52a180..3cbb467 100644 --- a/implementations/Crafter.Debug.Cli-Client.cpp +++ b/implementations/Crafter.Debug.Cli-Client.cpp @@ -360,10 +360,15 @@ namespace { const bool alive = std::ranges::any_of(env.list(), [&](const Instance& candidate) { return candidate.record.instanceId == instance->record.instanceId; }); if (!alive) { Err() << std::format("crafter-debug: {} has exited\n", instance->record.name); - } else { - Err() << std::format("crafter-debug: cannot reach {} on port {}: {}\n", instance->record.name, instance->record.port, why); + return exitNoInstance; } - return exitNoInstance; + // Its lock says it lives, yet not even the handshake is + // answered: the whole process is stopped, by a debugger most + // likely, or its network threads are stuck. + Err() << std::format("crafter-debug: {} is alive but not answering on port {} ({}): stopped in a debugger, or hung.\n", + instance->record.name, instance->record.port, why); + PrintAttach(true); + return exitTimeout; } // A request that got no reply. The app may be gone, stopped in a @@ -398,7 +403,8 @@ namespace { void PrintAttach(bool toErr) { std::ostream& stream = toErr ? Err() : Out(); const InstanceRecord& record = instance->record; - const std::string debugger = env.haveLldb && env.haveLldb() ? "lldb" : "gdb"; + const std::string lldb = env.lldb ? env.lldb() : std::string(); + const std::string debugger = lldb.empty() ? std::string("gdb") : lldb; if (!record.flatpakInstance.empty()) { stream << std::format(" attach (on the host): flatpak enter {} {} -p {}\n", record.flatpakInstance, debugger, record.pid); stream << std::format(" check whether a debugger holds it: flatpak enter {} grep State /proc/{}/status (t = stopped by a tracer)\n", record.flatpakInstance, record.pid); diff --git a/implementations/main.cpp b/implementations/main.cpp index fb59d17..d0e4f57 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -17,7 +17,12 @@ int main(int argc, char** argv) { { Cli::Environment environment; environment.connect = Cli::ConnectQuic; - environment.haveLldb = [] { return std::filesystem::exists("/app/bin/lldb") || std::filesystem::exists("/usr/bin/lldb"); }; + environment.lldb = [] { + for (const char* path : { "/app/bin/lldb", "/usr/bin/lldb" }) { + if (std::filesystem::exists(path)) return std::string(path); + } + return std::string(); + }; // Run closes its connections before it returns. code = Cli::Run(args, environment); } diff --git a/interfaces/Crafter.Debug.Cli-Client.cppm b/interfaces/Crafter.Debug.Cli-Client.cppm index 5d8dd09..02d56d4 100644 --- a/interfaces/Crafter.Debug.Cli-Client.cppm +++ b/interfaces/Crafter.Debug.Cli-Client.cppm @@ -58,8 +58,10 @@ export namespace Crafter::Debug::Cli { std::ostream* err = &std::cerr; // Where a screenshot without -o goes, relative paths included. std::filesystem::path cwd = std::filesystem::current_path(); - // Whether `lldb` exists here, for the attach command `debugger` prints. - std::function haveLldb; + // Where lldb is here, for the attach command `debugger` prints: its + // absolute path, since `flatpak enter` runs a command without the + // sandbox's PATH, which /app/bin is on. Empty for none: gdb then. + std::function lldb; }; std::int32_t Run(std::span args, Environment& environment); diff --git a/tests/ShouldRunClientCommands/main.cpp b/tests/ShouldRunClientCommands/main.cpp index 28451e3..2817e04 100644 --- a/tests/ShouldRunClientCommands/main.cpp +++ b/tests/ShouldRunClientCommands/main.cpp @@ -85,7 +85,7 @@ namespace { environment.out = &out; environment.err = &err; environment.cwd = fs::temp_directory_path() / "crafter-debug-ShouldRunClientCommands"; - environment.haveLldb = [] { return false; }; + environment.lldb = [] { return std::string(); }; const std::int32_t code = Cli::Run(args, environment); return { code, out.str(), err.str() }; } diff --git a/tests/ShouldServeOverQuic/main.cpp b/tests/ShouldServeOverQuic/main.cpp index e9d6096..9b39170 100644 --- a/tests/ShouldServeOverQuic/main.cpp +++ b/tests/ShouldServeOverQuic/main.cpp @@ -14,7 +14,9 @@ // connections, each get their own reply; // - destroying the Session answers a waiting follower and closes promptly // with a client still connected, and a client then finds the port dead -// within seconds. +// within seconds; +// - an instance alive by its lock but silent at its port, as one a +// debugger holds, is reported as such with how to attach, not as gone. #include #include import std; @@ -58,7 +60,7 @@ namespace { environment.connect = Cli::ConnectQuic; environment.out = &out; environment.err = &err; - environment.haveLldb = [] { return false; }; + environment.lldb = [] { return std::string(); }; const std::int32_t code = Cli::Run(args, environment); return { code, out.str(), err.str() }; } @@ -259,6 +261,15 @@ int main() { Check(!reply && reply.error().kind == Cli::CallError::Kind::Unreachable && deadTook < 5s, std::format("a client finds the port dead in {} ms", std::chrono::duration_cast(deadTook).count())); + // Alive by its lock, with nothing answering at its port: what a client + // sees of an app a debugger holds. + { + const Registration stopped(InstanceRecord{ .name = "held", .port = record.port, .token = "t", .protocol = protocolVersion, .sourceId = std::string(Detail::SourceId()) }); + const Ran held = Run({ "status" }); + Check(held.code == 5 && Has(held.err, "alive but not answering") && Has(held.err, "attach"), + std::format("an app alive but silent is reported as held, with how to attach (exit {}: {})", held.code, held.err)); + } + std::fflush(stdout); std::_Exit(failures == 0 ? 0 : 1); }