Skip to content

Forward the location policy on the empty-rank path (#611) - #656

Merged
lmoresi merged 1 commit into
developmentfrom
bugfix/global-evaluate-empty-rank-deadlock
Aug 28, 2026
Merged

Forward the location policy on the empty-rank path (#611)#656
lmoresi merged 1 commit into
developmentfrom
bugfix/global-evaluate-empty-rank-deadlock

Conversation

@lmoresi

@lmoresi lmoresi commented Aug 27, 2026

Copy link
Copy Markdown
Member

Fixes #611. global_evaluate deadlocked whenever a query set left some rank with no points — 900 s with no progress on a 300-point query at np=4, while passing at np=2.

What it actually was

Native stacks during the hang name both halves:

3 ranks   DMSwarmMigrate -> DMSwarmDataExCreate -> MPI_Comm_dup
1 rank    DMLocatePoints -> DMGetBoundingBox    -> MPI_Allreduce

A mismatched collective — every rank inside MPI, none in locator code. So neither slowness (ruled out: 300 points, 900 s) nor a location failure.

The cell-location policy (mesh._hint_is_authoritative) decides whether the barycentric hint may bypass PETSc's DMLocatePoints, which is collective on the mesh DM communicator. The policy is a mesh capability, and instrumenting confirmed it agrees on every rank (auth=True on all four).

It was discarded in transit. create_structure had no hint array to pass when a rank held zero points, and passed hintAuthoritative = 0 hardcoded beside the NULL:

ierr = DMInterpolationSetUp_UW(self._ipInfo, dm, 0, 1, NULL, 0)

so that rank alone entered the collective. Forwarding the caller's policy is the fix; petsc_tools.c additionally accepts a NULL hint when N == 0, since bypassing is trivially correct with nothing to locate.

Why np=2 hid it

The test biases every point into x > 0.5. At np=4 one rank owns solely x < 0.5 and receives 0 of 300 points — and that is precisely the rank the watchdog roll call singled out. At np=2 the coarser partition leaves both ranks straddling the split, so neither is empty.

Two wrong turns, reverted rather than shipped

Recorded because both look plausible:

  • Pre-touching mesh.dm.getBoundingBox() — useless. PETSc does not cache it, so the later call inside the locator reduces again.
  • Making the DMInterpolation cache decision unanimous — also useless. Tracing showed all four ranks MISS the cache, so it was never the divergence. This one I would have shipped on reasoning alone; the trace is what stopped it.

A docstring worth correcting separately

_location_capability says it is "deliberately NOT reduced across ranks: the evaluator runs on COMM_SELF [...] petsc_interpolate is reached only by ranks holding points". Both halves are falseDMLocatePoints(dm, ...) uses the mesh DM's communicator, and a trace shows every rank entering petsc_interpolate, including one with zero points. The conclusion (don't reduce the policy) is right, but for the wrong reason — and the wrong reason is what made the hardcoded 0 look harmless. Not changed here to keep the diff to the defect.

Tests

New test_1076_global_evaluate_empty_rank.py: a query biased so some rank is empty, plus an unbiased control that passed before the fix — a failure there means the ordinary path broke rather than the empty-rank path being repaired.

  • new regression: green at np=1, 2, 4
  • test_0760 (previously hung at np=4): green at np=1, 2, 4
  • test_0750, test_0755, test_1064 at np=2: green
  • level_1 and tier_a serial: 1074 passed, 0 failed

Underworld development team with AI support from Claude Code

`global_evaluate` deadlocked whenever a query set left some rank with no
points. At np=4 the migration test hung indefinitely -- 900 s with no progress
on a 300-point query -- while passing at np=2.

Native stacks during the hang name both halves:

    3 ranks   DMSwarmMigrate -> DMSwarmDataExCreate -> MPI_Comm_dup
    1 rank    DMLocatePoints -> DMGetBoundingBox    -> MPI_Allreduce

A mismatched collective, not slowness and not a location failure: every rank
was inside MPI, none in locator code.

The cell-location policy (`mesh._hint_is_authoritative`) decides whether the
barycentric hint may bypass PETSc's `DMLocatePoints`, which is COLLECTIVE on
the mesh DM communicator. The policy is a mesh capability, so all ranks agree
on it -- instrumenting confirmed `auth=True` on all four.

It was then discarded in transit. `create_structure` had no hint array to pass
when a rank held zero points, and passed `hintAuthoritative = 0` HARDCODED
beside the NULL:

    ierr = DMInterpolationSetUp_UW(self._ipInfo, dm, 0, 1, NULL, 0)

so that rank alone took the DMLocatePoints branch. Forwarding the caller's
policy is the fix; `petsc_tools.c` additionally has to accept a NULL hint when
there are no points to hint at, since the bypass is trivially correct with
nothing to locate.

Why it hid at np=2: the test biases every point into x > 0.5, and at np=4 one
rank owns solely x < 0.5 and so receives nothing, where at np=2 the coarser
partition leaves both ranks straddling the split. Measured ownership at np=4:
rank 1 gets 0 of the 300 points, and rank 1 is the rank the watchdog roll call
singled out.

Two wrong turns are worth recording so they are not retried. A pre-touch of
`mesh.dm.getBoundingBox()` does nothing -- PETSc does not cache it, so the
later call inside the locator reduces again. And forcing the DMInterpolation
cache decision to be unanimous does nothing here either: instrumenting showed
all four ranks MISS the cache, so it was never the divergence. Both were
reverted rather than shipped.

The docstring on `_location_capability` states the assumption that made this
invisible: "deliberately NOT reduced across ranks: the evaluator runs on
COMM_SELF [...] petsc_interpolate is reached only by ranks holding points".
Both halves are false -- `DMLocatePoints(dm, ...)` uses the mesh DM's
communicator, and a trace shows all ranks entering `petsc_interpolate`,
including one with zero points. The policy does not need reducing (it already
agrees); it needs to survive the call.

Regression covers a query biased so some rank is empty, plus an unbiased
control that passed before the fix -- so a failure there means the ordinary
path broke rather than the empty-rank path being repaired. Green at np=1, 2, 4,
as is the whole of test_0760 which previously hung at np=4.

Underworld development team with AI support from Claude Code
Copilot AI lite review requested due to automatic review settings August 27, 2026 04:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@lmoresi
lmoresi merged commit b431182 into development Aug 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants