diff options
author | Victor Stinner <victor.stinner@gmail.com> | 2014-07-17 20:45:42 (GMT) |
---|---|---|
committer | Victor Stinner <victor.stinner@gmail.com> | 2014-07-17 20:45:42 (GMT) |
commit | 2fa2c3dfc5d9f0e61a9ffd37b5edeb04e8b8faeb (patch) | |
tree | 9e236665c8630a1d4c4f284b6fb18c7d3d246430 | |
parent | dc2539f957610aa028cc6b60a000e78ec34afb15 (diff) | |
parent | fe5649c7b7bf52147480d6b1124a3d8e3597aee3 (diff) | |
download | cpython-2fa2c3dfc5d9f0e61a9ffd37b5edeb04e8b8faeb.zip cpython-2fa2c3dfc5d9f0e61a9ffd37b5edeb04e8b8faeb.tar.gz cpython-2fa2c3dfc5d9f0e61a9ffd37b5edeb04e8b8faeb.tar.bz2 |
(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.
-rw-r--r-- | Lib/asyncio/proactor_events.py | 2 | ||||
-rw-r--r-- | Lib/asyncio/selector_events.py | 6 | ||||
-rw-r--r-- | Lib/asyncio/unix_events.py | 20 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_proactor_events.py | 2 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_unix_events.py | 4 |
5 files changed, 27 insertions, 7 deletions
diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index d09e9faa1..c530687 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -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: diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index d79c080..cd1a75a 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -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 diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py index 4ba4f49..73a85c1 100644 --- a/Lib/asyncio/unix_events.py +++ b/Lib/asyncio/unix_events.py @@ -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: diff --git a/Lib/test/test_asyncio/test_proactor_events.py b/Lib/test/test_asyncio/test_proactor_events.py index 4bb4f0b..0c53698 100644 --- a/Lib/test/test_asyncio/test_proactor_events.py +++ b/Lib/test/test_asyncio/test_proactor_events.py @@ -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([]) diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py index 0ade7f2..d355def 100644 --- a/Lib/test/test_asyncio/test_unix_events.py +++ b/Lib/test/test_asyncio/test_unix_events.py @@ -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') |