Skip to content

Commit e7934c8

Browse files
horptoАлександр Менщиковfantix
authored
Fix incorrect main thread id value forking from a thread (#453)
* Fix incorrect main thread id value in mp.Process * Make MAIN_THREAD_ID a lazy value and add test Co-authored-by: Александр Менщиков <menshchikov@zvonok.com> Co-authored-by: Fantix King <fantix.king@gmail.com>
1 parent 73d7253 commit e7934c8

File tree

6 files changed

+60
-10
lines changed

6 files changed

+60
-10
lines changed

tests/test_signals.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ async def coro():
310310
with self.assertRaisesRegex(TypeError, 'coroutines cannot be used'):
311311
self.loop.add_signal_handler(signal.SIGHUP, coro)
312312

313-
def test_wakeup_fd_unchanged(self):
313+
def test_signals_wakeup_fd_unchanged(self):
314314
async def runner():
315315
PROG = R"""\
316316
import uvloop
@@ -349,6 +349,42 @@ async def f(): pass
349349

350350
self.loop.run_until_complete(runner())
351351

352+
def test_signals_fork_in_thread(self):
353+
# Refs #452, when forked from a thread, the main-thread-only signal
354+
# operations failed thread ID checks because we didn't update
355+
# MAIN_THREAD_ID after fork. It's now a lazy value set when needed and
356+
# cleared after fork.
357+
PROG = R"""\
358+
import asyncio
359+
import multiprocessing
360+
import signal
361+
import sys
362+
import threading
363+
import uvloop
364+
365+
multiprocessing.set_start_method('fork')
366+
367+
def subprocess():
368+
loop = """ + self.NEW_LOOP + """
369+
loop.add_signal_handler(signal.SIGINT, lambda *a: None)
370+
371+
def run():
372+
loop = """ + self.NEW_LOOP + """
373+
loop.add_signal_handler(signal.SIGINT, lambda *a: None)
374+
p = multiprocessing.Process(target=subprocess)
375+
t = threading.Thread(target=p.start)
376+
t.start()
377+
t.join()
378+
p.join()
379+
sys.exit(p.exitcode)
380+
381+
run()
382+
"""
383+
384+
subprocess.check_call([
385+
sys.executable, b'-W', b'ignore', b'-c', PROG,
386+
])
387+
352388

353389
class Test_UV_Signals(_TestSignal, tb.UVTestCase):
354390
NEW_LOOP = 'uvloop.new_event_loop()'

uvloop/includes/fork_handler.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
volatile uint64_t MAIN_THREAD_ID = 0;
2+
volatile int8_t MAIN_THREAD_ID_SET = 0;
13

24
typedef void (*OnForkHandler)();
35

@@ -9,6 +11,10 @@ Note: Fork handler needs to be in C (not cython) otherwise it would require
911
GIL to be present, but some forks can exec non-python processes.
1012
*/
1113
void handleAtFork(void) {
14+
// Reset the MAIN_THREAD_ID on fork, because the main thread ID is not
15+
// always the same after fork, especially when forked from within a thread.
16+
MAIN_THREAD_ID_SET = 0;
17+
1218
if (__forkHandler != NULL) {
1319
__forkHandler();
1420
}
@@ -25,3 +31,8 @@ void resetForkHandler(void)
2531
{
2632
__forkHandler = NULL;
2733
}
34+
35+
void setMainThreadID(uint64_t id) {
36+
MAIN_THREAD_ID = id;
37+
MAIN_THREAD_ID_SET = 1;
38+
}

uvloop/includes/stdlib.pxi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ cdef int ssl_SSL_ERROR_WANT_READ = ssl.SSL_ERROR_WANT_READ
135135
cdef int ssl_SSL_ERROR_WANT_WRITE = ssl.SSL_ERROR_WANT_WRITE
136136
cdef int ssl_SSL_ERROR_SYSCALL = ssl.SSL_ERROR_SYSCALL
137137

138-
cdef uint64_t MAIN_THREAD_ID = <uint64_t><int64_t>threading.main_thread().ident
139138
cdef threading_Thread = threading.Thread
139+
cdef threading_main_thread = threading.main_thread
140140

141141
cdef int subprocess_PIPE = subprocess.PIPE
142142
cdef int subprocess_STDOUT = subprocess.STDOUT

uvloop/includes/system.pxd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from libc.stdint cimport int8_t, uint64_t
2+
13
cdef extern from "arpa/inet.h" nogil:
24

35
int ntohl(int)
@@ -85,7 +87,10 @@ cdef extern from "includes/compat.h" nogil:
8587

8688
cdef extern from "includes/fork_handler.h":
8789

90+
uint64_t MAIN_THREAD_ID
91+
int8_t MAIN_THREAD_ID_SET
8892
ctypedef void (*OnForkHandler)()
8993
void handleAtFork()
9094
void setForkHandler(OnForkHandler handler)
9195
void resetForkHandler()
96+
void setMainThreadID(uint64_t id)

uvloop/loop.pxd

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ cdef class Loop:
4444
bint _stopping
4545

4646
uint64_t _thread_id
47-
bint _thread_is_main
4847

4948
object _task_factory
5049
object _exception_handler

uvloop/loop.pyx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ cdef class Loop:
142142

143143
self._closed = 0
144144
self._debug = 0
145-
self._thread_is_main = 0
146145
self._thread_id = 0
147146
self._running = 0
148147
self._stopping = 0
@@ -216,7 +215,11 @@ cdef class Loop:
216215
self._servers = set()
217216

218217
cdef inline _is_main_thread(self):
219-
return MAIN_THREAD_ID == PyThread_get_thread_ident()
218+
cdef uint64_t main_thread_id = system.MAIN_THREAD_ID
219+
if system.MAIN_THREAD_ID_SET == 0:
220+
main_thread_id = <uint64_t><int64_t>threading_main_thread().ident
221+
system.setMainThreadID(main_thread_id)
222+
return main_thread_id == PyThread_get_thread_ident()
220223

221224
def __init__(self):
222225
self.set_debug((not sys_ignore_environment
@@ -520,7 +523,6 @@ cdef class Loop:
520523
self._last_error = None
521524

522525
self._thread_id = PyThread_get_thread_ident()
523-
self._thread_is_main = MAIN_THREAD_ID == self._thread_id
524526
self._running = 1
525527

526528
self.handler_check__exec_writes.start()
@@ -541,7 +543,6 @@ cdef class Loop:
541543

542544
self._pause_signals()
543545

544-
self._thread_is_main = 0
545546
self._thread_id = 0
546547
self._running = 0
547548
self._stopping = 0
@@ -3287,16 +3288,14 @@ cdef Loop __forking_loop = None
32873288

32883289

32893290
cdef void __get_fork_handler() nogil:
3290-
global __forking
3291-
global __forking_loop
3292-
32933291
with gil:
32943292
if (__forking and __forking_loop is not None and
32953293
__forking_loop.active_process_handler is not None):
32963294
__forking_loop.active_process_handler._after_fork()
32973295

32983296
cdef __install_atfork():
32993297
global __atfork_installed
3298+
33003299
if __atfork_installed:
33013300
return
33023301
__atfork_installed = 1

0 commit comments

Comments
 (0)