Skip to content

Fix MPI hang/abort on UNK read failure in plot_wannier - #647

Merged
JeromeCCP9 merged 4 commits into
wannier-developers:developfrom
elinscott:fix/plot-unk-error-deadlock
Jul 17, 2026
Merged

Fix MPI hang/abort on UNK read failure in plot_wannier#647
JeromeCCP9 merged 4 commits into
wannier-developers:developfrom
elinscott:fix/plot-unk-error-deadlock

Conversation

@elinscott

@elinscott elinscott commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Symptom

In wannier_plot, a missing, truncated, or corrupted UNK file kills the run with a raw Fortran runtime error and no wannier90 error message. A missing UNK file is silently created empty in the process.

This can be reproduced using the content of testw90_cube_format: delete UNK00005.1 and run. The code writes a 0-byte UNK00005.1, then dies with Fortran runtime error: End of file and a backtrace.

Cause

The UNK opens in plot_wannier had no status='old', so opening a missing file created it. None of the header/wavefunction reads had iostat guards, so a malformed file caused an ugly abort.

Earlier misdiagnosis

Earlier commits and review discussion wrongly attributed this failure to mismatched MPI collectives. However, the comms/error layer already handles a lone failing rank by design. The final commit reverts the error plumbing to the standard "return" idiom.

Changes

  • All UNK opens now have status='old' plus an inquire existence check, so a missing file produces a proper error
  • All UNK reads carry iostat and report failures through the standard set_error_file mechanism with an immediate return upon an error
  • The per-k-point read sequence is factored into a helper
  • the previously duplicated disentangled/direct and spinor read paths are unified
  • The UNK00001 grid-dimension probe at the top of plot_wannier gets the same status='old' & iostat checks (a truncated UNK00001 also previously produced a raw abort)

@JeromeCCP9

Copy link
Copy Markdown
Collaborator

This is nice! May I modify it slightly to put the set_error_* calls closer to the error condition along with immediate returns (instead of waiting until the end of the subroutine); that makes the code a little simpler and matches the way we check errors elsewhere (eg in deallocs, etc)?

@elinscott

Copy link
Copy Markdown
Contributor Author

This is nice! May I modify it slightly to put the set_error_* calls closer to the error condition along with immediate returns (instead of waiting until the end of the subroutine); that makes the code a little simpler and matches the way we check errors elsewhere (eg in deallocs, etc)?

I've tweaked, it, as far as MPI allows, but the return itself can't be moved. Take mpirun -np 2 with 8 k-points, where rank 1 owns UNK00005.1, and that file is missing. Currently, set_error_file contains an mpi_allreduce (via comms_sync_error), so if rank 1 called it and returned at the failure site, rank 1 would sit in that allreduce while rank 0 - which saw nothing wrong - proceeds to the comms_reduce of wann_func and hangs. To avoid this, we need to wait until all ranks meet at a single comms_sync_error.

Cf. the error checks for deallocate you mentioned: these can do an immediate return because all ranks run the same deallocate, so in practice* either all or no ranks enter the sync inside set_error_dealloc.

I was able to tidy things a little using set_base_error, but I now realise that this would be the first use of this funciton outside of comms.F90 — possibly something you might want to avoid?

*a deallocate really only fails on programming errors such as deallocating an array that was never allocated, and so all ranks hit the same bug

@elinscott

elinscott commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

... actually, thinking about it a bit more this what we need is a helper read_unk function within the kpt loop. Then things become a lot neater. Let me implement that...

@elinscott

Copy link
Copy Markdown
Contributor Author

... done!

@JeromeCCP9

JeromeCCP9 commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

I think it would be ok to 'return' from within the k-point loop, no? (It's just a small question, I think we can merge this already.)

@elinscott

elinscott commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

No — if failing ranks return from within the k-point loop and succeeding ones do not, a combination of failing and succeeding ranks would encounter different comms routines (which was the reason for the original ugly crash).

That said, the refactor into a helper routine achieves the effect that you're after with an in-loop return: inside the helper each failure returns immediately, rather than having ierr checks everywhere.

@JeromeCCP9

Copy link
Copy Markdown
Collaborator

We designed the comms/error such that this would exactly not be a problem--the next collective operation (regardless of what it is!) is when the error status is propagated. If what you write is true, that means there is a larger problem; may I ask, did you test it, actually? May I test it?
J.

@JeromeCCP9
JeromeCCP9 force-pushed the fix/plot-unk-error-deadlock branch from 103892b to d4222d5 Compare July 16, 2026 20:42
@elinscott

Copy link
Copy Markdown
Contributor Author

@JeromeCCP9 ah, you're right, sorry! Having another look at it, the real issue was the lack of a status=old and iostat checks throughout the UNK reading. These were added at the same time as me trying to catch all the branches with the same comms_sync_error, so when the problem vanished during my testing I misattributed it to me routing every rank through the same comms call.

I have updated the PR description — have a read for more details (including how to reproduce).

I have also updated the PR content so that it uses the immediate return as you suggested and the error handling remains clean.

Thanks for the catch!

@JeromeCCP9 JeromeCCP9 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Adds error checking/handling (using the current design) for reading of UNK files.

Slightly verbose because the file read is encapsulated in a new function, which counts as an improvement.

Comments are in the "claude fable5 style", but all otherwise seems fine.

(Unfortunately, this functionality is not explicitly tested in the test suite right now.)

elinscott and others added 4 commits July 17, 2026 13:06
Symptom: with MPI and wannier_plot enabled, a missing, unreadable, or
header-mismatched UNK file for one k-point could hang or abort the run
with no clean error message, instead of reporting the offending file.

Cause: k-points are distributed over ranks (dist_k), so each rank reads
only its own UNK files. Two rank-local failure sites inside the read
loop were mishandled:
  1. A header mismatch called set_error_file directly. That routine syncs
     the error across the communicator (comms_sync_error -> mpi_allreduce).
     When only the owning rank failed, it entered the allreduce while the
     other ranks proceeded to the comms_reduce of wann_func: mismatched
     collectives, i.e. a deadlock (observed as an abnormal termination on
     OpenMPI, an indefinite hang on stricter MPI stacks).
  2. A missing file was not detected: open without status='old' recreated
     it empty and the subsequent unguarded read aborted with a raw EOF
     runtime error and no W90 message.

Fix: detect missing files (inquire + status='old'), add iostat to the
open and all UNK reads, and record any rank-local failure in a local
status flag instead of erroring mid-loop. After the loop every rank
synchronises together: failing ranks call set_error_file (naming the
file), succeeding ranks call comms_sync_error(comm, error, 0), so the
collective is matched and all ranks return before comms_reduce. This is
the same reduce-then-set idiom already used elsewhere (e.g. the
unlucky_rank path in wannier_prog.F90 and write_kmesh in library_extra.F90).

Repro (test-suite testw90_cube_format, 8 k-points, formatted UNK):
  mpirun -np 2 wannier90.x gaas   # rank 1 owns k=5-8
deleting or corrupting UNK00005.1 previously hung/aborted; it now exits
nonzero within seconds with "plot_wannier: file UNK00005.1 not found
(rank: 1)". Control runs (all files present) are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review follow-up: record each rank-local UNK read failure directly at
the failure condition with the non-collective set_base_error (the same
primitive the comms module uses in its rank-divergent paths), instead
of carrying ierr_read/errmsg_read flags to the end of the loop. All
ranks still meet at a single comms_sync_error before the comms_reduce;
an immediate collective set_error_file at the site would recreate the
mismatched-collective hang this branch fixes.

Re-verified (gfortran-12, OpenMPI): np=2 control byte-identical to
serial modulo timestamp; missing UNK at np=2 and truncated UNK at np=4
exit with the proper werr message and no empty file created.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract the inquire/open/header-check/band-read sequence into a private
helper. Because the helper is purely rank-local, every failure site can
now set the error (set_base_error) and return immediately; the caller
exits the k-point loop on an allocated error and the single collective
comms_sync_error after the loop is unchanged.

The disentangled and direct read paths collapse into one loop (the
direct case is inc_band all-true with num_inc = num_bands), and an
internal plot_read_unk_band handles one band's grid sweep, removing the
eight near-identical read blocks and their iostat cascades.

Also add status='old'/iostat to the UNK00001 grid-dimension probe, which
previously aborted with a raw EOF backtrace on a truncated file; that
path is executed by all ranks, so the collective set_error_file is safe
there.

Verified (gfortran-12, OpenMPI): np=2 control cube byte-identical to
serial; missing UNK00005.1 (np=2), truncated body (np=4), truncated
header (np=2) and truncated UNK00001 (np=2) all exit cleanly with the
proper werr message and no empty file created.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review follow-up correcting an earlier misdiagnosis. The comms layer is
two-tier: every synchronised collective (comms_reduce etc.) opens with
the same one-integer comms_sync_error handshake that set_error_* ends
with, so a single rank erroring into set_error_file is matched by the
healthy ranks' next collective and the error propagates by design -
there is no mismatched-collective deadlock, as verified by driving a
UNK header mismatch on one rank through the pre-fix code (clean exit,
correct message). What the pre-fix code actually suffered from was
rank death before any error call: a missing UNK file was silently
created empty by the unguarded open and the subsequent read aborted
with a raw Fortran EOF runtime error.

Accordingly, drop the manual comms_sync_error after the k-point loop
and the set_base_error bookkeeping: each failure site in plot_read_unk
now calls the ordinary collective set_error_file and returns
immediately, and the caller simply returns on an allocated error.

Verified (OpenMPI, gfortran-12): np=2 control byte-identical to serial;
missing UNK, truncated UNK (np=4), header mismatch, and two ranks
failing simultaneously all exit cleanly with the proper werr message
and no empty file created.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JeromeCCP9
JeromeCCP9 force-pushed the fix/plot-unk-error-deadlock branch from d4222d5 to 4b65dcd Compare July 17, 2026 12:06
@JeromeCCP9
JeromeCCP9 merged commit 1ca67a1 into wannier-developers:develop Jul 17, 2026
11 checks passed
@elinscott
elinscott deleted the fix/plot-unk-error-deadlock branch July 17, 2026 12:21
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.

3 participants