Swarm/mesh checkpoint quiescence, .msh coordinate-system default, rank-symmetric stats temporaries#401
Conversation
Swarm.save and SwarmVariable.save appended rank-0 metadata after the data write and returned immediately on the other ranks — a caller that reopened the file straight away (read_timestep after write_timestep) hit HDF5 file locking (BlockingIOError errno 35), and the resulting rank divergence hung the next collective. A trailing barrier in both methods makes the return collective. Verification: tests/test_0003_save_load.py swarm tests — previously 1 failed + 1 hung at np2 (240s mpirun timeout) — now 10/10 pass at np2 and np4 in ~5s. The write_timestep xdmf block after the saves only re-opens the files read-only on rank 0, which does not conflict. Closes #330 Underworld development team with AI support from Claude Code
…ven (#397) A mesh loaded directly from a .msh file (or an h5 checkpoint without coordinate metadata) left CoordinateSystemType=None, and mesh.write() crashed in its metadata block. The default now matches every uw.meshing constructor and CoordinateSystem's own signature default; nothing anywhere used None as a sentinel (checked repo-wide). Regression test: test_0003::test_mesh_from_msh_file_writes. Closes #397 Underworld development team with AI support from Claude Code
…_tensor_stats (#384) The temporary magnitude/Frobenius variables were named with id(self) — a rank-local memory address, so the names differed across ranks while variable creation/destruction performs collective DM operations keyed by field name. The suffix is now the parent variable's clean_name, which is identical on every rank (and the finally-block cleanup already bounds its lifetime). Verification: tests/parallel/test_0750_global_statistics.py — 12 passed at np2 and np4. Closes #384 Underworld development team with AI support from Claude Code
…399 workaround) - New test: a mesh loaded from a .msh file must write (issue #397). - test_write_timestep_missing_directory_raises used a 2x2 mesh, which starves ranks of cells at np4 and crashes in mesh construction — the empty-rank navigation kd-tree bug now tracked as issue #399. A 4x4 mesh keeps the test cheap and every rank populated, letting the whole file run under MPI (first time test_0003 is np2/np4-clean end to end). Underworld development team with AI support from Claude Code
…coverage, honest #397 default) Responses to the public adversarial review of this branch: - _vector_stats/_tensor_stats refuse a pre-existing '_temp_*' variable name instead of silently aliasing it (creation returns the registered object on a name collision; the stats pass would then overwrite the user's data and deregister their variable — demonstrated live by the review). - The #330 quiescence barrier extends to the same rank-0 metadata-append-after-collective-write pattern in MeshVariable.save and mesh.write; both metadata blocks now use context managers so an exception cannot leak the HDF5 handle (mesh.write previously never closed it on the exception path). - Loading an h5 checkpoint with no coordinate_system_type metadata warns on rank 0 before defaulting to CARTESIAN: pre-#397 the lost metadata crashed write() (a loud tripwire); the default must not launder a wrong label into re-written files silently. The review also confirmed all four primary fixes against rank-asymmetric control flow, barrier/close ordering, zero-particle ranks and both I/O branches at np4, and surfaced the pre-existing tensor-stats indexing defect now tracked as #400. Underworld development team with AI support from Claude Code
Adversarial review — round 1 (public record)Semi-independent skeptical pass over the code changes, instructed to refute/rebut each fix. Live probes ran at np4 in the branch worktree environment (both the mpio and force_sequential I/O branches, zero-particle ranks, and back-to-back write/read cycles). Attack 1 — #330 barriers: can any rank miss them (new deadlock)?HELD. Every exit path in both save methods was traced: one return each, after the barrier; the only raise (the .h5 suffix check) is argument-driven and rank-symmetric for every in-repo caller. Branch selection (mpio vs sequential) depends only on a build constant and a same-valued argument, so no rank can diverge. Both paths were already unconditionally collective before this change (allgather / collective create / barrier loop), so the added barrier introduces no new collectivity requirement. Verified live at np4 with zero-particle ranks and three consecutive write/read cycles on both branches: no hang, no BlockingIOError. Attack 2 — barrier vs h5py close orderingHELD. The rank-0 metadata append is inside a context manager, closed strictly before the barrier; non-zero ranks cannot pass until rank 0 arrives, i.e. after close. Attack 3 — #384 clean_name collisions / cleanup lifecycleHELD for the fix — but the review DEMONSTRATED a new corruption surface: a user variable literally named Attack 4 — #397 None→CARTESIAN semantic driftHELD. All construction sites grep-verified: every uw.meshing constructor passes an explicit enum; adapt/submesh children inherit the parent's. Pre-fix, Attack 5 — the 2x2→4x4 test mesh changeHELD. The guarded behaviour (missing-directory raise, before any collective) is resolution-independent. The #399 justification verified live: elementRes=(2,2) at np4 deadlocks mesh construction on unmodified development. Findings outside the diff
Response commita559e47 carries all three fixes; the level_1+tier_a gate (442 passed) and the np2/np4 test batteries were re-run after it. Underworld development team with AI support from Claude Code |
Three fixes from the triage backlog, plus an adversarial-review round and its responses. Closes #330, closes #397, closes #384.
The fixes
#330 — save() races its own metadata append (
fix(swarm)+ review follow-up)Swarm.saveandSwarmVariable.saveappended rank-0 metadata after the data write and returned immediately on the other ranks; an immediate reopen (read_timestep after write_timestep) hit HDF5 file locking (BlockingIOError errno 35) and the rank divergence hung the next collective. Both methods now end with a barrier so the file is quiescent on return. The review found the identical pattern inMeshVariable.saveandmesh.write— both now carry the same barrier, and both metadata blocks use context managers so an exception cannot leak the HDF5 handle.tests/test_0003_save_load.py— previously 1 failure + a hard hang at np2 (the swarm tests were never run under MPI by any gate) — now passes 10/10 at np2 and np4 in ~5 s. This unmasked a further pre-existing crash (empty-rank navigation kd-tree, tiny meshes at np4), filed as #399 with a live reproducer; the affected test now uses a 4x4 mesh so the whole file stays MPI-clean.#397 — mesh loaded from a .msh file couldn't be written
CoordinateSystemTypestayedNoneandmesh.write()crashed in its metadata block. The construction now defaults to CARTESIAN — matching everyuw.meshingconstructor, and matching whatCoordinateSystemalready did at runtime (a None-typed mesh was always functionally Cartesian; the review verified no consumer anywhere treats None specially). Per the review: loading an h5 checkpoint that has lost its coordinate metadata now warns before defaulting, so a wrong label cannot launder itself silently into re-written files (pre-fix, the crash was the tripwire).#384 — rank-asymmetric temporary names in vector/tensor stats
The stats temporaries were named with
id(self)— a rank-local address — while variable creation/destruction runs collective DM operations keyed by field name. The suffix is now the parent'sclean_name(identical on every rank). Per the review: both sites now refuse a pre-existing temp name outright — a collision would silently alias, overwrite, and deregister a user's variable (demonstrated live in the review round).Adversarial review (public, round 1)
Posted in full as the first comment below. Summary: all four primary fixes held against rank-asymmetric control-flow tracing, barrier/file-close ordering, zero-particle ranks, and live np4 probes of both I/O branches with back-to-back write/read cycles. It caught three things that are fixed in the response commit (temp-name aliasing, the wider #330 pattern, the silent-default loudness), and one pre-existing defect outside the diff, filed as #400 (
_tensor_statsindexes the flat component count against the structured array axis — crashes for any true tensor variable).Verification
test_0003_save_load.py: 10/10 at serial, np2, np4 (first time this file is MPI-clean end to end).tests/parallel/test_0750_global_statistics.py: 12 passed at np2 and np4.Reminder for the merge: manual close needed for #330, #397, #384 (PRs target development; auto-close does not fire).
Underworld development team with AI support from Claude Code