Skip to content

Commit

Permalink
Make signals processing more reliable
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
1st1 committed May 25, 2018
1 parent ce2bd4f commit 6e03e51
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 19 deletions.
4 changes: 3 additions & 1 deletion uvloop/loop.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ cdef class Loop:
dict _fd_to_reader_fileobj
dict _fd_to_writer_fileobj

set _signals
dict _signal_handlers
object _ssock
object _csock
Expand Down Expand Up @@ -199,7 +200,8 @@ cdef class Loop:

cdef _handle_signal(self, sig)
cdef _read_from_self(self)
cdef _process_self_data(self, data)
cdef inline _ceval_process_signals(self)
cdef _invoke_signals(self, bytes data)

cdef _set_coroutine_debug(self, bint enabled)

Expand Down
63 changes: 45 additions & 18 deletions uvloop/loop.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \
Py_buffer, PyBytes_AsString, PyBytes_CheckExact, \
Py_SIZE, PyBytes_AS_STRING

from cpython cimport PyErr_CheckSignals

from . import _noop


Expand Down Expand Up @@ -149,6 +147,7 @@ cdef class Loop:
self, "loop._exec_queued_writes",
<method_t>self._exec_queued_writes, self))

self._signals = set()
self._ssock = self._csock = None
self._signal_handlers = {}
self._listening_signals = False
Expand Down Expand Up @@ -234,7 +233,7 @@ cdef class Loop:
self._ssock.setblocking(False)
self._csock.setblocking(False)
try:
signal_set_wakeup_fd(self._csock.fileno())
_set_signal_wakeup_fd(self._csock.fileno())
except (OSError, ValueError):
# Not the main thread
self._ssock.close()
Expand Down Expand Up @@ -290,23 +289,54 @@ cdef class Loop:

self._listening_signals = False

def __sighandler(self, signum, frame):
self._signals.add(signum)

cdef inline _ceval_process_signals(self):
# Invoke CPython eval loop to let process signals.
PyErr_CheckSignals()
# Calling a pure-Python function will invoke
# _PyEval_EvalFrameDefault which will process
# pending signal callbacks.
_noop.noop() # Might raise ^C

cdef _read_from_self(self):
cdef bytes sigdata
sigdata = b''
while True:
try:
data = self._ssock.recv(4096)
data = self._ssock.recv(65536)
if not data:
break
self._process_self_data(data)
sigdata += data
except InterruptedError:
continue
except BlockingIOError:
break
if sigdata:
self._invoke_signals(sigdata)

cdef _invoke_signals(self, bytes data):
cdef set sigs

self._ceval_process_signals()

cdef _process_self_data(self, data):
sigs = self._signals.copy()
self._signals.clear()
for signum in data:
if not signum:
# ignore null bytes written by _write_to_self()
# ignore null bytes written by set_wakeup_fd()
continue
sigs.discard(signum)
self._handle_signal(signum)

for signum in sigs:
# Since not all signals are registered by add_signal_handler()
# (for instance, we use the default SIGINT handler) not all
# signals will trigger loop.__sighandler() callback. Therefore
# we combine two datasources: one is self-pipe, one is data
# from __sighandler; this ensures that signals shouldn't be
# lost even if set_wakeup_fd() couldn't write to the self-pipe.
self._handle_signal(signum)

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

if handle is None:
# Some signal that we aren't listening through
# add_signal_handler. Invoke CPython eval loop
# to let it being processed.
PyErr_CheckSignals()
_noop.noop()
self._ceval_process_signals()
return

if handle._cancelled:
Expand Down Expand Up @@ -2516,13 +2542,12 @@ cdef class Loop:

self._check_signal(sig)
self._check_closed()

try:
# set_wakeup_fd() raises ValueError if this is not the
# main thread. By calling it early we ensure that an
# event loop running in another thread cannot add a signal
# handler.
signal_set_wakeup_fd(self._csock.fileno())
_set_signal_wakeup_fd(self._csock.fileno())
except (ValueError, OSError) as exc:
raise RuntimeError(str(exc))

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

# Set SA_RESTART to limit EINTR occurrences.
signal_siginterrupt(sig, False)
Expand Down Expand Up @@ -2867,9 +2892,11 @@ cdef __install_pymem():
raise convert_error(err)


def _sighandler_noop(signum, frame):
"""Dummy signal handler."""
pass
cdef _set_signal_wakeup_fd(fd):
if sys_version_info >= (3, 7, 0) and fd >= 0:
signal_set_wakeup_fd(fd, warn_on_full_buffer=False)
else:
signal_set_wakeup_fd(fd)


########### Stuff for tests:
Expand Down

0 comments on commit 6e03e51

Please sign in to comment.