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); }