Skip to content

Commit 0d671c0

Browse files
authored
bpo-35621: Support running subprocesses in asyncio when loop is executed in non-main thread (GH-14344)
1 parent 5cbbbd7 commit 0d671c0

File tree

7 files changed

+378
-72
lines changed

7 files changed

+378
-72
lines changed

Doc/library/asyncio-policy.rst

+54-8
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ asyncio ships with the following built-in policies:
117117

118118
.. availability:: Windows.
119119

120+
.. _asyncio-watchers:
120121

121122
Process Watchers
122123
================
@@ -129,10 +130,11 @@ In asyncio, child processes are created with
129130
:func:`create_subprocess_exec` and :meth:`loop.subprocess_exec`
130131
functions.
131132

132-
asyncio defines the :class:`AbstractChildWatcher` abstract base class,
133-
which child watchers should implement, and has two different
134-
implementations: :class:`SafeChildWatcher` (configured to be used
135-
by default) and :class:`FastChildWatcher`.
133+
asyncio defines the :class:`AbstractChildWatcher` abstract base class, which child
134+
watchers should implement, and has four different implementations:
135+
:class:`ThreadedChildWatcher` (configured to be used by default),
136+
:class:`MultiLoopChildWatcher`, :class:`SafeChildWatcher`, and
137+
:class:`FastChildWatcher`.
136138

137139
See also the :ref:`Subprocess and Threads <asyncio-subprocess-threads>`
138140
section.
@@ -184,23 +186,64 @@ implementation used by the asyncio event loop:
184186

185187
Note: loop may be ``None``.
186188

189+
.. method:: is_active()
190+
191+
Return ``True`` if the watcher is ready to use.
192+
193+
Spawning a subprocess with *inactive* current child watcher raises
194+
:exc:`RuntimeError`.
195+
196+
.. versionadded:: 3.8
197+
187198
.. method:: close()
188199

189200
Close the watcher.
190201

191202
This method has to be called to ensure that underlying
192203
resources are cleaned-up.
193204

194-
.. class:: SafeChildWatcher
205+
.. class:: ThreadedChildWatcher
206+
207+
This implementation starts a new waiting thread for every subprocess spawn.
208+
209+
It works reliably even when the asyncio event loop is run in a non-main OS thread.
210+
211+
There is no noticeable overhead when handling a big number of children (*O(1)* each
212+
time a child terminates), but stating a thread per process requires extra memory.
213+
214+
This watcher is used by default.
215+
216+
.. versionadded:: 3.8
195217

196-
This implementation avoids disrupting other code spawning processes
218+
.. class:: MultiLoopChildWatcher
219+
220+
This implementation registers a :py:data:`SIGCHLD` signal handler on
221+
instantiation. That can break third-party code that installs a custom handler for
222+
`SIGCHLD`. signal).
223+
224+
The watcher avoids disrupting other code spawning processes
197225
by polling every process explicitly on a :py:data:`SIGCHLD` signal.
198226

199-
This is a safe solution but it has a significant overhead when
227+
There is no limitation for running subprocesses from different threads once the
228+
watcher is installed.
229+
230+
The solution is safe but it has a significant overhead when
200231
handling a big number of processes (*O(n)* each time a
201232
:py:data:`SIGCHLD` is received).
202233

203-
asyncio uses this safe implementation by default.
234+
.. versionadded:: 3.8
235+
236+
.. class:: SafeChildWatcher
237+
238+
This implementation uses active event loop from the main thread to handle
239+
:py:data:`SIGCHLD` signal. If the main thread has no running event loop another
240+
thread cannot spawn a subprocess (:exc:`RuntimeError` is raised).
241+
242+
The watcher avoids disrupting other code spawning processes
243+
by polling every process explicitly on a :py:data:`SIGCHLD` signal.
244+
245+
This solution is as safe as :class:`MultiLoopChildWatcher` and has the same *O(N)*
246+
complexity but requires a running event loop in the main thread to work.
204247

205248
.. class:: FastChildWatcher
206249

@@ -211,6 +254,9 @@ implementation used by the asyncio event loop:
211254
There is no noticeable overhead when handling a big number of
212255
children (*O(1)* each time a child terminates).
213256

257+
This solution requires a running event loop in the main thread to work, as
258+
:class:`SafeChildWatcher`.
259+
214260

215261
Custom Policies
216262
===============

Doc/library/asyncio-subprocess.rst

+17-9
Original file line numberDiff line numberDiff line change
@@ -293,18 +293,26 @@ their completion.
293293
Subprocess and Threads
294294
----------------------
295295

296-
Standard asyncio event loop supports running subprocesses from
297-
different threads, but there are limitations:
296+
Standard asyncio event loop supports running subprocesses from different threads by
297+
default.
298298

299-
* An event loop must run in the main thread.
299+
On Windows subprocesses are provided by :class:`ProactorEventLoop` only (default),
300+
:class:`SelectorEventLoop` has no subprocess support.
300301

301-
* The child watcher must be instantiated in the main thread
302-
before executing subprocesses from other threads. Call the
303-
:func:`get_child_watcher` function in the main thread to instantiate
304-
the child watcher.
302+
On UNIX *child watchers* are used for subprocess finish waiting, see
303+
:ref:`asyncio-watchers` for more info.
305304

306-
Note that alternative event loop implementations might not share
307-
the above limitations; please refer to their documentation.
305+
306+
.. versionchanged:: 3.8
307+
308+
UNIX switched to use :class:`ThreadedChildWatcher` for spawning subprocesses from
309+
different threads without any limitation.
310+
311+
Spawning a subprocess with *inactive* current child watcher raises
312+
:exc:`RuntimeError`.
313+
314+
Note that alternative event loop implementations might have own limitations;
315+
please refer to their documentation.
308316

309317
.. seealso::
310318

0 commit comments

Comments
 (0)