Skip to content

Fix two restart-write defects around zsolars in radmod - #63

Open
Sohex wants to merge 1 commit into
alphaparrot:masterfrom
Sohex:fix-zsolars-restart-overread
Open

Fix two restart-write defects around zsolars in radmod#63
Sohex wants to merge 1 commit into
alphaparrot:masterfrom
Sohex:fix-zsolars-restart-overread

Conversation

@Sohex

@Sohex Sohex commented Aug 20, 2026

Copy link
Copy Markdown

Two restart-write defects in radmod.f90, both found while chasing something else. Neither affects the integration.

1. radstop writes 8190 elements of buffer for a 2-element array

zsolars is declared real :: zsolars(2) (radmod.f90:251), and radstop saves it with

call mpputgp('zsolars',zsolars,2,1)

mpputgp (mpimod.f90) declares its dummy p(kdim,klev), allocates a local z(NUGP,klev), gathers into it with mpgagp, and writes all of z:

real :: z(NUGP,klev)
call mpgagp(z,p,klev)
if (mypid == NROOT) call put_restart_array(yn,z,NUGP,NUGP,klev)

mpgagp moves NHOR elements per rank. At T42 on 16 ranks each rank reads 512 elements out of an array holding 2, and the record that reaches the file is NUGP = 8192 doubles: two solar constants and 8190 elements of gather buffer.

zsolars is a global pair, not a distributed gridpoint field, so it does not belong in a gather. solarini already uses the right idiom for this array, so the fix is to use it in radstop too.

This is normally invisible because -finit-real=zero is in the default flags and holds the buffer at zero. It stops being invisible under -flto, where the compiler may skip zeroing a local it can prove is never read: the restart then differs run to run from one binary on one input. That is how this was found. It matters for anyone hashing restarts to identify them.

2. solarini writes nine restart records to a unit nothing has opened

nwriunit is unit 34, and solarini's nine put_restart_array calls run at initialisation, before any restart file is open on it. Fortran connects fort.34 and writes them there, so every run directory gains a stray 432-byte file and none of the nine arrays reaches the restart.

They are the write half of a round trip whose read half is already commented out below, in the get_restart_array block under nstarfile > 0, where solarini was made unconditional in its place. These arrays are recomputed from the namelist at every start, so removing the orphaned writes matches the decision already made there.

Why this is safe

  • nothing reads zsolars back; the get_restart_array for it is part of the commented-out block
  • reseek locates records by name and skips data records whole, so a record changing size cannot dislodge another, and old and new restarts remain mutually readable

Verified

On a fork of this model at T42 L10 on 16 ranks, 600 steps from a common restart:

  • the zsolars record is 16 bytes where it was 65536, still carrying both solar constants
  • 397 of the restart's 398 records are byte-identical to the unfixed build; only the intended one moved
  • restarts cross the change in both directions: fixed binary resuming an unfixed restart, and unfixed binary resuming a fixed one, neither reporting a missing record
  • with change 2 applied on its own, the restart is byte-identical to change 1 alone, and fort.34 is no longer created

Happy to split this into two PRs if you would rather review them separately.

radstop saves zsolars, declared real :: zsolars(2), with mpputgp. mpputgp
declares its dummy p(kdim,klev), gathers into a local z(NUGP,klev) via mpgagp
and writes all of z, so mpgagp moves NHOR elements per rank from an array that
holds 2, and the restart record is NUGP doubles of which two are the solar
constants and the rest is buffer. At T42 on 16 ranks that is 8192 written for 2,
with each rank reading 510 elements past the end of zsolars.

zsolars is a global pair rather than a distributed gridpoint field, so it does
not belong in a gather at all. solarini already uses the correct idiom for it.

Separately, solarini's nine put_restart_array calls run at initialisation, when
no restart file is open on nwriunit. Fortran connects fort.34 and writes them
there, so every run directory gains a stray file and none of the nine arrays
reaches the restart. They are the write half of a round trip whose read half is
commented out below (the get_restart_array block under nstarfile > 0), where
solarini was made unconditional; these arrays are recomputed from the namelist
at every start and are not restart state.

Neither change affects the integration. Nothing reads zsolars back, and
restarts are located by name and skipped whole by reseek, so a record changing
size cannot dislodge another.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant