Skip to content

Commit 07d028f

Browse files
b-passpre-commit-ci[bot]henryiiiCopilotSkylion007
authored
feat: Embeded sub-interpreters (#5666)
* First draft a subinterpreter embedding API * Move subinterpreter tests to their own file * Migrate subinterpreter tests to use the new embedded class. * Add a test for moving subinterpreters across threads for destruction And find a better way to make that work. * Code organization * Add a test which shows demostrates how gil_scoped interacts with sub-interpreters * Add documentation for embeded sub-interpreters * Some additional docs work * Add some convenience accessors * Add some docs cross references * Sync some things that were split out into #5665 * Update subinterpreter docs example to not use the CPython api * Fix pip test * style: pre-commit fixes * Fix MSVC warnings I am surprised other compilers allowed this code with a deleted move ctor. * Add some sub-headings to the docs * Oops, make_unique is C++14 so remove it from the tests. * I think this fixes the EndInterpreter issues on all versions. It just has to be ifdef'd because it is slightly broken on 3.12, working well on 3.13, and kind of crashy on 3.14beta. These two verion ifdefs solve all the issues. * Add a note about exceptions. They contain Python object references and acquire the GIL, that means they are a danger with subinterpreters! * style: pre-commit fixes * Add try/catch to docs examples to match the tips * Python 3.12 is very picky about this first PyThreadState Try special casing the destruction on the same thread. * style: pre-commit fixes * Missed a rename in a ifdef block * I think this test is causing problems in 3.12, so try ifdefing it to see if the problems go away. * style: pre-commit fixes * Document the 3.12 constraints with a warning * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> * ci: add cpptest to the clang-tidy job Signed-off-by: Henry Schreiner <[email protected]> * noexcept move operations * Update include/pybind11/subinterpreter.h std::memset Co-authored-by: Aaron Gokaslan <[email protected]> --------- Signed-off-by: Henry Schreiner <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Henry Schreiner <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Aaron Gokaslan <[email protected]>
1 parent ec8b050 commit 07d028f

File tree

10 files changed

+1021
-308
lines changed

10 files changed

+1021
-308
lines changed

.github/workflows/format.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,6 @@ jobs:
5555
5656
- name: Build
5757
run: cmake --build build -j 2 -- --keep-going
58+
59+
- name: Embedded
60+
run: cmake --build build -t cpptest -j 2 -- --keep-going

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ set(PYBIND11_HEADERS
223223
include/pybind11/operators.h
224224
include/pybind11/pybind11.h
225225
include/pybind11/pytypes.h
226+
include/pybind11/subinterpreter.h
226227
include/pybind11/stl.h
227228
include/pybind11/stl_bind.h
228229
include/pybind11/stl/filesystem.h

docs/advanced/embedding.rst

Lines changed: 246 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -237,31 +237,259 @@ global data. All the details can be found in the CPython documentation.
237237

238238
Creating two concurrent ``scoped_interpreter`` guards is a fatal error. So is
239239
calling ``initialize_interpreter`` for a second time after the interpreter
240-
has already been initialized.
240+
has already been initialized. Use :class:`scoped_subinterpreter` to create
241+
a sub-interpreter. See :ref:`subinterp` for important details on sub-interpreters.
241242

242243
Do not use the raw CPython API functions ``Py_Initialize`` and
243244
``Py_Finalize`` as these do not properly handle the lifetime of
244245
pybind11's internal data.
245246

246247

247-
Sub-interpreter support
248-
=======================
248+
.. _subinterp:
249+
250+
Embedding Sub-interpreters
251+
==========================
252+
253+
A sub-interpreter is a separate interpreter instance which provides a
254+
separate, isolated interpreter environment within the same process as the main
255+
interpreter. Sub-interpreters are created and managed with a separate API from
256+
the main interpreter. Beginning in Python 3.12, sub-interpreters each have
257+
their own Global Interpreter Lock (GIL), which means that running a
258+
sub-interpreter in a separate thread from the main interpreter can achieve true
259+
concurrency.
260+
261+
pybind11's sub-interpreter API can be found in ``pybind11/subinterpreter.h``.
262+
263+
pybind11 :class:`subinterpreter` instances can be safely moved and shared between
264+
threads as needed. However, managing multiple threads and the lifetimes of multiple
265+
interpreters and their GILs can be challenging.
266+
Proceed with caution (and lots of testing)!
267+
268+
The main interpreter must be initialized before creating a sub-interpreter, and
269+
the main interpreter must outlive all sub-interpreters. Sub-interpreters are
270+
managed through a different API than the main interpreter.
271+
272+
The :class:`subinterpreter` class manages the lifetime of sub-interpreters.
273+
Instances are movable, but not copyable. Default constructing this class does
274+
*not* create a sub-interpreter (it creates an empty holder). To create a
275+
sub-interpreter, call :func:`subinterpreter::create()`.
276+
277+
.. warning::
278+
279+
Sub-interpreter creation acquires (and subsequently releases) the main
280+
interpreter GIL. If another thread holds the main GIL, the function will
281+
block until the main GIL can be acquired.
282+
283+
Sub-interpreter destruction temporarily activates the sub-interpreter. The
284+
sub-interpreter must not be active (on any threads) at the time the
285+
:class:`subinterpreter` destructor is called.
286+
287+
Both actions will re-acquire any interpreter's GIL that was held prior to
288+
the call before returning (or return to no active interpreter if none was
289+
active at the time of the call).
290+
291+
Each sub-interpreter will import a separate copy of each ``PYBIND11_EMBEDDED_MODULE``
292+
when those modules specify a ``multiple_interpreters`` tag. If a module does not
293+
specify a ``multiple_interpreters`` tag, then Python will report an ``ImportError``
294+
if it is imported in a sub-interpreter.
295+
296+
pybind11 also has a :class:`scoped_subinterpreter` class, which creates and
297+
activates a sub-interpreter when it is constructed, and deactivates and deletes
298+
it when it goes out of scope.
299+
300+
Activating a Sub-interpreter
301+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
302+
303+
Once a sub-interpreter is created, you can "activate" it on a thread (and
304+
acquire its GIL) by creating a :class:`subinterpreter_scoped_activate`
305+
instance and passing it the sub-intepreter to be activated. The function
306+
will acquire the sub-interpreter's GIL and make the sub-interpreter the
307+
current active interpreter on the current thread for the lifetime of the
308+
instance. When the :class:`subinterpreter_scoped_activate` instance goes out
309+
of scope, the sub-interpreter GIL is released and the prior interpreter that
310+
was active on the thread (if any) is reactivated and it's GIL is re-acquired.
311+
312+
When using ``subinterpreter_scoped_activate``:
313+
314+
1. If the thread holds any interpreter's GIL:
315+
- That GIL is released
316+
2. The new sub-interpreter's GIL is acquired
317+
3. The new sub-interpreter is made active.
318+
4. When the scope ends:
319+
- The sub-interpreter's GIL is released
320+
- If there was a previous interpreter:
321+
- The old interpreter's GIL is re-acquired
322+
- The old interpreter is made active
323+
- Otherwise, no interpreter is currently active and no GIL is held.
324+
325+
Example:
326+
327+
.. code-block:: cpp
328+
329+
py::initialize_interpreter();
330+
// Main GIL is held
331+
{
332+
py::subinterpreter sub = py::subinterpreter::create();
333+
// Main interpreter is still active, main GIL re-acquired
334+
{
335+
py::subinterpreter_scoped_activate guard(sub);
336+
// Sub-interpreter active, thread holds sub's GIL
337+
{
338+
py::subinterpreter_scoped_activate main_guard(py);
339+
// Sub's GIL was automatically released
340+
// Main interpreter active, thread holds main's GIL
341+
}
342+
// Back to sub-interpreter, thread holds sub's GIL again
343+
}
344+
// Main interpreter is active, main's GIL is held
345+
}
346+
347+
348+
GIL API for sub-interpreters
349+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
350+
351+
:class:`gil_scoped_release` and :class:`gil_scoped_acquire` can be used to
352+
manage the GIL of a sub-interpreter just as they do for the main interpreter.
353+
They both manage the GIL of the currently active interpreter, without the
354+
programmer having to do anything special or different. There is one important
355+
caveat:
356+
357+
.. note::
358+
359+
When no interpreter is active through a
360+
:class:`subinterpreter_scoped_activate` instance (such as on a new thread),
361+
:class:`gil_scoped_acquire` will acquire the **main** GIL and
362+
activate the **main** interpreter.
363+
364+
365+
Full Sub-interpreter example
366+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
367+
368+
Here is an example showing how to create and activate sub-interpreters:
369+
370+
.. code-block:: cpp
371+
372+
#include <iostream>
373+
#include <pybind11/embed.h>
374+
#include <pybind11/subinterpreter.h>
375+
376+
namespace py = pybind11;
377+
378+
PYBIND11_EMBEDDED_MODULE(printer, m, py::multiple_interpreters::per_interpreter_gil()) {
379+
m.def("which", [](const std::string& when) {
380+
std::cout << when << "; Current Interpreter is "
381+
<< py::subinterpreter::current().id()
382+
<< std::endl;
383+
});
384+
}
385+
386+
int main() {
387+
py::scoped_interpreter main_interp;
388+
389+
py::module_::import("printer").attr("which")("First init");
390+
391+
{
392+
py::subinterpreter sub = py::subinterpreter::create();
393+
394+
py::module_::import("printer").attr("which")("Created sub");
395+
396+
{
397+
py::subinterpreter_scoped_activate guard(sub);
398+
try {
399+
py::module_::import("printer").attr("which")("Activated sub");
400+
}
401+
catch (py::error_already_set &e) {
402+
std::cerr << "EXCEPTION " << e.what() << std::endl;
403+
return 1;
404+
}
405+
}
406+
407+
py::module_::import("printer").attr("which")("Deactivated sub");
408+
409+
{
410+
py::gil_scoped_release nogil;
411+
{
412+
py::subinterpreter_scoped_activate guard(sub);
413+
try {
414+
{
415+
py::subinterpreter_scoped_activate main_guard(py::subinterpreter::main());
416+
try {
417+
py::module_::import("printer").attr("which")("Main within sub");
418+
}
419+
catch (py::error_already_set &e) {
420+
std::cerr << "EXCEPTION " << e.what() << std::endl;
421+
return 1;
422+
}
423+
}
424+
py::module_::import("printer").attr("which")("After Main, still within sub");
425+
}
426+
catch (py::error_already_set &e) {
427+
std::cerr << "EXCEPTION " << e.what() << std::endl;
428+
return 1;
429+
}
430+
}
431+
}
432+
}
433+
434+
py::module_::import("printer").attr("which")("At end");
435+
436+
return 0;
437+
}
438+
439+
Expected output:
440+
441+
.. code-block:: text
442+
443+
First init; Current Interpreter is 0
444+
Created sub; Current Interpreter is 0
445+
Activated sub; Current Interpreter is 1
446+
Deactivated sub; Current Interpreter is 0
447+
Main within sub; Current Interpreter is 0
448+
After Main, still within sub; Current Interpreter is 1
449+
At end; Current Interpreter is 0
450+
451+
.. warning::
452+
453+
In Python 3.12 sub-interpreters must be destroyed in the same OS thread
454+
that created them. Failure to follow this rule may result in deadlocks
455+
or crashes when destroying the sub-interpreter on the wrong thread.
456+
457+
This constraint is not present in Python 3.13+.
458+
459+
460+
Best Practices for sub-interpreter safety
461+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
462+
463+
- Never share Python objects across different interpreters.
464+
465+
- :class:`error_already_set` objects contain a reference to the Python exception type,
466+
and :func:`error_already_set::what()` acquires the GIL. So Python exceptions must
467+
**never** be allowed to propagate past the enclosing
468+
:class:`subinterpreter_scoped_activate` instance!
469+
(So your try/catch should be *just inside* the scope covered by the
470+
:class:`subinterpreter_scoped_activate`.)
471+
472+
- Avoid global/static state whenever possible. Instead, keep state within each interpreter,
473+
such as within the interpreter state dict, which can be accessed via
474+
``subinterpreter::current().state_dict()``, or within instance members and tied to
475+
Python objects.
476+
477+
- Avoid trying to "cache" Python objects in C++ variables across function calls (this is an easy
478+
way to accidentally introduce sub-interpreter bugs). In the code example above, note that we
479+
did not save the result of :func:`module_::import`, in order to avoid accidentally using the
480+
resulting Python object when the wrong interpreter was active.
249481

250-
Creating multiple copies of ``scoped_interpreter`` is not possible because it
251-
represents the main Python interpreter. Sub-interpreters are something different
252-
and they do permit the existence of multiple interpreters. This is an advanced
253-
feature of the CPython API and should be handled with care. pybind11 does not
254-
currently offer a C++ interface for sub-interpreters, so refer to the CPython
255-
documentation for all the details regarding this feature.
482+
- Avoid moving or disarming RAII objects managing GIL and sub-interpreter lifetimes. Doing so can
483+
lead to confusion about lifetimes. (For example, accidentally extending a
484+
:class:`subinterpreter_scoped_activate` past the lifetime of it's :class:`subinterpreter`.)
256485

257-
We'll just mention a couple of caveats the sub-interpreters support in pybind11:
486+
- While sub-interpreters each have their own GIL, there can now be multiple independent GILs in one
487+
program so you need to consider the possibility of deadlocks caused by multiple GILs and/or the
488+
interactions of the GIL(s) and your C++ code's own locking.
258489

259-
1. Sub-interpreters will not receive independent copies of embedded modules.
260-
Instead, these are shared and modifications in one interpreter may be
261-
reflected in another.
490+
- When using multiple threads to run independent sub-interpreters, the independent GILs allow
491+
concurrent calls from different interpreters into the same C++ code from different threads.
492+
So you must still consider the thread safety of your C++ code. Remember, in Python 3.12
493+
sub-interpreters must be destroyed on the same thread that they were created on.
262494

263-
2. Managing multiple threads, multiple interpreters and the GIL can be
264-
challenging and there are several caveats here, even within the pure
265-
CPython API (please refer to the Python docs for details). As for
266-
pybind11, keep in mind that ``gil_scoped_release`` and ``gil_scoped_acquire``
267-
do not take sub-interpreters into account.
495+
- Familiarize yourself with :ref:`misc_concurrency`.

docs/advanced/misc.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ You can explicitly disable sub-interpreter support in your module by using the
228228
:func:`multiple_interpreters::not_supported()` tag. This is the default behavior if you do not
229229
specify a multiple_interpreters tag.
230230

231+
.. _misc_concurrency:
232+
231233
Concurrency and Parallelism in Python with pybind11
232234
===================================================
233235

include/pybind11/gil.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class gil_scoped_acquire {
130130
}
131131

132132
/// This method will disable the PyThreadState_DeleteCurrent call and the
133-
/// GIL won't be acquired. This method should be used if the interpreter
133+
/// GIL won't be released. This method should be used if the interpreter
134134
/// could be shutting down when this is called, as thread deletion is not
135135
/// allowed during shutdown. Check _Py_IsFinalizing() on Python 3.7+, and
136136
/// protect subsequent code.

0 commit comments

Comments
 (0)