diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2020-05-02 04:12:43 +0200 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2020-05-02 04:12:43 +0200 |
commit | 5aa34c0444d2a8675550df33282ab9b2dd979424 (patch) | |
tree | 222978befd779acfd5f8146a1f1ea98fa6a5cb39 | |
parent | 17560c35a6d12a8d036fd897d6cead5bef4205ae (diff) | |
download | psutil-5aa34c0444d2a8675550df33282ab9b2dd979424.tar.gz |
handle SIGSTOP and SIGCONT
-rw-r--r-- | psutil/_psposix.py | 54 | ||||
-rw-r--r-- | psutil/tests/__init__.py | 11 | ||||
-rwxr-xr-x | psutil/tests/test_process.py | 17 |
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 |