aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZac Medico <zmedico@gentoo.org>2018-05-24 15:36:29 -0700
committerZac Medico <zmedico@gentoo.org>2018-05-24 19:01:27 -0700
commitadee194534f0b3d9762efd1e8e8713c316b93f5a (patch)
tree089097d2cdeda80051431b4abc1791b56aa17326
parentSonameDepsProcessor: handle internal libs without DT_SONAME (bug 646190) (diff)
downloadportage-adee1945.tar.gz
portage-adee1945.tar.bz2
portage-adee1945.zip
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
-rw-r--r--pym/portage/tests/util/futures/asyncio/test_wakeup_fd_sigchld.py76
-rw-r--r--pym/portage/util/_eventloop/asyncio_event_loop.py37
2 files changed, 106 insertions, 7 deletions
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)