diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2023-04-17 02:51:06 +0200 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2023-04-17 02:51:06 +0200 |
commit | aa42066eacc5b1a2135d1737d16138de3511868a (patch) | |
tree | 743a494b69d82164382bf716c2070ef9a83ad3f3 | |
parent | 0a81fa089fd4b25b4b7ee71ed39213b83f73c052 (diff) | |
download | psutil-aa42066eacc5b1a2135d1737d16138de3511868a.tar.gz |
Fix #2239 / proc name(): don't fail with ZombieProcess on cmdline()
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 <g.rodola@gmail.com>
-rw-r--r-- | HISTORY.rst | 4 | ||||
-rw-r--r-- | psutil/__init__.py | 7 | ||||
-rwxr-xr-x | psutil/tests/test_process.py | 28 |
3 files changed, 36 insertions, 3 deletions
diff --git a/HISTORY.rst b/HISTORY.rst index 97c2c20e..003dad23 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -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** diff --git a/psutil/__init__.py b/psutil/__init__.py index 6036cbe9..2b0b3c6b 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -626,7 +626,12 @@ class Process(object): # 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: diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index ec15ffda..a67baa72 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -732,7 +732,15 @@ class TestProcess(PsutilTestCase): 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) @@ -745,7 +753,23 @@ class TestProcess(PsutilTestCase): 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") |