Skip to content

Allow multiphase modules to be re-imported #5782

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Aug 3, 2025

Conversation

b-pass
Copy link
Contributor

@b-pass b-pass commented Aug 1, 2025

Description

This fixes the exception reported in #5780 and gets things close to the behavior of single-phase module initialization. When import is done on a single-phase module after a del sys.modules[...], the single-phase module is completely reloaded and reinitialized.

I could not get CPython will not do this to multi-phase modules (I tried a few different things).. Instead, this PR keeps a cache of fully created and initialized modules (in the interpreter state dict). When creating a module, we look in the cache first and use the already created and initialized one if it is in there. Otherwise we create a new one. And then also before running the user's exec function we check in the cache.

So, with this PR, the difference is that single-phase modules are reinitialized after a del, and multi-phase modules silently return a cached handle to the previously initialized instance. I think this difference is probably OK because doing a del sys.modules[...] is probably not very common, and even in this case it was done as part of a test setup.

Suggested changelog entry:

  • Fixed issue that caused PYBIND11_MODULE code to run again if the module was re-imported after being deleted from sys.modules.

b-pass added 6 commits July 31, 2025 20:57
- When a module is successfully imported and exec'd, save its handle in a dict in the interpreter state
- Use a special Py_mod_create slot to look in the cache and return the cached handle if it is in the cache
- Don't re-run the user exec function if the module is in the interpreter's cache (implying it was already successfully imported)
Copy link
Collaborator

@rwgk rwgk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is my related ChatGPT conversation:

https://chatgpt.com/share/688c74a5-2e08-8008-9c30-daf546a2f9a8

I'm still pretty uncertain about the static inline question, even though ChatGPT strongly suggests keeping the static. We have many helper functions that are not static.

A Py_mod_create slot function which will return the previously created module from the cache if one
exists, and otherwise will create a new module object.
*/
static inline PyObject *cached_create_module(PyObject *spec, PyModuleDef *) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this needs to be extern "C", because the function pointer needs to have C linkage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it does, CPython doesn't care what the name of the symbol is only what the pointer is as supplied by the slot.

assert x is y


@pytest.mark.xfail("env.GRAALPY", reason="TODO should be fixed on GraalPy side")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@msimacek for visibility

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case this TODO survives for some time, it'll be useful to have a pointer back here:

reason="TODO should be fixed on GraalPy side (see PR #5782)"

assert x is y


@pytest.mark.xfail("env.GRAALPY", reason="TODO should be fixed on GraalPy side")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case this TODO survives for some time, it'll be useful to have a pointer back here:

reason="TODO should be fixed on GraalPy side (see PR #5782)"

Copy link
Collaborator

@rwgk rwgk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@rwgk rwgk merged commit 0db3d59 into pybind:master Aug 3, 2025
82 checks passed
@github-actions github-actions bot added the needs changelog Possibly needs a changelog entry label Aug 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs changelog Possibly needs a changelog entry
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants