Leave PETSc initialisable after importing petsctools - #50
Conversation
`options.py` needs `PETSc.Options` at import time, to subclass it, but must
not initialise PETSc to get it, so it calls `petsc4py.lib.ImportPETSc`. That
registers the extension module under the name of the `petsc4py/PETSc.py`
shim it shadows, both in `sys.modules` and as an attribute of the `petsc4py`
package. The shim is the only thing that ever calls `PETSc._initialize`, so
once it is shadowed it never runs again: every later `from petsc4py import
PETSc` in the process hands back a PETSc that nothing has initialised.
Anything that then reaches into PETSc dereferences a null pointer. In
Firedrake's `tests/tsfc` session, which imports `petsctools` through
`finat.citations` and never calls `petsctools.init`, every worker segfaults
at its first form compilation:
petsctools/citation.py:58 in cite -> PETSc.Sys.registerCitation
libpetsc.so.3.025 PetscSegBufferGet+0x1c <- PetscCitationsList is NULL
Put the import machinery back as it was found, so that the shim still runs
for whoever imports `petsc4py.PETSc` next. The module object is unchanged:
CPython keeps its own cache of single-phase extension modules, so the later
import returns the same object and only adds the initialisation.
This was found with the assistance of Claude Code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
This is actually a really exciting thing to learn. Corintis really want a way to not initialise MPI at import, and this looks like that might actually be possible via this mechanism. Can you not just run |
Are you saying that the mechanism here is useful for another purpose?
But in your next question you suggest to change mechanism. Could you be more precise? |
|
Claude says that changing back to init "fixes one caller but leaves the trap" and that the PR as is "preserves the deferral mechanism you are interested in rather than undoing it" |
As in, what petsctools now does in
No. I am suggesting you add
Sure, but I think that this "trap" might one day be desirable. I am open to the idea of merging this PR, but I just want to think through the implications. |
connorjward
left a comment
There was a problem hiding this comment.
This is a hack but I don't mind adding it for now. CI working is quite important.
options.pyneedsPETSc.Optionsat import time, to subclass it, but must notinitialise PETSc to get it, so #41 reached for
petsc4py.lib.ImportPETSc:The intent is right, but
ImportPETScregisters the extension module underpetsc4py.PETSc— the name of thepetsc4py/PETSc.pyshim it shadows —both in
sys.modulesand as an attribute of thepetsc4pypackage. That shimis the only thing that ever calls
PETSc._initialize, so once it is shadowed itnever runs again, and every later
from petsc4py import PETScin the processhands back a PETSc that nothing has initialised.
petsctools.initis unaffected, because it initialises explicitly right afterits own
ImportPETSc. Anything that only importspetsctoolsis not.How it shows up
Firedrake's
tests/tsfcsession importspetsctoolsthroughfinat.citationsand never callspetsctools.init. Since #41 every one of itsxdist workers segfaults at its first form compilation:
PetscCitationsListis created byPetscInitialize, so an uninitialised PETScmakes
registerCitationdereference a null pointer. The chain that gets there:That happens before
tsfc.femimports pyop2, so pyop2'sfrom petsc4py.PETSc import IntTypesilently reuses the uninitialised moduleand nothing in the process ever initialises PETSc.
The fix
Put the import machinery back as it was found, so the shim still runs for
whoever imports
petsc4py.PETScnext. Both registrations have to be undone:dropping only the
sys.modulesentry is not enough, becausefrom petsc4py import PETScshort-circuits on the parent package attribute, which theextension's own initialisation sets.
The module object is unchanged by this: CPython keeps its own cache of
single-phase extension modules, so the later import returns the same object and
only adds the initialisation.
Testing
tests/test_options.pygains a regression test, in a subprocess since PETSc isalready initialised in the test session. Before this change it fails on
assert PETSc.Sys.isInitialized(); after it, the full suite is27 passed, 1 skipped, andruff checkis clean.🤖 Generated with Claude Code