diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index fa6d4377..ade3aea4 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -483,18 +483,19 @@ class IOUVM(BaseVM): self._ioucon_thread = None self._terminate_process_iou() - try: - yield from asyncio.wait_for(self._iou_process.wait(), timeout=3) - except asyncio.TimeoutError: - self._iou_process.kill() - if self._iou_process.returncode is None: - log.warn("IOU process {} is still running".format(self._iou_process.pid)) + if self._iou_process.returncode is None: + try: + yield from gns3server.utils.asyncio.wait_for_process_termination(self._iou_process, timeout=3) + except asyncio.TimeoutError: + self._iou_process.kill() + if self._iou_process.returncode is None: + log.warn("IOU process {} is still running".format(self._iou_process.pid)) self._iou_process = None if self._iouyap_process is not None: self._terminate_process_iouyap() try: - yield from asyncio.wait_for(self._iouyap_process.wait(), timeout=3) + yield from gns3server.utils.asyncio.wait_for_process_termination(self._iouyap_process, timeout=3) except asyncio.TimeoutError: self._iouyap_process.kill() if self._iouyap_process.returncode is None: diff --git a/gns3server/utils/asyncio.py b/gns3server/utils/asyncio.py index 16dc8cd2..a977b717 100644 --- a/gns3server/utils/asyncio.py +++ b/gns3server/utils/asyncio.py @@ -53,3 +53,27 @@ def subprocess_check_output(*args, cwd=None, env=None): if output is None: return "" return output.decode("utf-8") + + +@asyncio.coroutine +def wait_for_process_termination(process, timeout=10): + """ + Wait for a process terminate, and raise asyncio.TimeoutError in case of + timeout. + + In theory this can be implemented by just: + yield from asyncio.wait_for(self._iou_process.wait(), timeout=100) + + But it's broken before Python 3.4: + http://bugs.python.org/issue23140 + + :param process: An asyncio subprocess + :param timeout: Timeout in seconds + """ + + while timeout > 0: + if process.returncode is not None: + return + yield from asyncio.sleep(0.1) + timeout -= 0.1 + raise asyncio.TimeoutError() diff --git a/tests/utils/test_asyncio.py b/tests/utils/test_asyncio.py index 38a1795f..96fbde7b 100644 --- a/tests/utils/test_asyncio.py +++ b/tests/utils/test_asyncio.py @@ -18,8 +18,9 @@ import asyncio import pytest +from unittest.mock import MagicMock -from gns3server.utils.asyncio import wait_run_in_executor, subprocess_check_output +from gns3server.utils.asyncio import wait_run_in_executor, subprocess_check_output, wait_for_process_termination def test_wait_run_in_executor(loop): @@ -50,3 +51,17 @@ def test_subprocess_check_output(loop, tmpdir, restore_original_path): exec = subprocess_check_output("cat", path) result = loop.run_until_complete(asyncio.async(exec)) assert result == "TEST" + + +def test_wait_for_process_termination(loop): + + process = MagicMock() + process.returncode = 0 + exec = wait_for_process_termination(process) + loop.run_until_complete(asyncio.async(exec)) + + process = MagicMock() + process.returncode = None + exec = wait_for_process_termination(process, timeout=0.5) + with pytest.raises(asyncio.TimeoutError): + loop.run_until_complete(asyncio.async(exec))