Skip to content

Commit 7de9458

Browse files
committed
Serialize process spawning across threads with a lock
Replace the RuntimeError-raising check for concurrent process spawning with a threading.Lock that serializes spawns across different event loops running in separate threads. The old approach would fail with "Racing with another loop to spawn a process" when multiple threads tried to spawn processes concurrently, since the global pthread_atfork handlers can only be active for one loop at a time. Now instead of failing, concurrent spawns wait for the lock, allowing them to proceed sequentially. The lock is properly released in all error paths via a finally block. Fixes #508
1 parent a308f75 commit 7de9458

File tree

2 files changed

+33
-14
lines changed

2 files changed

+33
-14
lines changed

uvloop/handles/process.pyx

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,21 +68,28 @@ cdef class UVProcess(UVHandle):
6868
self._abort_init()
6969
raise
7070

71-
if __forking or loop.active_process_handler is not None:
72-
# Our pthread_atfork handlers won't work correctly when
73-
# another loop is forking in another thread (even though
74-
# GIL should help us to avoid that.)
71+
# Acquire the global spawn lock to serialize process spawning
72+
# across threads. Without this, concurrent spawns from different
73+
# loops would race on the global pthread_atfork handlers.
74+
__spawn_lock.acquire()
75+
_spawn_lock_held = True
76+
77+
if loop.active_process_handler is not None:
78+
__spawn_lock.release()
79+
_spawn_lock_held = False
7580
self._abort_init()
7681
raise RuntimeError(
77-
'Racing with another loop to spawn a process.')
78-
79-
self._errpipe_read, self._errpipe_write = os_pipe()
80-
fds_to_close = self._fds_to_close
81-
self._fds_to_close = None
82-
fds_to_close.append(self._errpipe_read)
83-
# add the write pipe last so we can close it early
84-
fds_to_close.append(self._errpipe_write)
82+
'Racing with the same loop to spawn a process.')
83+
84+
fds_to_close = None
8585
try:
86+
self._errpipe_read, self._errpipe_write = os_pipe()
87+
fds_to_close = self._fds_to_close
88+
self._fds_to_close = None
89+
fds_to_close.append(self._errpipe_read)
90+
# add the write pipe last so we can close it early
91+
fds_to_close.append(self._errpipe_write)
92+
8693
os_set_inheritable(self._errpipe_write, True)
8794

8895
self._preexec_fn = preexec_fn
@@ -103,6 +110,8 @@ cdef class UVProcess(UVHandle):
103110
__forking_loop = None
104111
system.resetForkHandler()
105112
loop.active_process_handler = None
113+
__spawn_lock.release()
114+
_spawn_lock_held = False
106115

107116
PyOS_AfterFork_Parent()
108117

@@ -128,8 +137,16 @@ cdef class UVProcess(UVHandle):
128137
break
129138

130139
finally:
131-
while fds_to_close:
132-
os_close(fds_to_close.pop())
140+
if _spawn_lock_held:
141+
__forking = 0
142+
__forking_loop = None
143+
system.resetForkHandler()
144+
loop.active_process_handler = None
145+
__spawn_lock.release()
146+
147+
if fds_to_close is not None:
148+
while fds_to_close:
149+
os_close(fds_to_close.pop())
133150

134151
for fd in restore_inheritable:
135152
os_set_inheritable(fd, False)

uvloop/loop.pyx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ from cpython cimport (
4141
from cpython.pycapsule cimport PyCapsule_New, PyCapsule_GetPointer
4242

4343
from . import _noop
44+
import threading
45+
__spawn_lock = threading.Lock()
4446

4547

4648
include "includes/stdlib.pxi"

0 commit comments

Comments
 (0)