@@ -8,11 +8,16 @@ cdef class UVProcess(UVHandle):
88 self ._returncode = None
99 self ._pid = None
1010 self ._fds_to_close = set ()
11+ self ._preexec_fn = None
12+ self ._restore_signals = True
1113
1214 cdef _init(self , Loop loop, list args, dict env,
1315 cwd, start_new_session,
1416 _stdin, _stdout, _stderr, # std* can be defined as macros in C
15- pass_fds, debug_flags):
17+ pass_fds, debug_flags, preexec_fn, restore_signals):
18+
19+ global __forking
20+ global __forking_loop
1621
1722 cdef int err
1823
@@ -43,14 +48,62 @@ cdef class UVProcess(UVHandle):
4348 self ._abort_init()
4449 raise
4550
46- err = uv.uv_spawn( loop.uvloop,
47- < uv.uv_process_t * > self ._handle,
48- & self .options)
49- if err < 0 :
51+ if __forking or loop.active_process_handler is not None :
52+ # Our pthread_atfork handlers won't work correctly when
53+ # another loop is forking in another thread (even though
54+ # GIL should help us to avoid that.)
5055 self ._abort_init()
51- raise convert_error(err)
56+ raise RuntimeError (
57+ ' Racing with another loop to spawn a process.' )
58+
59+ self ._errpipe_read, self ._errpipe_write = os_pipe()
60+ try :
61+ os_set_inheritable(self ._errpipe_write, True )
62+
63+ self ._preexec_fn = preexec_fn
64+ self ._restore_signals = restore_signals
65+
66+ loop.active_process_handler = self
67+ __forking = 1
68+ __forking_loop = loop
69+
70+ _PyImport_AcquireLock()
71+
72+ err = uv.uv_spawn(loop.uvloop,
73+ < uv.uv_process_t* > self ._handle,
74+ & self .options)
5275
53- self ._finish_init()
76+ __forking = 0
77+ __forking_loop = None
78+ loop.active_process_handler = None
79+
80+ if _PyImport_ReleaseLock() < 0 :
81+ # See CPython/posixmodule.c for details
82+ self ._abort_init()
83+ raise RuntimeError (' not holding the import lock' )
84+
85+ if err < 0 :
86+ self ._abort_init()
87+ raise convert_error(err)
88+
89+ self ._finish_init()
90+
91+ os_close(self ._errpipe_write)
92+
93+ errpipe_data = bytearray()
94+ while True :
95+ part = os_read(self ._errpipe_read, 50000 )
96+ errpipe_data += part
97+ if not part or len (errpipe_data) > 50000 :
98+ break
99+
100+ finally :
101+ os_close(self ._errpipe_read)
102+ try :
103+ os_close(self ._errpipe_write)
104+ except OSError :
105+ # Might be already closed
106+ pass
54107
55108 # asyncio caches the PID in BaseSubprocessTransport,
56109 # so that the transport knows what the PID was even
@@ -68,6 +121,52 @@ cdef class UVProcess(UVHandle):
68121 if debug_flags & __PROCESS_DEBUG_SLEEP_AFTER_FORK:
69122 time_sleep(1 )
70123
124+ if errpipe_data:
125+ # preexec_fn has raised an exception. The child
126+ # process must be dead now.
127+ try :
128+ exc_name, exc_msg = errpipe_data.split(b' :' , 1 )
129+ exc_name = exc_name.decode()
130+ exc_msg = exc_msg.decode()
131+ except :
132+ self ._close()
133+ raise subprocess_SubprocessError(
134+ ' Bad exception data from child: {!r}' .format(
135+ errpipe_data))
136+ exc_cls = getattr (__builtins__, exc_name,
137+ subprocess_SubprocessError)
138+
139+ exc = subprocess_SubprocessError(
140+ ' Exception occurred in preexec_fn.' )
141+ exc.__cause__ = exc_cls(exc_msg)
142+ self ._close()
143+ raise exc
144+
145+ cdef _after_fork(self ):
146+ # See CPython/_posixsubprocess.c for details
147+
148+ if self ._restore_signals:
149+ _Py_RestoreSignals()
150+
151+ if self ._preexec_fn is not None :
152+ PyOS_AfterFork()
153+
154+ try :
155+ gc_disable()
156+ self ._preexec_fn()
157+ except BaseException as ex:
158+ try :
159+ with open (self ._errpipe_write, ' wb' ) as f:
160+ f.write(str (ex.__class__ .__name__ ).encode())
161+ f.write(b' :' )
162+ f.write(str (ex.args[0 ]).encode())
163+ finally :
164+ system._exit(255 )
165+ else :
166+ os_close(self ._errpipe_write)
167+ else :
168+ os_close(self ._errpipe_write)
169+
71170 cdef _close_after_spawn(self , int fd):
72171 if self ._fds_to_close is None :
73172 raise RuntimeError (
@@ -438,7 +537,9 @@ cdef class UVProcessTransport(UVProcess):
438537 cwd, start_new_session,
439538 _stdin, _stdout, _stderr, pass_fds,
440539 waiter,
441- debug_flags):
540+ debug_flags,
541+ preexec_fn,
542+ restore_signals):
442543
443544 cdef UVProcessTransport handle
444545 handle = UVProcessTransport.__new__ (UVProcessTransport)
@@ -448,7 +549,9 @@ cdef class UVProcessTransport(UVProcess):
448549 __process_convert_fileno(_stdout),
449550 __process_convert_fileno(_stderr),
450551 pass_fds,
451- debug_flags)
552+ debug_flags,
553+ preexec_fn,
554+ restore_signals)
452555
453556 if handle._init_futs:
454557 handle._stdio_ready = 0
0 commit comments