summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2020-05-02 04:12:43 +0200
committerGiampaolo Rodola <g.rodola@gmail.com>2020-05-02 04:12:43 +0200
commit5aa34c0444d2a8675550df33282ab9b2dd979424 (patch)
tree222978befd779acfd5f8146a1f1ea98fa6a5cb39
parent17560c35a6d12a8d036fd897d6cead5bef4205ae (diff)
downloadpsutil-5aa34c0444d2a8675550df33282ab9b2dd979424.tar.gz
handle SIGSTOP and SIGCONT
-rw-r--r--psutil/_psposix.py54
-rw-r--r--psutil/tests/__init__.py11
-rwxr-xr-xpsutil/tests/test_process.py17
3 files changed, 59 insertions, 23 deletions
diff --git a/psutil/_psposix.py b/psutil/_psposix.py
index c9151bdd..69219ceb 100644
--- a/psutil/_psposix.py
+++ b/psutil/_psposix.py
@@ -48,33 +48,40 @@ def pid_exists(pid):
def wait_pid(pid, timeout=None, proc_name=None):
- """Wait for a process PID to terminate and return its exit code.
- This is >= 0 if it exited "normally" (including on error), else
- it's the negated value of the signal which caused the termination
- (e.g. -SIGTERM).
+ """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 it was terminated by a signal it returns the negated value of the
+ signal which caused the termination (e.g. -SIGTERM).
If PID is not a children of os.getpid() (current process) just
wait until the process disappears and return None.
If PID does not exist at all return None immediately.
- Raise TimeoutExpired on timeout expired.
+ If *timeout* != None and process is still alive raise TimeoutExpired.
+ timeout=0 is also possible (either return immediately or raise).
"""
assert pid > 0, pid
timer = getattr(time, 'monotonic', time.time)
interval = 0.0001
- flags = 0
+ flags = os.WUNTRACED | os.WCONTINUED
if timeout is not None:
+ flags |= os.WNOHANG
stop_at = timer() + timeout
- flags = os.WNOHANG # return immediately if PID still exists
def sleep(interval):
+ # Sleep for some time and return a new increased interval.
if timeout is not None:
if timer() >= stop_at:
raise TimeoutExpired(timeout, pid=pid, name=proc_name)
time.sleep(interval)
return min(interval * 2, 0.04)
+ # See: https://linux.die.net/man/2/waitpid
while True:
try:
retpid, status = os.waitpid(pid, flags)
@@ -82,29 +89,42 @@ def wait_pid(pid, timeout=None, proc_name=None):
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 pid_exists(pid):
interval = sleep(interval)
return
else:
- # WNOHANG was used, PID is still running
if retpid == 0:
+ # 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
- elif 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 != SIGSTOP. Return the
+ # negative value of that signal.
+ return -os.WTERMSIG(status)
+ elif os.WIFSTOPPED(status):
+ # Process was stopped via SIGSTOP or is being traced, and
+ # waitpid() was called with WUNTRACED flag. Anyway, it's
+ # still alive. From now on waitpid() will keep returning
+ # (0, 0) until the process state doesn't change.
+ 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)
diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py
index c2766e23..cf994b16 100644
--- a/psutil/tests/__init__.py
+++ b/psutil/tests/__init__.py
@@ -494,9 +494,16 @@ def terminate(proc_or_pid, sig=signal.SIGTERM, wait_timeout=GLOBAL_TIMEOUT):
if POSIX:
return wait_pid(proc.pid, timeout)
+ def sendsig(proc, sig):
+ # If the process received SIGSTOP, SIGCONT is necessary first,
+ # otherwise SIGTERM won't work.
+ if POSIX and sig != signal.SIGKILL:
+ proc.send_signal(signal.SIGCONT)
+ proc.send_signal(sig)
+
def term_subproc(proc, timeout):
try:
- proc.send_signal(sig)
+ sendsig(proc, sig)
except OSError as err:
if WINDOWS and err.winerror == 6: # "invalid handle"
pass
@@ -506,7 +513,7 @@ def terminate(proc_or_pid, sig=signal.SIGTERM, wait_timeout=GLOBAL_TIMEOUT):
def term_psproc(proc, timeout):
try:
- proc.send_signal(sig)
+ sendsig(proc, sig)
except psutil.NoSuchProcess:
pass
return wait(proc, timeout)
diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py
index b353dc71..e0eb2443 100755
--- a/psutil/tests/test_process.py
+++ b/psutil/tests/test_process.py
@@ -1516,6 +1516,7 @@ if POSIX and os.getuid() == 0:
# ===================================================================
+@unittest.skipIf(not POSIX, "POSIX only")
class TestProcessWait(PsutilTestCase):
"""Tests for psutil.Process class."""
@@ -1549,10 +1550,18 @@ class TestProcessWait(PsutilTestCase):
code = p.wait()
self.assertEqual(code, -signal.SIGTERM)
- # def test_wait_stopped(self):
- # p = self.spawn_psproc()
- # p.send_signal(signal.SIGSTOP)
- # code = p.wait(timeout=0.0001)
+ def test_wait_stopped(self):
+ # Test waitpid() + WIFSTOPPED and WIFCONTINUED.
+ p = self.spawn_psproc()
+ p.send_signal(signal.SIGSTOP)
+ self.assertRaises(psutil.TimeoutExpired, p.wait, timeout=0.001)
+ p.send_signal(signal.SIGCONT)
+ self.assertRaises(psutil.TimeoutExpired, p.wait, timeout=0.001)
+ p.send_signal(signal.SIGTERM)
+ code = p.wait()
+ self.assertEqual(code, -signal.SIGTERM)
+ self.assertIsNone(p.wait(0))
+
# ===================================================================
# --- psutil.Popen tests