summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2023-04-17 02:51:06 +0200
committerGiampaolo Rodola <g.rodola@gmail.com>2023-04-17 02:51:06 +0200
commitaa42066eacc5b1a2135d1737d16138de3511868a (patch)
tree743a494b69d82164382bf716c2070ef9a83ad3f3
parent0a81fa089fd4b25b4b7ee71ed39213b83f73c052 (diff)
downloadpsutil-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.rst4
-rw-r--r--psutil/__init__.py7
-rwxr-xr-xpsutil/tests/test_process.py28
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")