fix(session): answer quit before raising QuitRequested

Quit raised the flag and then replied. A main loop that saw the flag in
between could end the Session, and with it the connections, before the
reply was given, and the agent then saw the app vanish (exit 3) instead
of its quit succeed. The reply now goes first: the transport delivers a
reply given before its connections close. quit --force raises the flag
first as before; it exits from the reply's own thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-11-01 03:06:38 +01:00
commit d75f935bf2
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB

View file

@ -595,13 +595,17 @@ namespace Crafter::Debug {
const auto decoded = DecodeParams<QuitParams>(params, *exchange); const auto decoded = DecodeParams<QuitParams>(params, *exchange);
if (!decoded) return; if (!decoded) return;
Agent(decoded->force ? "quit --force" : "quit", Value()); Agent(decoded->force ? "quit --force" : "quit", Value());
quitRequested.store(true);
if (decoded->force) { if (decoded->force) {
quitRequested.store(true);
// From the thread the reply leaves on, so the main loop // From the thread the reply leaves on, so the main loop
// need not run: --force is for an app whose loop is stuck. // need not run: --force is for an app whose loop is stuck.
return exchange->ReplyThen(MakeReply(Object({ { "quitting", Value(true) }, { "force", Value(true) } })), [] { std::_Exit(0); }); return exchange->ReplyThen(MakeReply(Object({ { "quitting", Value(true) }, { "force", Value(true) } })), [] { std::_Exit(0); });
} }
// The reply before the flag: a loop that sees the flag may end
// the Session at once, and the transport delivers a reply given
// before its connections close, not one given after.
exchange->Reply(MakeReply(Object({ { "quitting", Value(true) } }))); exchange->Reply(MakeReply(Object({ { "quitting", Value(true) } })));
quitRequested.store(true);
} }
// ---- the Pump thread ---- // ---- the Pump thread ----