Summary
Adding a MeshVariable replaces mesh.dm with a new object and destroys the
previous DM, while Python may still hold a reference to it. Any use of the old
handle is a use-after-free and segfaults (SIGSEGV, exit 139).
This makes a natural pattern unsafe:
dm = mesh.dm # capture
v = uw.discretisation.MeshVariable("v", mesh, mesh.dim, degree=2)
p = uw.discretisation.MeshVariable("p", mesh, 1, degree=1)
dm.getDimension() # SIGSEGV
There is no warning and no error — the process dies. It is easy to mistake for a
fault in whatever ran in between (in my case a Stokes solve with a P0 pressure,
which was entirely innocent).
Minimal reproducer
42 cells, no solver, no adaptation:
import underworld3 as uw
mesh = uw.meshing.UnstructuredSimplexBox(
minCoords=(0.0, 0.0), maxCoords=(1.0, 1.0), cellSize=0.3,
regular=False, qdegree=2)
held = mesh.dm
print("held:", held.getHeightStratum(0)[1]) # 42
v = uw.discretisation.MeshVariable("v1", mesh, mesh.dim, degree=2)
print("after v:", mesh.dm is held, held.getDimension()) # True 2 -- fine
p = uw.discretisation.MeshVariable("p1", mesh, 1, degree=1)
print("after p:", mesh.dm is held) # False
print(held.getDimension()) # SIGSEGV
Output:
held a handle; cells = 42
after nothing mesh.dm is held -> True
held.getDimension() = 2
after creating a P2 vector variable mesh.dm is held -> True
held.getDimension() = 2
after creating a P1 scalar variable mesh.dm is held -> False
the held handle is now a DIFFERENT object; using it...
<segmentation fault>
Note the trigger is the second variable — the first reuses the DM, the one
that changes the field layout rebuilds it.
Why this is a defect rather than a documentation matter
petsc4py objects are reference counted. Replacing mesh.dm is reasonable; what
is not is destroying the old DM while a Python reference to it is alive. The old
handle should stay valid (merely stale) until the last reference goes away, so
the failure mode is at worst a wrong answer from an out-of-date object, never a
crash.
Two possible fixes, in preference order:
- Do not destroy the old DM explicitly — let refcounting collect it. Then a held
handle stays safe.
- If it must be destroyed, keep the mesh's own reference and have the rebuild
mutate in place rather than swap the object.
Environment
feature/mesh-reconnection, amr-dev env, petsc4py from the shared PETSc build,
macOS. Reproduces on a plain UnstructuredSimplexBox with no adaptation, no cut
and no solver, so it is not specific to any of those.
Workaround
Always read mesh.dm at the point of use; never cache it across MeshVariable
creation.
Summary
Adding a
MeshVariablereplacesmesh.dmwith a new object and destroys theprevious DM, while Python may still hold a reference to it. Any use of the old
handle is a use-after-free and segfaults (SIGSEGV, exit 139).
This makes a natural pattern unsafe:
There is no warning and no error — the process dies. It is easy to mistake for a
fault in whatever ran in between (in my case a Stokes solve with a P0 pressure,
which was entirely innocent).
Minimal reproducer
42 cells, no solver, no adaptation:
Output:
Note the trigger is the second variable — the first reuses the DM, the one
that changes the field layout rebuilds it.
Why this is a defect rather than a documentation matter
petsc4py objects are reference counted. Replacing
mesh.dmis reasonable; whatis not is destroying the old DM while a Python reference to it is alive. The old
handle should stay valid (merely stale) until the last reference goes away, so
the failure mode is at worst a wrong answer from an out-of-date object, never a
crash.
Two possible fixes, in preference order:
handle stays safe.
mutate in place rather than swap the object.
Environment
feature/mesh-reconnection,amr-devenv, petsc4py from the shared PETSc build,macOS. Reproduces on a plain
UnstructuredSimplexBoxwith no adaptation, no cutand no solver, so it is not specific to any of those.
Workaround
Always read
mesh.dmat the point of use; never cache it across MeshVariablecreation.