summaryrefslogtreecommitdiff
path: root/psutil/_psposix.py
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2020-05-02 15:19:53 -0700
committerGitHub <noreply@github.com>2020-05-03 00:19:53 +0200
commit42368e6a786415d932cb48d0fd006a0c302ab31b (patch)
treebb9ed83939f4742971791fa699282a82844ce66b /psutil/_psposix.py
parent6f2bdc4223789af13156915e930f2fff7d68d46a (diff)
downloadpsutil-42368e6a786415d932cb48d0fd006a0c302ab31b.tar.gz
Process wait() improvements (#1747)
* `Process.wait()` on POSIX now returns an `enum` showing the negative which was used to terminate the process: ```python >>> import psutil >>> p = psutil.Process(9891) >>> p.terminate() >>> p.wait() <Negsignal.SIGTERM: -15> ``` * the return value is cached so that the exit code can be retrieved on then next call, mimicking `subprocess.Popen.wait()` * `Process` object provides more `status` and `exitcode` additional info on `str()` and `repr()`: ``` >>> proc psutil.Process(pid=12739, name='python3', status='terminated', exitcode=<Negsigs.SIGTERM: -15>, started='15:08:20') ``` Extra: * improved `wait()` doc * reverted #1736: `psutil.Popen` uses original `subprocess.Popen.wait` method (safer)
Diffstat (limited to 'psutil/_psposix.py')
-rw-r--r--psutil/_psposix.py126
1 files changed, 87 insertions, 39 deletions
diff --git a/psutil/_psposix.py b/psutil/_psposix.py
index 88213ef8..2e6711a3 100644
--- a/psutil/_psposix.py
+++ b/psutil/_psposix.py
@@ -6,6 +6,7 @@
import glob
import os
+import signal
import sys
import time
@@ -21,6 +22,11 @@ from ._compat import ProcessLookupError
from ._compat import PY3
from ._compat import unicode
+if sys.version_info >= (3, 4):
+ import enum
+else:
+ enum = None
+
__all__ = ['pid_exists', 'wait_pid', 'disk_usage', 'get_terminal_map']
@@ -47,66 +53,108 @@ def pid_exists(pid):
return True
-def wait_pid(pid, timeout=None, proc_name=None):
- """Wait for process with pid 'pid' to terminate and return its
- exit status code as an integer.
+# Python 3.5 signals enum (contributed by me ^^):
+# https://bugs.python.org/issue21076
+if enum is not None and hasattr(signal, "Signals"):
+ Negsignal = enum.IntEnum(
+ 'Negsignal', dict([(x.name, -x.value) for x in signal.Signals]))
+
+ def negsig_to_enum(num):
+ """Convert a negative signal value to an enum."""
+ try:
+ return Negsignal(num)
+ except ValueError:
+ return num
+else:
+ def negsig_to_enum(num):
+ return num
+
+
+def wait_pid(pid, timeout=None, proc_name=None,
+ _waitpid=os.waitpid,
+ _timer=getattr(time, 'monotonic', time.time),
+ _min=min,
+ _sleep=time.sleep,
+ _pid_exists=pid_exists):
+ """Wait for a process PID to terminate.
+
+ If the process terminated normally by calling exit(3) or _exit(2),
+ or by returning from main(), the return value is the positive integer
+ passed to *exit().
- If pid is not a children of os.getpid() (current process) just
- waits until the process disappears and return None.
+ If it was terminated by a signal it returns the negated value of the
+ signal which caused the termination (e.g. -SIGTERM).
- If pid does not exist at all return None immediately.
+ If PID is not a children of os.getpid() (current process) just
+ wait until the process disappears and return None.
- Raise TimeoutExpired on timeout expired.
+ If PID does not exist at all return None immediately.
+
+ If *timeout* != None and process is still alive raise TimeoutExpired.
+ timeout=0 is also possible (either return immediately or raise).
"""
- def check_timeout(delay):
+ if pid <= 0:
+ raise ValueError("can't wait for PID 0") # see "man waitpid"
+ interval = 0.0001
+ flags = 0
+ if timeout is not None:
+ flags |= os.WNOHANG
+ stop_at = _timer() + timeout
+
+ def sleep(interval):
+ # Sleep for some time and return a new increased interval.
if timeout is not None:
- if timer() >= stop_at:
+ if _timer() >= stop_at:
raise TimeoutExpired(timeout, pid=pid, name=proc_name)
- time.sleep(delay)
- return min(delay * 2, 0.04)
-
- timer = getattr(time, 'monotonic', time.time)
- if timeout is not None:
- def waitcall():
- return os.waitpid(pid, os.WNOHANG)
- stop_at = timer() + timeout
- else:
- def waitcall():
- return os.waitpid(pid, 0)
+ _sleep(interval)
+ return _min(interval * 2, 0.04)
- delay = 0.0001
+ # See: https://linux.die.net/man/2/waitpid
while True:
try:
- retpid, status = waitcall()
+ retpid, status = os.waitpid(pid, flags)
except InterruptedError:
- delay = check_timeout(delay)
+ interval = sleep(interval)
except ChildProcessError:
# This has two meanings:
- # - pid is not a child of os.getpid() in which case
+ # - PID is not a child of os.getpid() in which case
# we keep polling until it's gone
- # - pid never existed in the first place
+ # - PID never existed in the first place
# In both cases we'll eventually return None as we
# can't determine its exit status code.
- while True:
- if pid_exists(pid):
- delay = check_timeout(delay)
- else:
- return
+ while _pid_exists(pid):
+ interval = sleep(interval)
+ return
else:
if retpid == 0:
- # WNOHANG was used, pid is still running
- delay = check_timeout(delay)
+ # WNOHANG flag was used and PID is still running.
+ interval = sleep(interval)
continue
- # process exited due to a signal; return the integer of
- # that signal
- if os.WIFSIGNALED(status):
- return -os.WTERMSIG(status)
- # process exited using exit(2) system call; return the
- # integer exit(2) system call has been called with
elif os.WIFEXITED(status):
+ # Process terminated normally by calling exit(3) or _exit(2),
+ # or by returning from main(). The return value is the
+ # positive integer passed to *exit().
return os.WEXITSTATUS(status)
+ elif os.WIFSIGNALED(status):
+ # Process exited due to a signal. Return the negative value
+ # of that signal.
+ return negsig_to_enum(-os.WTERMSIG(status))
+ # elif os.WIFSTOPPED(status):
+ # # Process was stopped via SIGSTOP or is being traced, and
+ # # waitpid() was called with WUNTRACED flag. PID is still
+ # # alive. From now on waitpid() will keep returning (0, 0)
+ # # until the process state doesn't change.
+ # # It may make sense to catch/enable this since stopped PIDs
+ # # ignore SIGTERM.
+ # interval = sleep(interval)
+ # continue
+ # elif os.WIFCONTINUED(status):
+ # # Process was resumed via SIGCONT and waitpid() was called
+ # # with WCONTINUED flag.
+ # interval = sleep(interval)
+ # continue
else:
- # should never happen
+ # Should never happen.
raise ValueError("unknown process exit status %r" % status)