bundled: disconnect child signals before terminate() to prevent restart-during-shutdown

teardownChild called terminate() then waitForFinished(2000), then
disconnected the QProcess signals. But waitForFinished pumps a local
event loop — when frankenphp exited inside that wait, QProcess::finished
fired synchronously, ran onChildFinished as the crash-supervisor's
restart path, and spawned a brand-new frankenphp child during
shutdown. That child's QProcess was then destroyed mid-spawn during
stack unwinding, producing the "QProcess: Destroyed while process is
still running" warning the bundled-supervisor.sh test catches.

Fix: disconnect first, then terminate. Severing signals before the
wait turns terminate() into the synchronous reap it should always
have been; onChildFinished can't run for a process we're explicitly
tearing down.

Local integration test passes clean — both the cache-baked-mount-path
relaunch and the graceful-shutdown assertion go through without the
warning or any orphan frankenphp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 19:23:33 +02:00
parent ed4db00a62
commit 427601403c

View File

@@ -380,6 +380,14 @@ bool BackendConnection::spawnChild(QString* errorOut)
void BackendConnection::teardownChild()
{
if (!m_child) return;
// Disconnect *before* terminating: waitForFinished() pumps a local event
// loop, so QProcess::finished would fire synchronously inside that wait,
// run onChildFinished as the crash-supervisor restart path, and spawn a
// brand-new frankenphp child during shutdown — the new QProcess then
// gets destroyed mid-spawn during stack unwinding and Qt warns
// "Destroyed while process is still running". Severing signals first
// turns terminate() into the synchronous reap it should always have been.
disconnect(m_child, nullptr, this, nullptr);
if (m_child->state() != QProcess::NotRunning) {
m_child->terminate();
if (!m_child->waitForFinished(2000)) {
@@ -387,7 +395,6 @@ void BackendConnection::teardownChild()
m_child->waitForFinished(1000);
}
}
disconnect(m_child, nullptr, this, nullptr);
m_child->deleteLater();
m_child = nullptr;
m_childLogBuffer.clear();