Skip to content

Commit 6e03e51

Browse files
committed
Make signals processing more reliable
In addition to signal.set_wakeup_fd() we now record signals in signal handlers. If the signals self-pipe is full and Python signals handler fails to write to it, we'll get the signal anyways.
1 parent ce2bd4f commit 6e03e51

File tree

2 files changed

+48
-19
lines changed

2 files changed

+48
-19
lines changed

uvloop/loop.pxd

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ cdef class Loop:
6262
dict _fd_to_reader_fileobj
6363
dict _fd_to_writer_fileobj
6464

65+
set _signals
6566
dict _signal_handlers
6667
object _ssock
6768
object _csock
@@ -199,7 +200,8 @@ cdef class Loop:
199200

200201
cdef _handle_signal(self, sig)
201202
cdef _read_from_self(self)
202-
cdef _process_self_data(self, data)
203+
cdef inline _ceval_process_signals(self)
204+
cdef _invoke_signals(self, bytes data)
203205

204206
cdef _set_coroutine_debug(self, bint enabled)
205207

uvloop/loop.pyx

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \
2727
Py_buffer, PyBytes_AsString, PyBytes_CheckExact, \
2828
Py_SIZE, PyBytes_AS_STRING
2929

30-
from cpython cimport PyErr_CheckSignals
31-
3230
from . import _noop
3331

3432

@@ -149,6 +147,7 @@ cdef class Loop:
149147
self, "loop._exec_queued_writes",
150148
<method_t>self._exec_queued_writes, self))
151149

150+
self._signals = set()
152151
self._ssock = self._csock = None
153152
self._signal_handlers = {}
154153
self._listening_signals = False
@@ -234,7 +233,7 @@ cdef class Loop:
234233
self._ssock.setblocking(False)
235234
self._csock.setblocking(False)
236235
try:
237-
signal_set_wakeup_fd(self._csock.fileno())
236+
_set_signal_wakeup_fd(self._csock.fileno())
238237
except (OSError, ValueError):
239238
# Not the main thread
240239
self._ssock.close()
@@ -290,23 +289,54 @@ cdef class Loop:
290289

291290
self._listening_signals = False
292291

292+
def __sighandler(self, signum, frame):
293+
self._signals.add(signum)
294+
295+
cdef inline _ceval_process_signals(self):
296+
# Invoke CPython eval loop to let process signals.
297+
PyErr_CheckSignals()
298+
# Calling a pure-Python function will invoke
299+
# _PyEval_EvalFrameDefault which will process
300+
# pending signal callbacks.
301+
_noop.noop() # Might raise ^C
302+
293303
cdef _read_from_self(self):
304+
cdef bytes sigdata
305+
sigdata = b''
294306
while True:
295307
try:
296-
data = self._ssock.recv(4096)
308+
data = self._ssock.recv(65536)
297309
if not data:
298310
break
299-
self._process_self_data(data)
311+
sigdata += data
300312
except InterruptedError:
301313
continue
302314
except BlockingIOError:
303315
break
316+
if sigdata:
317+
self._invoke_signals(sigdata)
318+
319+
cdef _invoke_signals(self, bytes data):
320+
cdef set sigs
321+
322+
self._ceval_process_signals()
304323

305-
cdef _process_self_data(self, data):
324+
sigs = self._signals.copy()
325+
self._signals.clear()
306326
for signum in data:
307327
if not signum:
308-
# ignore null bytes written by _write_to_self()
328+
# ignore null bytes written by set_wakeup_fd()
309329
continue
330+
sigs.discard(signum)
331+
self._handle_signal(signum)
332+
333+
for signum in sigs:
334+
# Since not all signals are registered by add_signal_handler()
335+
# (for instance, we use the default SIGINT handler) not all
336+
# signals will trigger loop.__sighandler() callback. Therefore
337+
# we combine two datasources: one is self-pipe, one is data
338+
# from __sighandler; this ensures that signals shouldn't be
339+
# lost even if set_wakeup_fd() couldn't write to the self-pipe.
310340
self._handle_signal(signum)
311341

312342
cdef _handle_signal(self, sig):
@@ -318,11 +348,7 @@ cdef class Loop:
318348
handle = None
319349

320350
if handle is None:
321-
# Some signal that we aren't listening through
322-
# add_signal_handler. Invoke CPython eval loop
323-
# to let it being processed.
324-
PyErr_CheckSignals()
325-
_noop.noop()
351+
self._ceval_process_signals()
326352
return
327353

328354
if handle._cancelled:
@@ -2516,13 +2542,12 @@ cdef class Loop:
25162542

25172543
self._check_signal(sig)
25182544
self._check_closed()
2519-
25202545
try:
25212546
# set_wakeup_fd() raises ValueError if this is not the
25222547
# main thread. By calling it early we ensure that an
25232548
# event loop running in another thread cannot add a signal
25242549
# handler.
2525-
signal_set_wakeup_fd(self._csock.fileno())
2550+
_set_signal_wakeup_fd(self._csock.fileno())
25262551
except (ValueError, OSError) as exc:
25272552
raise RuntimeError(str(exc))
25282553

@@ -2532,7 +2557,7 @@ cdef class Loop:
25322557
try:
25332558
# Register a dummy signal handler to ask Python to write the signal
25342559
# number in the wakeup file descriptor.
2535-
signal_signal(sig, _sighandler_noop)
2560+
signal_signal(sig, self.__sighandler)
25362561

25372562
# Set SA_RESTART to limit EINTR occurrences.
25382563
signal_siginterrupt(sig, False)
@@ -2867,9 +2892,11 @@ cdef __install_pymem():
28672892
raise convert_error(err)
28682893

28692894

2870-
def _sighandler_noop(signum, frame):
2871-
"""Dummy signal handler."""
2872-
pass
2895+
cdef _set_signal_wakeup_fd(fd):
2896+
if sys_version_info >= (3, 7, 0) and fd >= 0:
2897+
signal_set_wakeup_fd(fd, warn_on_full_buffer=False)
2898+
else:
2899+
signal_set_wakeup_fd(fd)
28732900

28742901

28752902
########### Stuff for tests:

0 commit comments

Comments
 (0)