Skip to content

Commit 886710d

Browse files
committed
Issue 116: provide a 'timeout' parameter for Process.wait() and define a new TimeoutExpired exception.
1 parent 25dcdca commit 886710d

File tree

7 files changed

+96
-25
lines changed

7 files changed

+96
-25
lines changed

psutil/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
__all__ = [
1414
# exceptions
15-
"Error", "NoSuchProcess", "AccessDenied",
15+
"Error", "NoSuchProcess", "AccessDenied", "TimeoutExpired",
1616
# constants
1717
"NUM_CPUS", "TOTAL_PHYMEM", "BOOT_TIME",
1818
"version_info", "__version__",
@@ -39,7 +39,7 @@
3939
except ImportError:
4040
pwd = None
4141

42-
from psutil.error import Error, NoSuchProcess, AccessDenied
42+
from psutil.error import Error, NoSuchProcess, AccessDenied, TimeoutExpired
4343
from psutil._compat import property
4444
from psutil._common import (STATUS_RUNNING, STATUS_IDLE, STATUS_SLEEPING,
4545
STATUS_DISK_SLEEP, STATUS_STOPPED,
@@ -468,11 +468,11 @@ def kill(self):
468468
else:
469469
self._platform_impl.kill_process()
470470

471-
def wait(self):
471+
def wait(self, timeout=None):
472472
"""Wait for process to terminate and, if process is a children
473473
of the current one also return its exit code, else None.
474474
"""
475-
return self._platform_impl.process_wait()
475+
return self._platform_impl.process_wait(timeout)
476476

477477

478478
class Popen(Process):

psutil/_psbsd.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import _psutil_bsd
1010
import _psutil_posix
1111
import _psposix
12-
from psutil.error import AccessDenied, NoSuchProcess
12+
from psutil.error import AccessDenied, NoSuchProcess, TimeoutExpired
1313
from psutil._compat import namedtuple
1414
from psutil._common import *
1515

@@ -173,8 +173,11 @@ def get_connections(self):
173173
return lsof.get_process_connections()
174174

175175
@wrap_exceptions
176-
def process_wait(self):
177-
return _psposix.wait_pid(self.pid)
176+
def process_wait(self, timeout=None):
177+
try:
178+
return _psposix.wait_pid(self.pid, timeout)
179+
except TimeoutExpired:
180+
raise TimeoutExpired(self.pid, self._process_name)
178181

179182
@wrap_exceptions
180183
def get_process_nice(self):

psutil/_pslinux.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import _psutil_posix
1414
import _psutil_linux
1515
from psutil import _psposix
16-
from psutil.error import AccessDenied, NoSuchProcess
16+
from psutil.error import AccessDenied, NoSuchProcess, TimeoutExpired
1717
from psutil._compat import namedtuple
1818
from psutil._common import *
1919

@@ -294,8 +294,11 @@ def get_cpu_times(self):
294294
return ntuple_cputimes(utime, stime)
295295

296296
@wrap_exceptions
297-
def process_wait(self):
298-
return _psposix.wait_pid(self.pid)
297+
def process_wait(self, timeout=None):
298+
try:
299+
return _psposix.wait_pid(self.pid, timeout)
300+
except TimeoutExpired:
301+
raise TimeoutExpired(self.pid, self._process_name)
299302

300303
@wrap_exceptions
301304
def get_process_create_time(self):

psutil/_psosx.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import _psutil_osx
1010
import _psutil_posix
1111
import _psposix
12-
from psutil.error import AccessDenied, NoSuchProcess
12+
from psutil.error import AccessDenied, NoSuchProcess, TimeoutExpired
1313
from psutil._compat import namedtuple
1414
from psutil._common import *
1515

@@ -162,8 +162,11 @@ def get_connections(self):
162162
return lsof.get_process_connections()
163163

164164
@wrap_exceptions
165-
def process_wait(self):
166-
return _psposix.wait_pid(self.pid)
165+
def process_wait(self, timeout=None):
166+
try:
167+
return _psposix.wait_pid(self.pid, timeout)
168+
except TimeoutExpired:
169+
raise TimeoutExpired(self.pid, self._process_name)
167170

168171
@wrap_exceptions
169172
def get_process_nice(self):

psutil/_psposix.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import warnings
1616
import time
1717

18-
from psutil.error import AccessDenied, NoSuchProcess
18+
from psutil.error import AccessDenied, NoSuchProcess, TimeoutExpired
1919
from psutil._compat import namedtuple
2020

2121

@@ -30,29 +30,50 @@ def pid_exists(pid):
3030
else:
3131
return True
3232

33-
def wait_pid(pid):
33+
def wait_pid(pid, timeout=None):
3434
"""Wait for process with pid 'pid' to terminate and return its
3535
exit status code as an integer.
3636
3737
If pid is not a children of os.getpid() (current process) just
3838
waits until the process disappears and return None.
3939
4040
If pid does not exist at all return None immediately.
41+
42+
Raise TimeoutExpired on timeout expired.
4143
"""
42-
while True:
44+
def check_timeout():
45+
if timeout:
46+
if time.time() >= stop_at:
47+
raise TimeoutExpired
48+
time.sleep(0.001)
49+
50+
if timeout:
51+
waitcall = lambda: os.waitpid(pid, os.WNOHANG)
52+
stop_at = time.time() + timeout
53+
else:
54+
waitcall = lambda: os.waitpid(pid, 0)
55+
56+
while 1:
4357
try:
44-
pid, status = os.waitpid(pid, 0)
58+
retpid, status = waitcall()
4559
except OSError, err:
4660
if err.errno == errno.EINTR:
61+
check_timeout()
4762
continue
4863
elif err.errno == errno.ECHILD:
4964
# not a child of os.getpid(): poll until pid has
5065
# disappeared and return None instead
51-
while pid_exists(pid):
52-
time.sleep(0.05)
53-
return
54-
raise
66+
while 1:
67+
if pid_exists(pid):
68+
check_timeout()
69+
else:
70+
return
71+
else:
72+
raise
5573
else:
74+
if retpid == 0:
75+
check_timeout()
76+
continue
5677
# process exited due to a signal; return the integer of
5778
# that signal
5879
if os.WIFSIGNALED(status):

psutil/error.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,32 @@ def __init__(self, pid=None, name=None, msg=None):
3939
self.name = name
4040
self.msg = msg
4141
if msg is None:
42-
if (pid is not None) and (name is not None):
42+
if pid and name:
4343
self.msg = "(pid=%s, name=%s)" % (pid, repr(name))
44-
elif (pid is not None):
44+
elif pid:
4545
self.msg = "(pid=%s)" % self.pid
4646
else:
4747
self.msg = ""
4848

4949
def __str__(self):
5050
return self.msg
5151

52+
53+
class TimeoutExpired(Error):
54+
"""Raised on Process.wait(timeout) if timeout expires and process
55+
is still alive.
56+
"""
57+
58+
def __init__(self, pid=None, name=None):
59+
self.pid = pid
60+
self.name = name
61+
if pid and name:
62+
self.msg = "(pid=%s, name=%s)" % (pid, repr(name))
63+
elif pid:
64+
self.msg = "(pid=%s)" % self.pid
65+
else:
66+
self.msg = ""
67+
68+
def __str__(self):
69+
return self.msg
70+

test/test_psutil.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,14 @@ def test_send_signal(self):
229229
self.assertFalse(psutil.pid_exists(test_pid) and name == PYTHON)
230230

231231
def test_wait(self):
232+
# check exit code signal
232233
sproc = get_test_subprocess()
233234
p = psutil.Process(sproc.pid)
234235
p.kill()
235236
code = p.wait()
236237
if os.name == 'posix':
237238
self.assertEqual(code, signal.SIGKILL)
238239
else:
239-
# windows
240240
self.assertEqual(code, 0)
241241
self.assertFalse(p.is_running())
242242

@@ -247,10 +247,10 @@ def test_wait(self):
247247
if os.name == 'posix':
248248
self.assertEqual(code, signal.SIGTERM)
249249
else:
250-
# windows
251250
self.assertEqual(code, 0)
252251
self.assertFalse(p.is_running())
253252

253+
# check sys.exit() code
254254
code = "import time, sys; time.sleep(0.01); sys.exit(5);"
255255
sproc = get_test_subprocess([PYTHON, "-c", code])
256256
p = psutil.Process(sproc.pid)
@@ -266,6 +266,28 @@ def test_wait(self):
266266
self.assertEqual(p.wait(), 5)
267267
self.assertTrue(p.wait() in (5, None))
268268

269+
# test timeout
270+
sproc = get_test_subprocess()
271+
p = psutil.Process(sproc.pid)
272+
p.name
273+
self.assertRaises(psutil.TimeoutExpired, p.wait, 0.01)
274+
275+
def test_wait_non_children(self):
276+
# test wait() against processes which are not our children
277+
code = "import sys;"
278+
code += "from subprocess import Popen, PIPE;"
279+
code += "cmd = ['%s', '-c', 'import time; time.sleep(10)'];" %PYTHON
280+
code += "sp = Popen(cmd, stdout=PIPE);"
281+
code += "sys.stdout.write(str(sp.pid));"
282+
sproc = get_test_subprocess([PYTHON, "-c", code], stdout=subprocess.PIPE)
283+
284+
grandson_pid = int(sproc.stdout.read())
285+
grandson_proc = psutil.Process(grandson_pid)
286+
self.assertRaises(psutil.TimeoutExpired, grandson_proc.wait, 0.01)
287+
grandson_proc.kill()
288+
ret = grandson_proc.wait()
289+
self.assertEqual(ret, None)
290+
269291
def test_TOTAL_PHYMEM(self):
270292
x = psutil.TOTAL_PHYMEM
271293
self.assertTrue(isinstance(x, (int, long)))

0 commit comments

Comments
 (0)