blob: a11a10205bdb42fbd4c5fee2dfa8f98920ad1fc7 [file] [log] [blame]
# Copyright 2018 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
import os
import pdb
import signal
import sys
try:
import asyncio as _real_asyncio
from asyncio.events import AbstractEventLoop as _AbstractEventLoop
except ImportError:
# Allow ImportModulesTestCase to succeed.
_real_asyncio = None
_AbstractEventLoop = object
import portage
class AsyncioEventLoop(_AbstractEventLoop):
"""
Implementation of asyncio.AbstractEventLoop which wraps asyncio's
event loop and is minimally compatible with _PortageEventLoop.
"""
# Use portage's internal event loop in subprocesses, as a workaround
# for https://bugs.python.org/issue22087, and also
# https://bugs.python.org/issue29703 which affects pypy3-5.10.1.
supports_multiprocessing = False
def __init__(self, loop=None):
loop = loop or _real_asyncio.get_event_loop()
self._loop = loop
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
self.add_reader = loop.add_reader
self.remove_reader = loop.remove_reader
self.add_writer = loop.add_writer
self.remove_writer = loop.remove_writer
self.run_in_executor = loop.run_in_executor
self.time = loop.time
self.default_exception_handler = loop.default_exception_handler
self.call_exception_handler = loop.call_exception_handler
self.set_debug = loop.set_debug
self.get_debug = loop.get_debug
self._wakeup_fd = -1
if portage._internal_caller:
loop.set_exception_handler(self._internal_caller_exception_handler)
@staticmethod
def _internal_caller_exception_handler(loop, context):
"""
An exception handler which drops to a pdb shell if std* streams
refer to a tty, and otherwise kills the process with SIGTERM.
In order to avoid potential interference with API consumers, this
implementation is only used when portage._internal_caller is True.
"""
loop.default_exception_handler(context)
if 'exception' in context:
# If we have a tty then start the debugger, since in might
# aid in diagnosis of the problem. If there's no tty, then
# exit immediately.
if all(s.isatty() for s in (sys.stdout, sys.stderr, sys.stdin)):
# Restore default SIGINT handler, since emerge's Scheduler
# has a SIGINT handler which delays exit until after
# cleanup, and cleanup cannot occur here since the event
# loop is suspended (see bug 672540).
signal.signal(signal.SIGINT, signal.SIG_DFL)
pdb.set_trace()
else:
# Normally emerge will wait for all coroutines to complete
# after SIGTERM has been received. However, an unhandled
# exception will prevent the interrupted coroutine from
# completing, therefore use the default SIGTERM handler
# in order to ensure that emerge exits immediately (though
# uncleanly).
signal.signal(signal.SIGTERM, signal.SIG_DFL)
os.kill(os.getpid(), signal.SIGTERM)
def _create_future(self):
"""
Provide AbstractEventLoop.create_future() for python3.4.
"""
return _real_asyncio.Future(loop=self._loop)
@property
def _asyncio_child_watcher(self):
"""
Portage internals use this as a layer of indirection for
asyncio.get_child_watcher(), in order to support versions of
python where asyncio is not available.
@rtype: asyncio.AbstractChildWatcher
@return: the internal event loop's AbstractChildWatcher interface
"""
return _real_asyncio.get_child_watcher()
@property
def _asyncio_wrapper(self):
"""
Portage internals use this as a layer of indirection in cases
where a wrapper around an asyncio.AbstractEventLoop implementation
is needed for purposes of compatiblity.
@rtype: asyncio.AbstractEventLoop
@return: the internal event loop's AbstractEventLoop interface
"""
return self
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)