Skip to content

Commit

Permalink
[OpenBSD, NetBSD] fix zombie process with no ctime (#2289)
Browse files Browse the repository at this point in the history
Zombie processes on Open/NetBSD have a creation time of 0.0. Modify __eq__ so that it takes this into account, fixing is_running() return False for a process which turned into a zombie.
  • Loading branch information
giampaolo authored Aug 2, 2023
1 parent 8bd2405 commit a384858
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 4 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ install-pip: ## Install pip (no-op if already installed).
setup-dev-env: ## Install GIT hooks, pip, test deps (also upgrades them).
${MAKE} install-git-hooks
${MAKE} install-pip
$(PYTHON) -m pip install $(INSTALL_OPTS) --upgrade --trusted-host files.pythonhosted.org pip
$(PYTHON) -m pip install $(INSTALL_OPTS) --upgrade --trusted-host files.pythonhosted.org $(PY_DEPS)
$(PYTHON) -m pip install $(INSTALL_OPTS) --trusted-host files.pythonhosted.org --trusted-host pypi.org --upgrade pip
$(PYTHON) -m pip install $(INSTALL_OPTS) --trusted-host files.pythonhosted.org --trusted-host pypi.org --upgrade $(PY_DEPS)

# ===================================================================
# Tests
Expand Down
16 changes: 15 additions & 1 deletion psutil/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ def __str__(self):
pass
if self._exitcode not in (_SENTINEL, None):
info["exitcode"] = self._exitcode
if self._create_time:
if self._create_time is not None:
info['started'] = _pprint_secs(self._create_time)
return "%s.%s(%s)" % (
self.__class__.__module__,
Expand All @@ -418,6 +418,20 @@ def __eq__(self, other):
# on PID and creation time.
if not isinstance(other, Process):
return NotImplemented
if OPENBSD or NETBSD: # pragma: no cover
# Zombie processes on Open/NetBSD have a creation time of
# 0.0. This covers the case when a process started normally
# (so it has a ctime), then it turned into a zombie. It's
# important to do this because is_running() depends on
# __eq__.
pid1, ctime1 = self._ident
pid2, ctime2 = other._ident
if pid1 == pid2:
if ctime1 and not ctime2:
try:
return self.status() == STATUS_ZOMBIE
except Error:
pass
return self._ident == other._ident

def __ne__(self, other):
Expand Down
9 changes: 8 additions & 1 deletion psutil/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
from psutil import AIX
from psutil import LINUX
from psutil import MACOS
from psutil import NETBSD
from psutil import OPENBSD
from psutil import POSIX
from psutil import SUNOS
from psutil import WINDOWS
Expand Down Expand Up @@ -959,7 +961,12 @@ def assertProcessGone(self, proc):

def assertProcessZombie(self, proc):
# A zombie process should always be instantiable.
psutil.Process(proc.pid)
clone = psutil.Process(proc.pid)
# Cloned zombie on Open/NetBSD has null creation time, see:
# https://github.com/giampaolo/psutil/issues/2287
self.assertEqual(proc, clone)
if not (OPENBSD or NETBSD):
self.assertEqual(hash(proc), hash(clone))
# Its status always be querable.
self.assertEqual(proc.status(), psutil.STATUS_ZOMBIE)
# It should be considered 'running'.
Expand Down
5 changes: 5 additions & 0 deletions psutil/tests/test_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ def test_process_iter(self):
p.wait()
self.assertNotIn(sproc.pid, [x.pid for x in psutil.process_iter()])

# assert there are no duplicates
ls = [x for x in psutil.process_iter()]
self.assertEqual(sorted(ls, key=lambda x: x.pid),
sorted(set(ls), key=lambda x: x.pid))

with mock.patch('psutil.Process',
side_effect=psutil.NoSuchProcess(os.getpid())):
self.assertEqual(list(psutil.process_iter()), [])
Expand Down

0 comments on commit a384858

Please sign in to comment.