commit:     adee194534f0b3d9762efd1e8e8713c316b93f5a
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Thu May 24 22:36:29 2018 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Fri May 25 02:01:27 2018 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=adee1945

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(-)

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)

Reply via email to