diff --git a/plugins/ndf/scripts/lib/procs.py b/plugins/ndf/scripts/lib/procs.py index 039364470..9c7c303f0 100644 --- a/plugins/ndf/scripts/lib/procs.py +++ b/plugins/ndf/scripts/lib/procs.py @@ -82,14 +82,19 @@ def pid_is_zombie(pid: int) -> bool: def cmdline_contains(pid: int, expected: str) -> bool | None: - """pid の起動の引数(空白でつないだもの)が `expected` を含むか(大文字と小文字を区別しない)。読めなければ None。""" + """pid の起動の引数(空白でつないだもの)が `expected` を含むか(大文字と小文字を区別しない)。読めなければ None。 + + 空の引数も読めないとみなす。終了の途中(ゾンビになる前)の pid は、psutil が例外でなく `[]` を返す。 + False(別のプロセス)と取ると、すぐ終わる CLI を監視が PIDFILE_BAD で止める(CI の不定の失敗)。 + """ p = _process(pid) if p is None: return None try: - return expected.lower() in " ".join(p.cmdline()).lower() + args = p.cmdline() except psutil.Error: return None + return expected.lower() in " ".join(args).lower() if args else None def parent_and_name(pid: int) -> tuple[int, str] | None: diff --git a/plugins/ndf/scripts/tests/test_lib_procs.py b/plugins/ndf/scripts/tests/test_lib_procs.py index 34b19fbd0..42fbce818 100644 --- a/plugins/ndf/scripts/tests/test_lib_procs.py +++ b/plugins/ndf/scripts/tests/test_lib_procs.py @@ -51,6 +51,17 @@ def test_zombie_counts_as_dead_and_missing_pids_are_quiet(): assert procs.wait_gone(p.pid, timeout=0.1) +def test_empty_cmdline_of_an_exiting_process_is_unreadable(monkeypatch): + """終了の途中(ゾンビになる前)の pid は psutil が空の引数を返す。別のプロセス(False)と取らない。""" + + class Exiting: + def cmdline(self): + return [] + + monkeypatch.setattr(procs, "_process", lambda pid: Exiting()) + assert procs.cmdline_contains(12345, "claude") is None + + def test_memory_and_cgroup(tmp_path: Path): avail, total = procs.memory_available() assert 0 < avail <= total