Skip to content

Commit

Permalink
#1044: define a separate ctx manager which handles zombie processes
Browse files Browse the repository at this point in the history
  • Loading branch information
giampaolo committed Jun 2, 2017
1 parent 9da480c commit 0058541
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 20 deletions.
64 changes: 46 additions & 18 deletions psutil/_psosx.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

"""OSX platform implementation."""

import contextlib
import errno
import functools
import os
Expand Down Expand Up @@ -291,22 +292,40 @@ def wrapper(self, *args, **kwargs):
try:
return fun(self, *args, **kwargs)
except OSError as err:
if self.pid == 0:
if 0 in pids():
raise AccessDenied(self.pid, self._name)
else:
raise
if err.errno == errno.ESRCH:
if not pid_exists(self.pid):
raise NoSuchProcess(self.pid, self._name)
else:
raise ZombieProcess(self.pid, self._name, self._ppid)
raise NoSuchProcess(self.pid, self._name)
if err.errno in (errno.EPERM, errno.EACCES):
raise AccessDenied(self.pid, self._name)
raise
return wrapper


@contextlib.contextmanager
def catch_zombie(proc):
"""There are some poor C APIs which incorrectly raise ESRCH when
the process is still alive or it's a zombie, or even RuntimeError
(those who don't set errno). This is here in order to solve:
https://github.com/giampaolo/psutil/issues/1044
"""
try:
yield
except (OSError, RuntimeError) as err:
if isinstance(err, RuntimeError) or err.errno == errno.ESRCH:
try:
# status() is not supposed to lie and correctly detect
# zombies so if it raises ESRCH it's true.
status = proc.status()
except NoSuchProcess:
raise err
else:
if status == _common.STATUS_ZOMBIE:
raise ZombieProcess(proc.pid, proc._name, proc._ppid)
else:
raise AccessDenied(proc.pid, proc._name)
else:
raise


class Process(object):
"""Wrapper class around underlying C implementation."""

Expand All @@ -327,7 +346,8 @@ def _get_kinfo_proc(self):
@memoize_when_activated
def _get_pidtaskinfo(self):
# Note: should work for PIDs owned by user only.
ret = cext.proc_pidtaskinfo_oneshot(self.pid)
with catch_zombie(self):
ret = cext.proc_pidtaskinfo_oneshot(self.pid)
assert len(ret) == len(pidtaskinfo_map)
return ret

Expand All @@ -346,13 +366,15 @@ def name(self):

@wrap_exceptions
def exe(self):
return cext.proc_exe(self.pid)
with catch_zombie(self):
return cext.proc_exe(self.pid)

@wrap_exceptions
def cmdline(self):
if not pid_exists(self.pid):
raise NoSuchProcess(self.pid, self._name)
return cext.proc_cmdline(self.pid)
with catch_zombie(self):
return cext.proc_cmdline(self.pid)

@wrap_exceptions
def environ(self):
Expand All @@ -367,7 +389,8 @@ def ppid(self):

@wrap_exceptions
def cwd(self):
return cext.proc_cwd(self.pid)
with catch_zombie(self):
return cext.proc_cwd(self.pid)

@wrap_exceptions
def uids(self):
Expand Down Expand Up @@ -440,7 +463,8 @@ def open_files(self):
if self.pid == 0:
return []
files = []
rawlist = cext.proc_open_files(self.pid)
with catch_zombie(self):
rawlist = cext.proc_open_files(self.pid)
for path, fd in rawlist:
if isfile_strict(path):
ntuple = _common.popenfile(path, fd)
Expand All @@ -453,7 +477,8 @@ def connections(self, kind='inet'):
raise ValueError("invalid %r kind argument; choose between %s"
% (kind, ', '.join([repr(x) for x in conn_tmap])))
families, types = conn_tmap[kind]
rawlist = cext.proc_connections(self.pid, families, types)
with catch_zombie(self):
rawlist = cext.proc_connections(self.pid, families, types)
ret = []
for item in rawlist:
fd, fam, type, laddr, raddr, status = item
Expand All @@ -468,7 +493,8 @@ def connections(self, kind='inet'):
def num_fds(self):
if self.pid == 0:
return 0
return cext.proc_num_fds(self.pid)
with catch_zombie(self):
return cext.proc_num_fds(self.pid)

@wrap_exceptions
def wait(self, timeout=None):
Expand All @@ -479,11 +505,13 @@ def wait(self, timeout=None):

@wrap_exceptions
def nice_get(self):
return cext_posix.getpriority(self.pid)
with catch_zombie(self):
return cext_posix.getpriority(self.pid)

@wrap_exceptions
def nice_set(self, value):
return cext_posix.setpriority(self.pid, value)
with catch_zombie(self):
return cext_posix.setpriority(self.pid, value)

@wrap_exceptions
def status(self):
Expand Down
10 changes: 8 additions & 2 deletions psutil/_psutil_osx.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ psutil_pids(PyObject *self, PyObject *args) {
* using sysctl() and filling up a kinfo_proc struct.
* It should be possible to do this for all processes without
* incurring into permission (EPERM) errors.
* This will also succeed for zombie processes returning correct
* information.
*/
static PyObject *
psutil_proc_kinfo_oneshot(PyObject *self, PyObject *args) {
Expand Down Expand Up @@ -173,8 +175,9 @@ psutil_proc_kinfo_oneshot(PyObject *self, PyObject *args) {
* Return multiple process info as a Python tuple in one shot by
* using proc_pidinfo(PROC_PIDTASKINFO) and filling a proc_taskinfo
* struct.
* Contrarily from proc_kinfo above this function will return EACCES
* for PIDs owned by another user.
* Contrarily from proc_kinfo above this function will fail with
* EACCES for PIDs owned by another user and with ESRCH for zombie
* processes.
*/
static PyObject *
psutil_proc_pidtaskinfo_oneshot(PyObject *self, PyObject *args) {
Expand Down Expand Up @@ -226,6 +229,7 @@ psutil_proc_name(PyObject *self, PyObject *args) {

/*
* Return process current working directory.
* Raises NSP in case of zombie process.
*/
static PyObject *
psutil_proc_cwd(PyObject *self, PyObject *args) {
Expand Down Expand Up @@ -1186,6 +1190,7 @@ psutil_proc_open_files(PyObject *self, PyObject *args) {

/*
* Return process TCP and UDP connections as a list of tuples.
* Raises NSP in case of zombie process.
* References:
* - lsof source code: http://goo.gl/SYW79 and http://goo.gl/wNrC0
* - /usr/include/sys/proc_info.h
Expand Down Expand Up @@ -1389,6 +1394,7 @@ psutil_proc_connections(PyObject *self, PyObject *args) {

/*
* Return number of file descriptors opened by process.
* Raises NSP in case of zombie process.
*/
static PyObject *
psutil_proc_num_fds(PyObject *self, PyObject *args) {
Expand Down

0 comments on commit 0058541

Please sign in to comment.