summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2014-07-17 20:45:42 (GMT)
committerVictor Stinner <victor.stinner@gmail.com>2014-07-17 20:45:42 (GMT)
commit2fa2c3dfc5d9f0e61a9ffd37b5edeb04e8b8faeb (patch)
tree9e236665c8630a1d4c4f284b6fb18c7d3d246430
parentdc2539f957610aa028cc6b60a000e78ec34afb15 (diff)
parentfe5649c7b7bf52147480d6b1124a3d8e3597aee3 (diff)
downloadcpython-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.py2
-rw-r--r--Lib/asyncio/selector_events.py6
-rw-r--r--Lib/asyncio/unix_events.py20
-rw-r--r--Lib/test/test_asyncio/test_proactor_events.py2
-rw-r--r--Lib/test/test_asyncio/test_unix_events.py4
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')