From adee194534f0b3d9762efd1e8e8713c316b93f5a Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Thu, 24 May 2018 15:36:29 -0700 Subject: AsyncioEventLoop: suppress BlockingIOError warning (bug 655656) Override AbstractEventLoop.run_until_complete() to prevent BlockingIOError from occurring when the event loop is not running, by using signal.set_wakeup_fd(-1) to temporarily disable the wakeup fd. In order to avoid potential interference with API consumers, only modify wakeup fd when portage._interal_caller is True. Bug: https://bugs.gentoo.org/655656 --- .../util/futures/asyncio/test_wakeup_fd_sigchld.py | 76 ++++++++++++++++++++++ pym/portage/util/_eventloop/asyncio_event_loop.py | 37 +++++++++-- 2 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 pym/portage/tests/util/futures/asyncio/test_wakeup_fd_sigchld.py diff --git a/pym/portage/tests/util/futures/asyncio/test_wakeup_fd_sigchld.py b/pym/portage/tests/util/futures/asyncio/test_wakeup_fd_sigchld.py new file mode 100644 index 000000000..abc67c241 --- /dev/null +++ b/pym/portage/tests/util/futures/asyncio/test_wakeup_fd_sigchld.py @@ -0,0 +1,76 @@ +# Copyright 2018 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +import os +import subprocess + +import portage +from portage.const import PORTAGE_PYM_PATH +from portage.tests import TestCase +from portage.util._eventloop.global_event_loop import _asyncio_enabled + + +class WakeupFdSigchldTestCase(TestCase): + def testWakeupFdSigchld(self): + """ + This is expected to trigger a bunch of messages like the following + unless the fix for bug 655656 works as intended: + + Exception ignored when trying to write to the signal wakeup fd: + BlockingIOError: [Errno 11] Resource temporarily unavailable + """ + if not _asyncio_enabled: + self.skipTest('asyncio not enabled') + + script = """ +import asyncio as _real_asyncio +import os +import signal +import sys + +import portage + +# In order to avoid potential interference with API consumers, wakeup +# fd handling is enabled only when portage._interal_caller is True. +portage._internal_caller = True + +from portage.util.futures import asyncio + +loop = asyncio._wrap_loop() + +# Cause the loop to register a child watcher. +proc = loop.run_until_complete(_real_asyncio.create_subprocess_exec('sleep', '0')) +loop.run_until_complete(proc.wait()) + +for i in range(8192): + os.kill(os.getpid(), signal.SIGCHLD) + +# Verify that the child watcher still works correctly +# (this will hang if it doesn't). +proc = loop.run_until_complete(_real_asyncio.create_subprocess_exec('sleep', '0')) +loop.run_until_complete(proc.wait()) +loop.close() +sys.stdout.write('success') +sys.exit(os.EX_OK) +""" + + pythonpath = os.environ.get('PYTHONPATH', '').strip().split(':') + if not pythonpath or pythonpath[0] != PORTAGE_PYM_PATH: + pythonpath = [PORTAGE_PYM_PATH] + pythonpath + pythonpath = ':'.join(filter(None, pythonpath)) + + proc = subprocess.Popen( + [portage._python_interpreter, '-c', script], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + env=dict(os.environ, PYTHONPATH=pythonpath)) + + out, err = proc.communicate() + try: + self.assertEqual(out[:100], b'success') + except Exception: + portage.writemsg(''.join('{}\n'.format(line) + for line in out.decode(errors='replace').splitlines()[:50]), + noiselevel=-1) + raise + + self.assertEqual(proc.wait(), os.EX_OK) diff --git a/pym/portage/util/_eventloop/asyncio_event_loop.py b/pym/portage/util/_eventloop/asyncio_event_loop.py index bf5937de8..65b354544 100644 --- a/pym/portage/util/_eventloop/asyncio_event_loop.py +++ b/pym/portage/util/_eventloop/asyncio_event_loop.py @@ -1,6 +1,7 @@ # Copyright 2018 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 +import os import signal try: @@ -11,6 +12,8 @@ except ImportError: _real_asyncio = None _AbstractEventLoop = object +import portage + class AsyncioEventLoop(_AbstractEventLoop): """ @@ -26,13 +29,15 @@ class AsyncioEventLoop(_AbstractEventLoop): def __init__(self, loop=None): loop = loop or _real_asyncio.get_event_loop() self._loop = loop - self.run_until_complete = loop.run_until_complete + self.run_until_complete = (self._run_until_complete + if portage._internal_caller else loop.run_until_complete) self.call_soon = loop.call_soon self.call_soon_threadsafe = loop.call_soon_threadsafe self.call_later = loop.call_later self.call_at = loop.call_at self.is_running = loop.is_running self.is_closed = loop.is_closed + self.close = loop.close self.create_future = (loop.create_future if hasattr(loop, 'create_future') else self._create_future) self.create_task = loop.create_task @@ -46,6 +51,7 @@ class AsyncioEventLoop(_AbstractEventLoop): self.call_exception_handler = loop.call_exception_handler self.set_debug = loop.set_debug self.get_debug = loop.get_debug + self._wakeup_fd = -1 def _create_future(self): """ @@ -77,9 +83,26 @@ class AsyncioEventLoop(_AbstractEventLoop): """ return self - def close(self): - # Suppress spurious error messages like the following for bug 655656: - # Exception ignored when trying to write to the signal wakeup fd: - # BlockingIOError: [Errno 11] Resource temporarily unavailable - self._loop.remove_signal_handler(signal.SIGCHLD) - self._loop.close() + def _run_until_complete(self, future): + """ + An implementation of AbstractEventLoop.run_until_complete that supresses + spurious error messages like the following reported in bug 655656: + + Exception ignored when trying to write to the signal wakeup fd: + BlockingIOError: [Errno 11] Resource temporarily unavailable + + In order to avoid potential interference with API consumers, this + implementation is only used when portage._internal_caller is True. + """ + if self._wakeup_fd != -1: + signal.set_wakeup_fd(self._wakeup_fd) + self._wakeup_fd = -1 + # Account for any signals that may have arrived between + # set_wakeup_fd calls. + os.kill(os.getpid(), signal.SIGCHLD) + try: + return self._loop.run_until_complete(future) + finally: + self._wakeup_fd = signal.set_wakeup_fd(-1) + if self._wakeup_fd != -1: + signal.set_wakeup_fd(-1) -- cgit v1.2.3-65-gdbad