Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fe5649c

Browse files
committed
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.
1 parent ddc8c8d commit fe5649c

5 files changed

Lines changed: 27 additions & 7 deletions

File tree

Lib/asyncio/proactor_events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ def _loop_self_reading(self, f=None):
443443
f.add_done_callback(self._loop_self_reading)
444444

445445
def _write_to_self(self):
446-
self._csock.send(b'x')
446+
self._csock.send(b'\0')
447447

448448
def _start_serving(self, protocol_factory, sock, ssl=None, server=None):
449449
if ssl:

Lib/asyncio/selector_events.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,16 @@ def _make_self_pipe(self):
9494
self._internal_fds += 1
9595
self.add_reader(self._ssock.fileno(), self._read_from_self)
9696

97+
def _process_self_data(self, data):
98+
pass
99+
97100
def _read_from_self(self):
98101
while True:
99102
try:
100103
data = self._ssock.recv(4096)
101104
if not data:
102105
break
106+
self._process_self_data(data)
103107
except InterruptedError:
104108
continue
105109
except BlockingIOError:
@@ -114,7 +118,7 @@ def _write_to_self(self):
114118
csock = self._csock
115119
if csock is not None:
116120
try:
117-
csock.send(b'x')
121+
csock.send(b'\0')
118122
except OSError:
119123
pass
120124

Lib/asyncio/unix_events.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
raise ImportError('Signals are not really supported on Windows')
3232

3333

34+
def _sighandler_noop(signum, frame):
35+
"""Dummy signal handler."""
36+
pass
37+
38+
3439
class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
3540
"""Unix event loop.
3641
@@ -49,6 +54,13 @@ def close(self):
4954
for sig in list(self._signal_handlers):
5055
self.remove_signal_handler(sig)
5156

57+
def _process_self_data(self, data):
58+
for signum in data:
59+
if not signum:
60+
# ignore null bytes written by _write_to_self()
61+
continue
62+
self._handle_signal(signum)
63+
5264
def add_signal_handler(self, sig, callback, *args):
5365
"""Add a handler for a signal. UNIX only.
5466
@@ -69,7 +81,11 @@ def add_signal_handler(self, sig, callback, *args):
6981
self._signal_handlers[sig] = handle
7082

7183
try:
72-
signal.signal(sig, self._handle_signal)
84+
# Register a dummy signal handler to ask Python to write the signal
85+
# number in the wakup file descriptor. _process_self_data() will
86+
# read signal numbers from this file descriptor to handle signals.
87+
signal.signal(sig, _sighandler_noop)
88+
7389
# Set SA_RESTART to limit EINTR occurrences.
7490
signal.siginterrupt(sig, False)
7591
except OSError as exc:
@@ -85,7 +101,7 @@ def add_signal_handler(self, sig, callback, *args):
85101
else:
86102
raise
87103

88-
def _handle_signal(self, sig, arg):
104+
def _handle_signal(self, sig):
89105
"""Internal helper that is the actual signal handler."""
90106
handle = self._signal_handlers.get(sig)
91107
if handle is None:

Lib/test/test_asyncio/test_proactor_events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ def test_loop_self_reading_exception(self):
435435

436436
def test_write_to_self(self):
437437
self.loop._write_to_self()
438-
self.csock.send.assert_called_with(b'x')
438+
self.csock.send.assert_called_with(b'\0')
439439

440440
def test_process_events(self):
441441
self.loop._process_events([])

Lib/test/test_asyncio/test_unix_events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ def test_check_signal(self):
4242
ValueError, self.loop._check_signal, signal.NSIG + 1)
4343

4444
def test_handle_signal_no_handler(self):
45-
self.loop._handle_signal(signal.NSIG + 1, ())
45+
self.loop._handle_signal(signal.NSIG + 1)
4646

4747
def test_handle_signal_cancelled_handler(self):
4848
h = asyncio.Handle(mock.Mock(), (),
4949
loop=mock.Mock())
5050
h.cancel()
5151
self.loop._signal_handlers[signal.NSIG + 1] = h
5252
self.loop.remove_signal_handler = mock.Mock()
53-
self.loop._handle_signal(signal.NSIG + 1, ())
53+
self.loop._handle_signal(signal.NSIG + 1)
5454
self.loop.remove_signal_handler.assert_called_with(signal.NSIG + 1)
5555

5656
@mock.patch('asyncio.unix_events.signal')

0 commit comments

Comments
 (0)