Skip to content

Commit

Permalink
Fix #2239 / proc name(): don't fail with ZombieProcess on cmdline()
Browse files Browse the repository at this point in the history
A recent failure observed on OpenBSD led me to an interesting consideration.

```
======================================================================
ERROR: psutil.tests.test_process.TestProcess.test_long_name
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/vagrant/psutil/psutil/_psbsd.py", line 566, in wrapper
    return fun(self, *args, **kwargs)
  File "/vagrant/psutil/psutil/_psbsd.py", line 684, in cmdline
    return cext.proc_cmdline(self.pid)
ProcessLookupError: [Errno 3] No such process

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/vagrant/psutil/psutil/tests/test_process.py", line 751, in test_long_name
    self.assertEqual(p.name(), os.path.basename(testfn))
  File "/vagrant/psutil/psutil/__init__.py", line 628, in name
    cmdline = self.cmdline()
  File "/vagrant/psutil/psutil/__init__.py", line 681, in cmdline
    return self._proc.cmdline()
  File "/vagrant/psutil/psutil/_psbsd.py", line 569, in wrapper
    raise ZombieProcess(self.pid, self._name, self._ppid)
psutil.ZombieProcess: PID still exists but it's a zombie (pid=48379)
----------------------------------------------------------------------
```

The exception above occurs sporadically. It originates from `sysctl
(KERN_PROC_ARGV)`:
https://github.com/giampaolo/psutil/blob/0a81fa089fd4b25b4b7ee71ed39213b83f73c052/psutil/arch/openbsd/proc.c#L149
The error per se does not represent a bug in the OpenBSD `cmdline
()` implemention because the process **really** is a zombie at that point
(I'm not sure why it's a zombie - this seems only to occur only on OpenBSD for
this specific test case - but that's not the point).

The interesting thing is that the test calls process `name()` (which succeeds,
despite it's a zombie process), but since the process name is too long it gets
truncated to 15 chars (this is a UNIX thing) so psutil tries to guess the
remaining characters from the process `cmdline()`, which fails:
https://github.com/giampaolo/psutil/blob/0a81fa089fd4b25b4b7ee71ed39213b83f73c052/psutil/__init__.py#L623-L630

The problem to fix here is that, if `name()` succeeds but `cmdline()` fails, we
should not raise `ZombieProcess`: we should simply return the
(truncated) process `name()` instead, because that is better than nothing.
Not on OpenBSD but on all platforms.

Signed-off-by: Giampaolo Rodola <[email protected]>
  • Loading branch information
giampaolo committed Apr 17, 2023
1 parent 0a81fa0 commit aa42066
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 3 deletions.
4 changes: 4 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
(e.g. directory no longer exists), in which case we returned either ``None``
or an empty string. This was consolidated and we now return ``""`` on all
platforms.
- 2239_, [UNIX]: if process is a zombie, and we can only determine part of the
its truncated `Process.name()`_ (15 chars), don't fail with `ZombieProcess`_
when we try to guess the full name from the `Process.cmdline()`_. Just
return the truncated name.

**Bug fixes**

Expand Down
7 changes: 6 additions & 1 deletion psutil/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,12 @@ def name(self):
# Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon".
try:
cmdline = self.cmdline()
except AccessDenied:
except (AccessDenied, ZombieProcess):
# Just pass and return the truncated name: it's better
# than nothing. Note: there are actual cases where a
# zombie process can return a name() but not a
# cmdline(), see:
# https://github.com/giampaolo/psutil/issues/2239
pass
else:
if cmdline:
Expand Down
28 changes: 26 additions & 2 deletions psutil/tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,15 @@ def test_long_cmdline(self):
create_exe(testfn)
cmdline = [testfn] + (["0123456789"] * 20)
p = self.spawn_psproc(cmdline)
self.assertEqual(p.cmdline(), cmdline)
if OPENBSD:
# XXX: for some reason the test process may turn into a
# zombie (don't know why).
try:
self.assertEqual(p.cmdline(), cmdline)
except psutil.ZombieProcess:
raise self.skipTest("OPENBSD: process turned into zombie")
else:
self.assertEqual(p.cmdline(), cmdline)

def test_name(self):
p = self.spawn_psproc(PYTHON_EXE)
Expand All @@ -745,7 +753,23 @@ def test_long_name(self):
testfn = self.get_testfn(suffix="0123456789" * 2)
create_exe(testfn)
p = self.spawn_psproc(testfn)
self.assertEqual(p.name(), os.path.basename(testfn))
if OPENBSD:
# XXX: for some reason the test process may turn into a
# zombie (don't know why). Because the name() is long, all
# UNIX kernels truncate it to 15 chars, so internally psutil
# tries to guess the full name() from the cmdline(). But the
# cmdline() of a zombie on OpenBSD fails (internally), so we
# just compare the first 15 chars. Full explanation:
# https://github.com/giampaolo/psutil/issues/2239
try:
self.assertEqual(p.name(), os.path.basename(testfn))
except AssertionError:
if p.status() == psutil.STATUS_ZOMBIE:
assert os.path.basename(testfn).startswith(p.name())
else:
raise
else:
self.assertEqual(p.name(), os.path.basename(testfn))

# XXX
@unittest.skipIf(SUNOS, "broken on SUNOS")
Expand Down

0 comments on commit aa42066

Please sign in to comment.