Skip to content

Commit 97a6450

Browse files
committed
signals: Use 'sigaction' syscall for setting up signal handlers
1 parent e7c7af4 commit 97a6450

File tree

2 files changed

+101
-13
lines changed

2 files changed

+101
-13
lines changed

tests/test_signals.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,85 @@ def handler_hup():
227227
class Test_UV_Signals(_TestSignal, tb.UVTestCase):
228228
NEW_LOOP = 'uvloop.new_event_loop()'
229229

230+
def test_signals_restore(self):
231+
# Test that uvloop restores signals installed with the signals
232+
# module after the loop is done running.
233+
234+
async def runner():
235+
PROG = R"""\
236+
import asyncio
237+
import uvloop
238+
import signal
239+
import time
240+
241+
srv = None
242+
243+
async def worker():
244+
global srv
245+
cb = lambda *args: None
246+
srv = await asyncio.start_server(cb, '127.0.0.1', 0)
247+
print('READY', flush=True)
248+
249+
def py_handler(signum, frame):
250+
print('pyhandler', flush=True)
251+
252+
def aio_handler():
253+
print('aiohandler', flush=True)
254+
loop.stop()
255+
256+
signal.signal(signal.SIGUSR1, py_handler)
257+
258+
print('step1', flush=True)
259+
print(input(), flush=True)
260+
loop = """ + self.NEW_LOOP + """
261+
loop.add_signal_handler(signal.SIGUSR1, aio_handler)
262+
asyncio.set_event_loop(loop)
263+
loop.create_task(worker())
264+
loop.run_forever()
265+
print('step3', flush=True)
266+
print(input(), flush=True)
267+
"""
268+
269+
proc = await asyncio.create_subprocess_exec(
270+
sys.executable, b'-c', PROG,
271+
stdin=subprocess.PIPE,
272+
stdout=subprocess.PIPE,
273+
stderr=subprocess.PIPE,
274+
loop=self.loop)
275+
276+
ln = await proc.stdout.readline()
277+
self.assertEqual(ln, b'step1\n')
278+
279+
proc.send_signal(signal.SIGUSR1)
280+
ln = await proc.stdout.readline()
281+
self.assertEqual(ln, b'pyhandler\n')
282+
283+
proc.stdin.write(b'test\n')
284+
ln = await proc.stdout.readline()
285+
self.assertEqual(ln, b'test\n')
286+
287+
ln = await proc.stdout.readline()
288+
self.assertEqual(ln, b'READY\n')
289+
290+
proc.send_signal(signal.SIGUSR1)
291+
ln = await proc.stdout.readline()
292+
self.assertEqual(ln, b'aiohandler\n')
293+
294+
ln = await proc.stdout.readline()
295+
self.assertEqual(ln, b'step3\n')
296+
297+
proc.send_signal(signal.SIGUSR1)
298+
ln = await proc.stdout.readline()
299+
self.assertEqual(ln, b'pyhandler\n')
300+
301+
proc.stdin.write(b'done\n')
302+
303+
out, err = await proc.communicate()
304+
self.assertEqual(out, b'done\n')
305+
self.assertEqual(err, b'')
306+
307+
self.loop.run_until_complete(runner())
308+
230309

231310
class Test_AIO_Signals(_TestSignal, tb.AIOTestCase):
232311
NEW_LOOP = 'asyncio.new_event_loop()'

uvloop/os_signal.pyx

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from posix.signal cimport sigaction_t, sigaction
2-
from libc.signal cimport SIG_DFL, SIG_IGN, SIG_ERR, sighandler_t, signal, SIGINT
1+
from posix.signal cimport sigaction_t, sigaction, sigfillset
2+
from libc.signal cimport SIG_DFL, SIG_IGN, sighandler_t, signal, SIGINT
33

44

55
cdef class SignalsStack:
@@ -22,16 +22,22 @@ cdef class SignalsStack:
2222
cdef restore(self):
2323
cdef:
2424
sighandler_t sig
25+
sigaction_t sa
2526

2627
if not self.saved:
2728
raise RuntimeError("SignalsStack.save() wasn't called")
2829

2930
for i in range(MAX_SIG):
3031
if self.signals[i] == NULL:
3132
continue
32-
sig = signal(i, self.signals[i])
33-
if sig == SIG_ERR:
34-
raise RuntimeError("Couldn't restore signal {}".format(i))
33+
34+
memset(&sa, 0, sizeof(sa))
35+
if sigfillset(&sa.sa_mask):
36+
raise RuntimeError(
37+
'failed to restore signal (sigfillset failed)')
38+
sa.sa_handler = self.signals[i]
39+
if sigaction(i, &sa, NULL):
40+
raise convert_error(-errno.errno)
3541

3642

3743
cdef void __signal_handler_sigint(int sig) nogil:
@@ -41,11 +47,7 @@ cdef void __signal_handler_sigint(int sig) nogil:
4147
# Python code here -- all '.' and '[]' operators work on
4248
# C structs/pointers.
4349

44-
if sig != SIGINT:
45-
return
46-
47-
if __main_loop__ is None or __main_loop__.py_signals is None:
48-
# Shouldn't ever happen.
50+
if sig != SIGINT or __main_loop__ is None:
4951
return
5052

5153
if __main_loop__._executing_py_code and not __main_loop__._custom_sigint:
@@ -54,9 +56,16 @@ cdef void __signal_handler_sigint(int sig) nogil:
5456

5557
if __main_loop__.uv_signals is not None:
5658
handle = __main_loop__.uv_signals.signals[sig]
57-
if handle not in (SIG_DFL, SIG_IGN, SIG_ERR, NULL):
59+
if handle is not NULL:
5860
handle(sig) # void
5961

6062

61-
cdef void __signal_set_sigint():
62-
signal(SIGINT, <sighandler_t>__signal_handler_sigint)
63+
cdef __signal_set_sigint():
64+
cdef sigaction_t sa
65+
memset(&sa, 0, sizeof(sa))
66+
if sigfillset(&sa.sa_mask):
67+
raise RuntimeError(
68+
'failed to set SIGINT signal (sigfillset failed)')
69+
sa.sa_handler = __signal_handler_sigint
70+
if sigaction(SIGINT, &sa, NULL):
71+
raise convert_error(-errno.errno)

0 commit comments

Comments
 (0)