[Python-checkins] cpython (merge 3.4 -> default): (Merge 3.4) Python issue #21645, Tulip issue 192: Rewrite signal handling

victor.stinner python-checkins at python.org
Thu Jul 17 22:49:26 CEST 2014


http://hg.python.org/cpython/rev/2176496951a4
changeset:   91720:2176496951a4
parent:      91718:84f9bf424720
parent:      91719:c4f053f1b47f
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Thu Jul 17 22:45:42 2014 +0200
summary:
  (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.

files:
  Lib/asyncio/proactor_events.py                |   2 +-
  Lib/asyncio/selector_events.py                |   6 ++-
  Lib/asyncio/unix_events.py                    |  20 +++++++++-
  Lib/test/test_asyncio/test_proactor_events.py |   2 +-
  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
--- a/Lib/asyncio/proactor_events.py
+++ b/Lib/asyncio/proactor_events.py
@@ -443,7 +443,7 @@
             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
--- a/Lib/asyncio/selector_events.py
+++ b/Lib/asyncio/selector_events.py
@@ -94,12 +94,16 @@
         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 @@
         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
--- a/Lib/asyncio/unix_events.py
+++ b/Lib/asyncio/unix_events.py
@@ -31,6 +31,11 @@
     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 @@
         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 @@
         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 @@
             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
--- a/Lib/test/test_asyncio/test_proactor_events.py
+++ b/Lib/test/test_asyncio/test_proactor_events.py
@@ -435,7 +435,7 @@
 
     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
--- a/Lib/test/test_asyncio/test_unix_events.py
+++ b/Lib/test/test_asyncio/test_unix_events.py
@@ -42,7 +42,7 @@
             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 @@
         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')

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list