Repository navigation
[tpython] Handle Python interpreter embedding in TPython, not cppjit - #23616
guitargeek wants to merge 3 commits into
Conversation
cppjit is a Python extension library, not an embedding library. Remove the PyConfig/Py_InitializeFromConfig fallback from cpyrt's private Initialize() and let it fail gracefully when the interpreter has not been initialized by the embedding application. TPython::Initialize() now owns interpreter embedding for the Cling-first case, replacing the cpyrt::Scope_Check(nullptr) side-effect hack. Along the same boundary, move the 'access to the python interpreter' functions out of cpyrt entirely, since their only consumer is TPython and they use none of cppjit's proxy machinery: - cpyrt::Exec/ExecScript/Prompt are inlined into TPython::Exec, TPython::ExecScript and TPython::Prompt. This also removes the duplicated static gMainDict in API.cxx; TPython now consistently uses its own. - cpyrt::Import is inlined into TPython::Import, which additionally stops leaking the module reference. The attach of the imported module to the cppjit module (to prevent a proxy of the C++ proxy) goes through a new small exported getter, cpyrt::GetThisModule(). The dead class-iteration loop (which computed a class name that was never used) is dropped; namespace/class creation already happens on the TPython side via TClass::GetClass. Collateral changes: - Utility::IncludePython() now explicitly includes <vector> in the JIT preamble, which previously arrived transitively through cpyrt/API.h. - Dropped test_api.py::test02_interpreter_access, which exercised the removed cpyrt::Exec API. 🤖 Done with the help of AI
- Deduplicate the PyGILRAII helper, which had one copy per TU, into a single private header, PyGILRAII.h. - Drop the write-only mainThreadState global: the PyEval_SaveThread() result was never restored because ROOT intentionally never finalizes the embedded interpreter; say so and discard it explicitly. - Warn in TPython::Import() when the cppjit module is unavailable and the imported module cannot be attached to it (previously a silent skip with a confusing double-proxy symptom downstream). - Fix a stale comment that still referred to cpyrt.h instead of cpyrt/API.h. 🤖 Done with the help of AI
The compiled ROOTTPython library was built with -DPy_LIMITED_API, but TPython now embeds the Python interpreter (PyConfig, Py_InitializeFromConfig) and serves the interactive prompt (PyRun_InteractiveLoop), none of which are part of the limited API: embedding interpreters is out of the stable ABI's scope by design. This broke all non-free-threaded, non-Windows CI builds, where the flag was active.
Test Results 24 files 24 suites 3d 21h 8m 1s ⏱️ For more details on these failures, see this check. Results for commit 9159ea6. ♻️ This comment has been updated with latest results. |
|
|
||
| // | ||
| // Access to the python interpreter and API onto cpyrt. | ||
| // API onto cpyrt: access to cppjit proxies from Cling and C++. |
There was a problem hiding this comment.
Does cppjit want to have comments mentioning explicitly Cling?
There was a problem hiding this comment.
It did before, the Cling name drop was just moved from API.cxx. But you're right, this is the moment to remove that mention.
| // saved thread state is never restored: ROOT never finalizes the | ||
| // embedded interpreter. |
There was a problem hiding this comment.
// saved thread state is never restored: ROOT never finalizes the
// embedded interpreter.
Has this always been the case? What are the implications?
There was a problem hiding this comment.
This behavior dates from the thread-safety rework in 96fc4cc (Sept 2024). Before that, Initialize never released the GIL, which only worked because TPython use was usually single-threaded. This is why it's required: Initialize() runs implicitly on the first TPython call (e.g. CPPInstance_FromVoidPtr), and all API functions acquire/release the GIL via PyGILRAII, which expects the caller to not hold the GIL afterwards. If Initialize didn't release it, any second thread using TPython would deadlock in PyGILState_Ensure.
Note that on the long term (next release) I want to deprecate the implicit initialization and require the user to initialize the Python interpreter themselves. Then we avoid confusion from implicit initialization like this one. That would also deter analyzers from over-using TPython while leaving it accessible to framework experts like in ATLAS/Athena.
|
|
||
| // attach to the cppjit module to prevent the creation of a python proxy | ||
| // for the C++ proxy | ||
| if (PyObject *thisModule = cppjit::cpyrt::GetThisModule()) { |
There was a problem hiding this comment.
So, TPython also depends on cppjit's C++ API. Is this wanted also in the future?
There was a problem hiding this comment.
We don't know, that's a a separate discussion for another PR and the Tuesday meetings. I wanted to clearly separate the API usage that is just for Python interpreter embedding (independent from the rest of cppjit) from the API functions that actually use the cppjit type system. ALTAS uses the latter inside event loops in Athena, so this performance requirement makes is hard to not have a cppjit C++ API exposed via TPython....
| // attach to the cppjit module to prevent the creation of a python proxy | ||
| // for the C++ proxy |
There was a problem hiding this comment.
This sentence is not clear to me, can you elaborate?
| std::vector<std::string> args(argc); | ||
| for (int i = 0; i < argc; ++i) { | ||
| args[i] = argv[i]; | ||
| FILE *fp = fopen(name, "r"); |
There was a problem hiding this comment.
I don't see the corresponding fclose, maybe use an RAII struct to avoid confusion
There was a problem hiding this comment.
Right, I now see the comment at line 401, that's just confusing.
cppjit is a Python extension library, not an embedding library. Remove
the PyConfig/Py_InitializeFromConfig fallback from cpyrt's private
Initialize() and let it fail gracefully when the interpreter has not
been initialized by the embedding application. TPython::Initialize()
now owns interpreter embedding for the Cling-first case, replacing the
cpyrt::Scope_Check(nullptr) side-effect hack.
Along the same boundary, move the 'access to the python interpreter'
functions out of cpyrt entirely, since their only consumer is TPython
and they use none of cppjit's proxy machinery:
TPython::ExecScript and TPython::Prompt. This also removes the
duplicated static gMainDict in API.cxx; TPython now consistently
uses its own.
stops leaking the module reference. The attach of the imported
module to the cppjit module (to prevent a proxy of the C++ proxy)
goes through a new small exported getter, cpyrt::GetThisModule().
The dead class-iteration loop (which computed a class name that was
never used) is dropped; namespace/class creation already happens on
the TPython side via TClass::GetClass.
Collateral changes:
preamble, which previously arrived transitively through cpyrt/API.h.
removed cpyrt::Exec API.
🤖 Done with the help of AI