Harden systemd scope cleanup

Tighten transient unit lifecycle handling in GameCenter.

- Drop stale unit watcher mappings on PropertiesChanged for removed sessions.
- When stopping a monitored launch due to timeout or attach failure, fall back
  to terminating scope PIDs if StopUnit fails unexpectedly.

This reduces leaked scopes and improves robustness in failure paths.
This commit is contained in:
Marco Allegretti 2026-02-14 16:45:37 +01:00
parent 985f6dac03
commit 7200ad179c

View file

@ -833,6 +833,15 @@ void GameCenterDaemon::handleSystemdUnitPropertiesChanged(const QDBusObjectPath
auto sit = m_sessions.find(sessionId);
if (sit == m_sessions.end()) {
unwatchSystemdUnit({}, unitPath);
for (auto nameIt = m_unitNameToSessionId.begin(); nameIt != m_unitNameToSessionId.end();) {
if (nameIt.value() == sessionId) {
nameIt = m_unitNameToSessionId.erase(nameIt);
} else {
++nameIt;
}
}
return;
}
@ -1570,8 +1579,31 @@ QString GameCenterDaemon::launchMonitored(const QVariantMap &launchSpec, const Q
QVariantMap finalState = sessionToVariantMap(it.value(), stopping ? QStringLiteral("Stopped") : QStringLiteral("Failed"));
if (!it.value().unitName.isEmpty()) {
m_systemd.stopUnit(it.value().unitName);
unwatchSystemdUnit(it.value().unitName, it.value().unitPath);
const QString unitName = it.value().unitName;
const QDBusObjectPath unitPath = it.value().unitPath;
const uint mainPid = it.value().mainPid;
const QDBusReply<QDBusObjectPath> stopReply = m_systemd.stopUnit(unitName);
if (!stopReply.isValid() && stopReply.error().name() != QLatin1String("org.freedesktop.systemd1.NoSuchUnit")) {
QList<uint> pids;
if (!unitPath.path().isEmpty()) {
pids = m_systemd.scopePids(unitPath);
}
if (pids.isEmpty() && it.value().process && it.value().process->processId() > 0) {
pids = {static_cast<uint>(it.value().process->processId())};
}
if (pids.isEmpty() && mainPid > 0) {
pids = {mainPid};
}
if (!pids.isEmpty()) {
terminatePids(pids);
QTimer::singleShot(5000, this, [pids]() {
killPids(pids);
});
}
}
unwatchSystemdUnit(unitName, unitPath);
}
if (it.value().scanner) {
@ -1643,8 +1675,25 @@ void GameCenterDaemon::attachPidsToSession(const QString &sessionId, const QList
};
if (!it.value().unitName.isEmpty()) {
m_systemd.stopUnit(it.value().unitName);
unwatchSystemdUnit(it.value().unitName, it.value().unitPath);
const QString staleUnitName = it.value().unitName;
const QDBusObjectPath staleUnitPath = it.value().unitPath;
const QDBusReply<QDBusObjectPath> stopReply = m_systemd.stopUnit(staleUnitName);
if (!stopReply.isValid() && stopReply.error().name() != QLatin1String("org.freedesktop.systemd1.NoSuchUnit")) {
QList<uint> stalePids;
if (!staleUnitPath.path().isEmpty()) {
stalePids = m_systemd.scopePids(staleUnitPath);
}
if (stalePids.isEmpty()) {
stalePids = pids;
}
terminatePids(stalePids);
QTimer::singleShot(5000, this, [stalePids]() {
killPids(stalePids);
});
}
unwatchSystemdUnit(staleUnitName, staleUnitPath);
}
QVariantMap finalState = sessionToVariantMap(it.value(), QStringLiteral("Failed"));