未验证 提交 9a8dfafd 编写于 作者: C Caio Carrara

Merge remote-tracking branch 'ldoktor/process-wait3'

Signed-off-by: NCaio Carrara <ccarrara@redhat.com>
...@@ -808,14 +808,64 @@ class SubProcess(object): ...@@ -808,14 +808,64 @@ class SubProcess(object):
self._fill_results(rc) self._fill_results(rc)
return rc return rc
def wait(self): def wait(self, timeout=None, sig=signal.SIGTERM):
""" """
Call the subprocess poll() method, fill results if rc is not None. Call the subprocess poll() method, fill results if rc is not None.
:param timeout: Time (seconds) we'll wait until the process is
finished. If it's not, we'll try to terminate it
and it's children using ``sig`` and get a
status. When the process refuses to die
within 1s we use SIGKILL and report the status
(be it exit_code or zombie)
:param sig: Signal to send to the process in case it did not end after
the specified timeout.
""" """
def nuke_myself():
self.result.interrupted = ("timeout after %ss"
% (time.time() - self.start_time))
try:
kill_process_tree(self.get_pid(), sig, timeout=1)
except Exception:
try:
kill_process_tree(self.get_pid(), signal.SIGKILL,
timeout=1)
log.warning("Process '%s' refused to die in 1s after "
"sending %s to, destroyed it successfully "
"using SIGKILL.", self.cmd, sig)
except Exception:
log.error("Process '%s' refused to die in 1s after "
"sending %s, followed by SIGKILL, probably "
"dealing with a zombie process.", self.cmd,
sig)
self._init_subprocess() self._init_subprocess()
rc = self._popen.wait() rc = None
if rc is not None:
self._fill_results(rc) if timeout is None:
rc = self._popen.wait()
elif timeout > 0.0:
timer = threading.Timer(timeout, nuke_myself)
try:
timer.start()
rc = self._popen.wait()
finally:
timer.cancel()
if rc is None:
stop_time = time.time() + 1
while time.time() < stop_time:
rc = self._popen.poll()
if rc is not None:
break
else:
nuke_myself()
rc = self._popen.poll()
if rc is None:
# If all this work fails, we're dealing with a zombie process.
raise AssertionError('Zombie Process %s' % self._popen.pid)
self._fill_results(rc)
return rc return rc
def stop(self): def stop(self):
...@@ -860,7 +910,10 @@ class SubProcess(object): ...@@ -860,7 +910,10 @@ class SubProcess(object):
:param timeout: Time (seconds) we'll wait until the process is :param timeout: Time (seconds) we'll wait until the process is
finished. If it's not, we'll try to terminate it finished. If it's not, we'll try to terminate it
and get a status. and it's children using ``sig`` and get a
status. When the process refuses to die
within 1s we use SIGKILL and report the status
(be it exit_code or zombie)
:type timeout: float :type timeout: float
:param sig: Signal to send to the process in case it did not end after :param sig: Signal to send to the process in case it did not end after
the specified timeout. the specified timeout.
...@@ -868,36 +921,8 @@ class SubProcess(object): ...@@ -868,36 +921,8 @@ class SubProcess(object):
:returns: The command result object. :returns: The command result object.
:rtype: A :class:`CmdResult` instance. :rtype: A :class:`CmdResult` instance.
""" """
def timeout_handler():
self.send_signal(sig)
self.result.interrupted = "timeout after %ss" % timeout
self._init_subprocess() self._init_subprocess()
self.wait(timeout, sig)
if timeout is None:
self.wait()
elif timeout > 0.0:
timer = threading.Timer(timeout, timeout_handler)
try:
timer.start()
self.wait()
finally:
timer.cancel()
if self.result.exit_status is None:
stop_time = time.time() + 1
while time.time() < stop_time:
self.poll()
if self.result.exit_status is not None:
break
else:
self.kill()
self.poll()
# If all this work fails, we're dealing with a zombie process.
e_msg = 'Zombie Process %s' % self._popen.pid
assert self.result.exit_status is not None, e_msg
return self.result return self.result
......
...@@ -4,6 +4,7 @@ import os ...@@ -4,6 +4,7 @@ import os
import shlex import shlex
import unittest import unittest
import sys import sys
import time
try: try:
from unittest import mock from unittest import mock
...@@ -13,6 +14,7 @@ except ImportError: ...@@ -13,6 +14,7 @@ except ImportError:
from .. import recent_mock from .. import recent_mock
from avocado.utils import astring from avocado.utils import astring
from avocado.utils import script
from avocado.utils import gdb from avocado.utils import gdb
from avocado.utils import process from avocado.utils import process
from avocado.utils import path from avocado.utils import path
...@@ -35,6 +37,20 @@ def probe_binary(binary): ...@@ -35,6 +37,20 @@ def probe_binary(binary):
ECHO_CMD = probe_binary('echo') ECHO_CMD = probe_binary('echo')
FICTIONAL_CMD = '/usr/bin/fictional_cmd' FICTIONAL_CMD = '/usr/bin/fictional_cmd'
REFUSE_TO_DIE = """import signal
import time
for sig in range(64):
try:
signal.signal(sig, signal.SIG_IGN)
except:
pass
end = time.time() + 120
while time.time() < end:
time.sleep(1)"""
class TestSubProcess(unittest.TestCase): class TestSubProcess(unittest.TestCase):
...@@ -321,6 +337,40 @@ class TestProcessRun(unittest.TestCase): ...@@ -321,6 +337,40 @@ class TestProcessRun(unittest.TestCase):
self.assertEqual(result.stdout, encoded_text) self.assertEqual(result.stdout, encoded_text)
self.assertEqual(result.stdout_text, text) self.assertEqual(result.stdout_text, text)
@unittest.skipIf(int(os.environ.get("AVOCADO_CHECK_LEVEL", 1)) < 2,
"Skipping test that take a long time to run, are "
"resource intensive or time sensitve")
def test_run_with_timeout_ugly_cmd(self):
with script.TemporaryScript("refuse_to_die", REFUSE_TO_DIE) as exe:
cmd = "%s '%s'" % (sys.executable, exe.path)
# Wait 1s to set the traps
res = process.run(cmd, timeout=1, ignore_status=True)
self.assertLess(res.duration, 100, "Took longer than expected, "
"process probably not interrupted by Avocado.\n%s"
% res)
self.assertNotEqual(res.exit_status, 0, "Command finished without "
"reporting failure but should be killed.\n%s"
% res)
@unittest.skipIf(int(os.environ.get("AVOCADO_CHECK_LEVEL", 0)) < 2,
"Skipping test that take a long time to run, are "
"resource intensive or time sensitve")
def test_run_with_negative_timeout_ugly_cmd(self):
with script.TemporaryScript("refuse_to_die", REFUSE_TO_DIE) as exe:
cmd = "%s '%s'" % (sys.executable, exe.path)
# Wait 1s to set the traps
proc = process.SubProcess(cmd)
proc.start()
time.sleep(1)
proc.wait(-1)
res = proc.result
self.assertLess(res.duration, 100, "Took longer than expected, "
"process probably not interrupted by Avocado.\n%s"
% res)
self.assertNotEqual(res.exit_status, 0, "Command finished without "
"reporting failure but should be killed.\n%s"
% res)
class MiscProcessTests(unittest.TestCase): class MiscProcessTests(unittest.TestCase):
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册