diff --git a/tests/test_signals.py b/tests/test_signals.py index 8462283..d5dbd72 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -227,6 +227,85 @@ loop.run_forever() class Test_UV_Signals(_TestSignal, tb.UVTestCase): NEW_LOOP = 'uvloop.new_event_loop()' + def test_signals_restore(self): + # Test that uvloop restores signals installed with the signals + # module after the loop is done running. + + async def runner(): + PROG = R"""\ +import asyncio +import uvloop +import signal +import time + +srv = None + +async def worker(): + global srv + cb = lambda *args: None + srv = await asyncio.start_server(cb, '127.0.0.1', 0) + print('READY', flush=True) + +def py_handler(signum, frame): + print('pyhandler', flush=True) + +def aio_handler(): + print('aiohandler', flush=True) + loop.stop() + +signal.signal(signal.SIGUSR1, py_handler) + +print('step1', flush=True) +print(input(), flush=True) +loop = """ + self.NEW_LOOP + """ +loop.add_signal_handler(signal.SIGUSR1, aio_handler) +asyncio.set_event_loop(loop) +loop.create_task(worker()) +loop.run_forever() +print('step3', flush=True) +print(input(), flush=True) +""" + + proc = await asyncio.create_subprocess_exec( + sys.executable, b'-c', PROG, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + loop=self.loop) + + ln = await proc.stdout.readline() + self.assertEqual(ln, b'step1\n') + + proc.send_signal(signal.SIGUSR1) + ln = await proc.stdout.readline() + self.assertEqual(ln, b'pyhandler\n') + + proc.stdin.write(b'test\n') + ln = await proc.stdout.readline() + self.assertEqual(ln, b'test\n') + + ln = await proc.stdout.readline() + self.assertEqual(ln, b'READY\n') + + proc.send_signal(signal.SIGUSR1) + ln = await proc.stdout.readline() + self.assertEqual(ln, b'aiohandler\n') + + ln = await proc.stdout.readline() + self.assertEqual(ln, b'step3\n') + + proc.send_signal(signal.SIGUSR1) + ln = await proc.stdout.readline() + self.assertEqual(ln, b'pyhandler\n') + + proc.stdin.write(b'done\n') + + out, err = await proc.communicate() + self.assertEqual(out, b'done\n') + self.assertEqual(err, b'') + + self.loop.run_until_complete(runner()) + class Test_AIO_Signals(_TestSignal, tb.AIOTestCase): NEW_LOOP = 'asyncio.new_event_loop()' diff --git a/uvloop/os_signal.pyx b/uvloop/os_signal.pyx index 05c5e1e..20e8964 100644 --- a/uvloop/os_signal.pyx +++ b/uvloop/os_signal.pyx @@ -1,5 +1,5 @@ -from posix.signal cimport sigaction_t, sigaction -from libc.signal cimport SIG_DFL, SIG_IGN, SIG_ERR, sighandler_t, signal, SIGINT +from posix.signal cimport sigaction_t, sigaction, sigfillset +from libc.signal cimport SIG_DFL, SIG_IGN, sighandler_t, signal, SIGINT cdef class SignalsStack: @@ -22,6 +22,7 @@ cdef class SignalsStack: cdef restore(self): cdef: sighandler_t sig + sigaction_t sa if not self.saved: raise RuntimeError("SignalsStack.save() wasn't called") @@ -29,9 +30,14 @@ cdef class SignalsStack: for i in range(MAX_SIG): if self.signals[i] == NULL: continue - sig = signal(i, self.signals[i]) - if sig == SIG_ERR: - raise RuntimeError("Couldn't restore signal {}".format(i)) + + memset(&sa, 0, sizeof(sa)) + if sigfillset(&sa.sa_mask): + raise RuntimeError( + 'failed to restore signal (sigfillset failed)') + sa.sa_handler = self.signals[i] + if sigaction(i, &sa, NULL): + raise convert_error(-errno.errno) cdef void __signal_handler_sigint(int sig) nogil: @@ -41,11 +47,7 @@ cdef void __signal_handler_sigint(int sig) nogil: # Python code here -- all '.' and '[]' operators work on # C structs/pointers. - if sig != SIGINT: - return - - if __main_loop__ is None or __main_loop__.py_signals is None: - # Shouldn't ever happen. + if sig != SIGINT or __main_loop__ is None: return if __main_loop__._executing_py_code and not __main_loop__._custom_sigint: @@ -54,9 +56,16 @@ cdef void __signal_handler_sigint(int sig) nogil: if __main_loop__.uv_signals is not None: handle = __main_loop__.uv_signals.signals[sig] - if handle not in (SIG_DFL, SIG_IGN, SIG_ERR, NULL): + if handle is not NULL: handle(sig) # void -cdef void __signal_set_sigint(): - signal(SIGINT, __signal_handler_sigint) +cdef __signal_set_sigint(): + cdef sigaction_t sa + memset(&sa, 0, sizeof(sa)) + if sigfillset(&sa.sa_mask): + raise RuntimeError( + 'failed to set SIGINT signal (sigfillset failed)') + sa.sa_handler = __signal_handler_sigint + if sigaction(SIGINT, &sa, NULL): + raise convert_error(-errno.errno)