ui: appmarks use effective cgroup via MainPID

This commit is contained in:
beckline
2026-02-15 13:24:49 +03:00
parent e789261e7e
commit 4b99057adb

View File

@@ -940,14 +940,77 @@ RU: Применяет policy-rules и проверяет health. При оши
if p.returncode != 0:
raise RuntimeError(f"systemd-run failed: {p.returncode}\n{out}".strip())
# EN: Some apps (e.g. Chrome wrappers) can return quickly; the transient scope
# EN: may appear/disappear fast. Retry briefly to avoid race.
# RU: Некоторые приложения (например, chrome-wrapper) быстро завершаются; scope
# RU: может появиться/исчезнуть очень быстро. Делаем небольшой retry.
cg = self._control_group_for_unit_retry(unit, timeout_sec=3.0)
# EN: Some apps can be migrated into a different app scope by the desktop/session
# EN: integration. Using unit ControlGroup is then incorrect. Prefer reading the
# EN: effective cgroup from the unit MainPID (/proc/<pid>/cgroup) and fall back
# EN: to systemctl ControlGroup only if needed.
# RU: Некоторые приложения могут мигрировать в другой app scope (интеграция
# RU: с desktop/session). Тогда ControlGroup юнита неверен. Предпочитаем читать
# RU: реальный cgroup по MainPID (/proc/<pid>/cgroup) и только потом fallback
# RU: на systemctl ControlGroup.
cg = self._effective_cgroup_for_unit_retry(unit, timeout_sec=3.0)
return cg, out
def _effective_cgroup_for_unit_retry(self, unit: str, *, timeout_sec: float = 2.0) -> str:
u = (unit or "").strip()
if not u:
raise ValueError("empty unit")
deadline = time.time() + max(0.2, float(timeout_sec or 0))
last_pid_out = ""
while time.time() < deadline:
code, out = self._systemctl_user(["show", "-p", "MainPID", "--value", u])
last_pid_out = out or ""
if code == 0:
try:
pid = int((out or "").strip() or "0")
except Exception:
pid = 0
if pid > 0:
cg = self._cgroup_path_from_pid(pid)
if cg:
return cg
low = (out or "").lower()
if "could not be found" in low or "not found" in low:
break
time.sleep(0.1)
# Fallback: unit ControlGroup (may be wrong for migrated apps).
try:
cg = self._control_group_for_unit_retry(u, timeout_sec=1.0)
if cg:
return cg
except Exception:
pass
raise RuntimeError(
(
"failed to query effective cgroup\n"
+ (last_pid_out.strip() or "(no output)")
+ "\n\n"
+ "EN: Could not resolve unit MainPID->/proc/<pid>/cgroup.\n"
+ "RU: Не удалось получить MainPID и прочитать /proc/<pid>/cgroup."
).strip()
)
def _cgroup_path_from_pid(self, pid: int) -> str:
p = int(pid or 0)
if p <= 0:
return ""
try:
with open(f"/proc/{p}/cgroup", "r", encoding="utf-8", errors="replace") as f:
for raw in f:
line = (raw or "").strip()
if not line:
continue
if line.startswith("0::"):
cg = line[len("0::") :].strip()
return cg
except Exception:
return ""
return ""
def _control_group_for_unit_retry(self, unit: str, *, timeout_sec: float = 2.0) -> str:
u = (unit or "").strip()
if not u:
@@ -1239,7 +1302,20 @@ RU: Применяет policy-rules и проверяет health. При оши
cg = ""
cg_id = 0
try:
cg = self._control_group_for_unit(unit)
# Prefer effective cgroup via MainPID (/proc/<pid>/cgroup) for services.
# Some GUI apps can migrate into a different app scope after launch.
if unit.endswith(".service"):
code, out = self._systemctl_user(["show", "-p", "MainPID", "--value", unit])
if code == 0:
try:
pid = int((out or "").strip() or "0")
except Exception:
pid = 0
if pid > 0:
cg = self._cgroup_path_from_pid(pid)
if not cg:
cg = self._control_group_for_unit(unit)
cg_id = self._cgroup_inode_id(cg)
except Exception:
pass