summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2020-05-02 02:31:38 +0200
committerGiampaolo Rodola <g.rodola@gmail.com>2020-05-02 02:31:38 +0200
commit17560c35a6d12a8d036fd897d6cead5bef4205ae (patch)
treec6b0d1c81006241f0ca86cdfe3c2fe27edd5e723
parenta6c0efbdbed7671b8512404090a15fa723805f05 (diff)
downloadpsutil-17560c35a6d12a8d036fd897d6cead5bef4205ae.tar.gz
refactor wait_pid() and write unit tests
-rw-r--r--psutil/_psposix.py54
-rwxr-xr-xpsutil/tests/test_process.py43
2 files changed, 69 insertions, 28 deletions
diff --git a/psutil/_psposix.py b/psutil/_psposix.py
index 88213ef8..c9151bdd 100644
--- a/psutil/_psposix.py
+++ b/psutil/_psposix.py
@@ -48,38 +48,38 @@ def pid_exists(pid):
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.
+ """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).
- If pid is not a children of os.getpid() (current process) just
- waits until the process disappears and return None.
+ 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.
+ If PID does not exist at all return None immediately.
Raise TimeoutExpired on timeout expired.
"""
- def check_timeout(delay):
- if timeout is not None:
- if timer() >= stop_at:
- raise TimeoutExpired(timeout, pid=pid, name=proc_name)
- time.sleep(delay)
- return min(delay * 2, 0.04)
-
+ assert pid > 0, pid
timer = getattr(time, 'monotonic', time.time)
+ interval = 0.0001
+ flags = 0
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)
+ flags = os.WNOHANG # return immediately if PID still exists
+
+ def sleep(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)
- delay = 0.0001
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
@@ -87,19 +87,17 @@ def wait_pid(pid, timeout=None, proc_name=None):
# - 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:
+ # WNOHANG was used, PID is still running
if retpid == 0:
- # WNOHANG was used, pid is still running
- delay = check_timeout(delay)
+ interval = sleep(interval)
continue
# process exited due to a signal; return the integer of
# that signal
- if os.WIFSIGNALED(status):
+ 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
diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py
index a9080b26..b353dc71 100755
--- a/psutil/tests/test_process.py
+++ b/psutil/tests/test_process.py
@@ -1512,6 +1512,49 @@ if POSIX and os.getuid() == 0:
# ===================================================================
+# --- psutil.Process.wait() tests
+# ===================================================================
+
+
+class TestProcessWait(PsutilTestCase):
+ """Tests for psutil.Process class."""
+
+ def spawn_psproc(self, *args, **kwargs):
+ sproc = self.spawn_testproc(*args, **kwargs)
+ return psutil.Process(sproc.pid)
+
+ def test_wait_exited(self):
+ # Test waitpid() + WIFEXITED -> WEXITSTATUS.
+ # normal return, same as exit(0)
+ cmd = [PYTHON_EXE, "-c", "pass"]
+ code = self.spawn_psproc(cmd).wait()
+ self.assertEqual(code, 0)
+ # exit(1), implicit in case of error
+ cmd = [PYTHON_EXE, "-c", "1 / 0"]
+ code = self.spawn_psproc(cmd, stderr=subprocess.PIPE).wait()
+ self.assertEqual(code, 1)
+ # via sys.exit()
+ cmd = [PYTHON_EXE, "-c", "import sys; sys.exit(5);"]
+ code = self.spawn_psproc(cmd).wait()
+ self.assertEqual(code, 5)
+ # via os._exit()
+ cmd = [PYTHON_EXE, "-c", "import os; os._exit(5);"]
+ code = self.spawn_psproc(cmd).wait()
+ self.assertEqual(code, 5)
+
+ def test_wait_signaled(self):
+ # Test waitpid() + WIFSIGNALED -> WTERMSIG.
+ p = self.spawn_psproc()
+ p.send_signal(signal.SIGTERM)
+ 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)
+
+# ===================================================================
# --- psutil.Popen tests
# ===================================================================