RFC: Implement Multi-Phase Module Initialization as per PEP 489#4162
RFC: Implement Multi-Phase Module Initialization as per PEP 489#4162
Conversation
The latest versions of pyO3 (a Python binding for Rust) explicitly added a check to detect multiple imports. In subinterpreter environments, this leads to an ImportError: "PyO3 modules may only be initialized once per interpreter process" (it has been recenlty replaced with a more specific: "PyO3 modules may only be initialized once per interpreter process". This is only a workaround while the root cause is fixed (see PyO3/pyo3#4162). Fixes: https://tracker.ceph.com/issues/64213 Signed-off-by: Ernesto Puerta <epuertat@redhat.com>
02eb845 to
909b49f
Compare
This module contains `ModuleState`, a struct used to represent state that lies on the per-module memory region of a Python module, as well as various exported functions that are used to integrate `ModuleState` with the Python interpreter. This commit does not implement any further functionality (such as actually representing any kind of state) and is instead more of a blank slate for further additions. Signed-off-by: Max R. Carrara <aequitosh@gmail.com>
These helpers and wrapper structs ensure that any slots and function pointers used during multi-phase initialization stay allocated throughout the remaining lifetime of the program. Signed-off-by: Max R. Carrara <aequitosh@gmail.com>
This commit adds experimental but fully functional support for multi-phase module initialization as specified in PEP 489. Note that this commit serves as a demonstration & basis for further improvements only; many tests have therefore not been adapted correspondingly yet. Signed-off-by: Max R. Carrara <aequitosh@gmail.com>
Signed-off-by: Max R. Carrara <aequitosh@gmail.com>
909b49f to
a1beab4
Compare
|
Upgraded this PR from a draft to an RFC. The description was also updated to reflect that. |
|
Just curious, any progress getting this reviewed? |
I'm planning to rebase this on main as soon as I got time; perhaps then someone's gonna have a look. |
|
Yes, I had a lot to think about with freethreading and also our changes for |
Sounds good! I've been quite busy myself, so it's all good; seems like the timing is just right. |
The latest versions of pyO3 (a Python binding for Rust) explicitly added a check to detect multiple imports. In subinterpreter environments, this leads to an ImportError: "PyO3 modules may only be initialized once per interpreter process" (it has been recenlty replaced with a more specific: "PyO3 modules may only be initialized once per interpreter process". This is only a workaround while the root cause is fixed (see PyO3/pyo3#4162). Fixes: https://tracker.ceph.com/issues/64213 Signed-off-by: Ernesto Puerta <epuertat@redhat.com>
|
Hi @Aequitosh. Thank you for working on this issue. How can I help to get this forward? |
|
@Aequitosh I'm just curious, as a workaround, would cleaning-up/freeing a PyO3 module ( Something like?: from cryptography.fernet import Fernet # a version that relies on this version of PyO3
key = Fernet.generate_key()
del cryptography
del sys.modules['cryptography']
gc.collect()If that fully removes all module state, we could have a context manager like this in sub-interpreters: with _import('cryptography.fernet') as Fernet:
key = Fernet.generate_key()What do you think? |
Hey @Sunnatillo, thank you for offering your help! I'm actually intending on rebasing my branch on I really wanna get this ball rolling again, so I'm grateful for any help.
Hi @epuertat! This unfortunately won't resolve things, because PyO3 modules are (as of time of writing) loaded using single-phase initialisation. The callbacks you mentioned are currently not used; there's no actual module state being managed at the moment, because native modules based on PyO3 are "global" (due to the fact of them using the older single-phase init mechanism). Glossing over some details here, but that's essentially what the situation is right now. This PR aims to replace the single-phase init with the newer multi-phase initialisation, which would then (almost) automatically make PyO3-based Python modules sound when used with sub-interpreters. The remaining static data that PyO3 stores would also need to be moved to the per-module memory location in So, unfortunately, your solution most likely won't work (or rather, it won't be safe). Relevant reads, if you're curious: With all that being said, expect some updates / changes to trickle in (hopefully) soon. |
|
Thanks a lot @Aequitosh for your quick & thorough reply! |
The latest versions of pyO3 (a Python binding for Rust) explicitly added a check to detect multiple imports. In subinterpreter environments, this leads to an ImportError: "PyO3 modules may only be initialized once per interpreter process" (it has been recenlty replaced with a more specific: "PyO3 modules may only be initialized once per interpreter process". This is only a workaround while the root cause is fixed (see PyO3/pyo3#4162). Fixes: https://tracker.ceph.com/issues/64213 Signed-off-by: Ernesto Puerta <epuertat@redhat.com>
The latest versions of pyO3 (a Python binding for Rust) explicitly added a check to detect multiple imports. In subinterpreter environments, this leads to an ImportError: "PyO3 modules may only be initialized once per interpreter process" (it has been recenlty replaced with a more specific: "PyO3 modules may only be initialized once per interpreter process". This is only a workaround while the root cause is fixed (see PyO3/pyo3#4162). Fixes: https://tracker.ceph.com/issues/64213 Signed-off-by: Ernesto Puerta <epuertat@redhat.com>
The latest versions of pyO3 (a Python binding for Rust) explicitly added a check to detect multiple imports. In subinterpreter environments, this leads to an ImportError: "PyO3 modules may only be initialized once per interpreter process" (it has been recenlty replaced with a more specific: "PyO3 modules may only be initialized once per interpreter process". This is only a workaround while the root cause is fixed (see PyO3/pyo3#4162). Fixes: https://tracker.ceph.com/issues/64213 Signed-off-by: Ernesto Puerta <epuertat@redhat.com>
The latest versions of pyO3 (a Python binding for Rust) explicitly added a check to detect multiple imports. In subinterpreter environments, this leads to an ImportError: "PyO3 modules may only be initialized once per interpreter process" (it has been recenlty replaced with a more specific: "PyO3 modules may only be initialized once per interpreter process". This is only a workaround while the root cause is fixed (see PyO3/pyo3#4162). Fixes: https://tracker.ceph.com/issues/64213 Signed-off-by: Ernesto Puerta <epuertat@redhat.com>
|
I'm very interested in this because NumPy eventually needs to make a similar transition to multi-phase init and the whole thing is very confusing to me. I'd also like to update the CPython docs to use multi-phase init in the C extension tutorial: https://docs.python.org/3/extending/extending.html |
|
@Aequitosh Are you still interested to continue this work? |
RFC: Multi-Phase Module Initialization as per PEP 489
This PR implements multi-phase initialization in PyO3 as a drop-in replacement for single phase initialization. While it still needs some polish here and there, the implementation is mostly complete.
What's still left to do:
m_slotsor perhaps an API around creatingPyModuleDefsm_slotsat runtime.Py<PyModule>from a*mut ffi::PyModuleDef#ident(#(#module_args),*)in proc macros) failsPyModule::new_boundPlease let me know what you think! I'm grateful for any feedback! :)
Specifically, I'd be very happy to receive insight / comments on the TODOs I've added here and there.