(Merge 3.4) Python issue #21645, Tulip issue 192: Rewrite signal handling
Since Python 3.3, the C signal handler writes the signal number into the wakeup file descriptor and then schedules the Python call using Py_AddPendingCall(). asyncio uses the wakeup file descriptor to wake up the event loop, and relies on Py_AddPendingCall() to schedule the final callback with call_soon(). If the C signal handler is called in a thread different than the thread of the event loop, the loop is awaken but Py_AddPendingCall() was not called yet. In this case, the event loop has nothing to do and go to sleep again. Py_AddPendingCall() is called while the event loop is sleeping again and so the final callback is not scheduled immediatly. This patch changes how asyncio handles signals. Instead of relying on Py_AddPendingCall() and the wakeup file descriptor, asyncio now only relies on the wakeup file descriptor. asyncio reads signal numbers from the wakeup file descriptor to call its signal handler.
This commit is contained in:
commit
2fa2c3dfc5
|
@ -443,7 +443,7 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
|||
f.add_done_callback(self._loop_self_reading)
|
||||
|
||||
def _write_to_self(self):
|
||||
self._csock.send(b'x')
|
||||
self._csock.send(b'\0')
|
||||
|
||||
def _start_serving(self, protocol_factory, sock, ssl=None, server=None):
|
||||
if ssl:
|
||||
|
|
|
@ -94,12 +94,16 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
|
|||
self._internal_fds += 1
|
||||
self.add_reader(self._ssock.fileno(), self._read_from_self)
|
||||
|
||||
def _process_self_data(self, data):
|
||||
pass
|
||||
|
||||
def _read_from_self(self):
|
||||
while True:
|
||||
try:
|
||||
data = self._ssock.recv(4096)
|
||||
if not data:
|
||||
break
|
||||
self._process_self_data(data)
|
||||
except InterruptedError:
|
||||
continue
|
||||
except BlockingIOError:
|
||||
|
@ -114,7 +118,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
|
|||
csock = self._csock
|
||||
if csock is not None:
|
||||
try:
|
||||
csock.send(b'x')
|
||||
csock.send(b'\0')
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
|
|
@ -31,6 +31,11 @@ if sys.platform == 'win32': # pragma: no cover
|
|||
raise ImportError('Signals are not really supported on Windows')
|
||||
|
||||
|
||||
def _sighandler_noop(signum, frame):
|
||||
"""Dummy signal handler."""
|
||||
pass
|
||||
|
||||
|
||||
class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
||||
"""Unix event loop.
|
||||
|
||||
|
@ -49,6 +54,13 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
|||
for sig in list(self._signal_handlers):
|
||||
self.remove_signal_handler(sig)
|
||||
|
||||
def _process_self_data(self, data):
|
||||
for signum in data:
|
||||
if not signum:
|
||||
# ignore null bytes written by _write_to_self()
|
||||
continue
|
||||
self._handle_signal(signum)
|
||||
|
||||
def add_signal_handler(self, sig, callback, *args):
|
||||
"""Add a handler for a signal. UNIX only.
|
||||
|
||||
|
@ -69,7 +81,11 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
|||
self._signal_handlers[sig] = handle
|
||||
|
||||
try:
|
||||
signal.signal(sig, self._handle_signal)
|
||||
# Register a dummy signal handler to ask Python to write the signal
|
||||
# number in the wakup file descriptor. _process_self_data() will
|
||||
# read signal numbers from this file descriptor to handle signals.
|
||||
signal.signal(sig, _sighandler_noop)
|
||||
|
||||
# Set SA_RESTART to limit EINTR occurrences.
|
||||
signal.siginterrupt(sig, False)
|
||||
except OSError as exc:
|
||||
|
@ -85,7 +101,7 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
|||
else:
|
||||
raise
|
||||
|
||||
def _handle_signal(self, sig, arg):
|
||||
def _handle_signal(self, sig):
|
||||
"""Internal helper that is the actual signal handler."""
|
||||
handle = self._signal_handlers.get(sig)
|
||||
if handle is None:
|
||||
|
|
|
@ -435,7 +435,7 @@ class BaseProactorEventLoopTests(test_utils.TestCase):
|
|||
|
||||
def test_write_to_self(self):
|
||||
self.loop._write_to_self()
|
||||
self.csock.send.assert_called_with(b'x')
|
||||
self.csock.send.assert_called_with(b'\0')
|
||||
|
||||
def test_process_events(self):
|
||||
self.loop._process_events([])
|
||||
|
|
|
@ -42,7 +42,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
|
|||
ValueError, self.loop._check_signal, signal.NSIG + 1)
|
||||
|
||||
def test_handle_signal_no_handler(self):
|
||||
self.loop._handle_signal(signal.NSIG + 1, ())
|
||||
self.loop._handle_signal(signal.NSIG + 1)
|
||||
|
||||
def test_handle_signal_cancelled_handler(self):
|
||||
h = asyncio.Handle(mock.Mock(), (),
|
||||
|
@ -50,7 +50,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
|
|||
h.cancel()
|
||||
self.loop._signal_handlers[signal.NSIG + 1] = h
|
||||
self.loop.remove_signal_handler = mock.Mock()
|
||||
self.loop._handle_signal(signal.NSIG + 1, ())
|
||||
self.loop._handle_signal(signal.NSIG + 1)
|
||||
self.loop.remove_signal_handler.assert_called_with(signal.NSIG + 1)
|
||||
|
||||
@mock.patch('asyncio.unix_events.signal')
|
||||
|
|
Loading…
Reference in New Issue