Skip to content

Fork Sync: Update from parent repository - #176

Merged
hppritcha merged 67 commits into
open-mpi:masterfrom
openpmix:master
Jul 20, 2026
Merged

Fork Sync: Update from parent repository#176
hppritcha merged 67 commits into
open-mpi:masterfrom
openpmix:master

Conversation

@github-actions

Copy link
Copy Markdown

No description provided.

rhc54 and others added 30 commits July 9, 2026 13:24
The PRRTE AGENTS.md and its PMIx counterpart share the same authorship
and much of the same guidance, but the PMIx file had accumulated several
general, project-agnostic rules that PRRTE's copy was missing.  Carrying
them over keeps the two orientation guides consistent so an agent moving
between the code bases meets the same expectations, and it fills real
gaps in the PRRTE guidance around style, hygiene, and process.

Added: the conditional-spacing style rule; a directive to update
.gitignore for any build product a change introduces; the "never bend a
test to accommodate a bug" testing rule; a shared-repository/worktree
section warning against repo-wide git commands; the preference for
C++-style comments; commit-message guidance (body line wrapping, no AI
tooling attribution, incidental fixes as standalone commits); and a
maintainer-mode caution for build-system edits.

While porting the build-system guidance, correct an over-broad claim in
the existing text: because PRRTE builds in maintainer mode, editing a
Makefile.am does not require the full autogen.pl plus configure cycle —
a plain make regenerates the affected Makefiles.  Also repair a garbled
duplicate line in the copyright-header rule.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The m4-configure component list for each framework is ordered from
highest to lowest priority so that STOP_AT_FIRST, STOP_AT_FIRST_PRIORITY,
and PRIORITY frameworks configure their components in the intended order.
Until now that ordering was produced by shelling out to
config/prte_mca_priority_sort.pl through esyscmd during configure, which
forks an external Perl interpreter at autogen time and adds Perl as an
implicit build dependency for this one small task.

Do the sort entirely in m4 instead.  MCA_ORDER_COMPONENT_LIST now scans
the framework's components to find the highest and lowest priorities,
then walks the priorities from high to low, emitting the components that
carry each priority.  Walking the priorities rather than sorting the
components preserves the original relative order among components that
share a priority, matching the previous behavior.  The Perl script and
its EXTRA_DIST entry are removed.

Signed-off-by: Ralph Castain <rhc@pmix.org>
PRRTE and PMIx are developed in lockstep and most contributors work in
both trees, yet the two AGENTS.md guides had drifted into different
section orders and different levels of coverage.  A rule learned in one
project was hard to relocate in the other, and PRRTE's guide had fallen
behind the code.

Reorder the page to follow the companion PMIx AGENTS.md so each topic
sits in the same place in both.  The thread model, caddy pattern, and
blocking/non-blocking examples move out of the Coding Rules grab-bag into
their own "Thread Safety & the Progress Thread" section, and the build
and testing material is consolidated into a single "Build & Test
Procedures" section rather than being split between a Build section and a
stray Testing subsection under Contributing.

Bring the content current.  The Testing text still claimed PRRTE had no
standalone unit tests; it now documents the test/unit suite wired into
make check and the offline mapper harness driven by make check-offline
over the shared synthetic topologies.  Add the two procedures the old
guide lacked -- how to test-build a change and a "did I break it"
verification checklist -- and import the portable rules the PMIx guide
carried that apply equally here: PRTE_EXPORT symbol visibility, unique
numeric values for status and state codes, a Performance Considerations
section, and a closing General Guidance section.

Finally, repoint the stale cross-reference in the rmaps unit-test
Makefile.am at the section that now describes the offline harness.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Give every MCA framework under src/mca a developer-level AGENTS.md that
orients a contributor to what the framework does, where it runs in the
DVM and job lifecycle, the module/component vtable contract, and a
detailed walk of the functions its base/ provides.  Each component
directory gains its own AGENTS.md describing that component's files,
selection gates, and the specifics of how it implements the framework
interface.  Every AGENTS.md carries a CLAUDE.md symlink so either name
resolves to the same guide.

These follow the model already established by the rmaps guides and let
an AI agent or a human new to a subsystem come up to speed from the code
itself rather than by reverse-engineering it each time.  The companion
rmaps framework guide is refreshed to match the current source (the
map_job size note and a ranking-wording correction).  The empty common
framework gets a short guide explaining why its Makefile.am placeholder
exists.

Documentation only; no source or build files are changed.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The DVM error manager's job- and process-error handlers carried two
latent defects that this commit repairs.

First, the `prte_finalizing` early return in `job_errors` returned
without releasing the state caddy it had just acquired, leaking the
object on every job-error activation seen while the DVM is shutting
down.  It now releases the caddy before returning, matching the
discipline used on every other exit path in the file.

Second, the FAILED_TO_START / FAILED_TO_LAUNCH arm of `proc_errors`
selected the reported job state with `if (PRTE_PROC_STATE_FAILED_TO_START)`
— a bare constant that is always true — so a genuine FAILED_TO_LAUNCH
proc was always recorded as FAILED_TO_START.  The test now compares the
actual `state`, so each failure is reported as itself.

Also refresh the component guide to describe the corrected code rather
than the former smell.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The HNP I/O-forwarding component gated its query on
`!PRTE_PROC_IS_MASTER && !PRTE_PROC_IS_MASTER` — the same predicate
tested twice, a harmless copy-paste artifact.  Collapse it to a single
`!PRTE_PROC_IS_MASTER` test and update the component guide to match.

Signed-off-by: Ralph Castain <rhc@pmix.org>
`plm_pals_init` printed "OOPS prte_plm_base_comm_start returned error"
straight to stderr on the comm-start failure path — a leftover debug
artifact that bypasses the project's error-reporting conventions.  The
failure is already recorded with `PRTE_ERROR_LOG`, so drop the raw
stderr write and refresh the component guide.

Signed-off-by: Ralph Castain <rhc@pmix.org>
`grpcomm_base_stubs.c` was never listed in `base/Makefile.am` and so was
never compiled.  It described a retired collective-tracking API
(`prte_grpcomm_signature_t`, `prte_grpcomm_base.actives`/`sig_table`,
`prte_grpcomm_base_get_tracker`, a `grp_construct` vtable entry, and
`prte_pmix_grp_caddy_t` fields that no longer exist), so it served only
to mislead a reader into thinking the base still owns collective state.
Delete it — the live tracking model lives entirely in the `direct`
component — and drop the guides' warnings about it.

Signed-off-by: Ralph Castain <rhc@pmix.org>
`ras_base_close.c` was not listed in `base/Makefile.am` and so was never
compiled; the framework's real close hook, `prte_ras_base_close`, is
defined in `ras_base_frame.c` and wired into the framework DECLARE.  The
orphaned copy still referenced `active_module`/`ras_opened` fields that
no longer exist on `prte_ras_base`, so it would not even build.  Delete
it and adjust the framework guide accordingly.

Signed-off-by: Ralph Castain <rhc@pmix.org>
`ess/base/base.h` declared four symbols — `prte_ess_env_get`,
`prte_ess_env_put`, `prte_ess_base_proc_binding`, and the
`prte_ess_base_std_buffering` variable — that have no definition
anywhere in the tree.  They are remnants of long-removed functionality
and only invite a reader to wire new code to something that will not
link.  Drop the declarations and update the framework guide.

Signed-off-by: Ralph Castain <rhc@pmix.org>
PRRTE carried a full prtedl (dlopen/dlsym/dlclose) framework, but it is
dead weight: nothing selects a prtedl module or calls the prte_dl_*
wrapper API anywhere in the tree.  PRRTE has no MCA base of its own —
the plugin/component system comes from the installed PMIx — so the code
that actually dlopen()s PRRTE's DSO components is PMIx's component
repository, which loads through PMIx's own pdl framework.  prtedl is a
leftover from when the MCA base was embedded in-tree and has been unused
for a long time (its libltdl component would not even compile).

Delete the whole src/mca/prtedl tree.  The generated framework list
(config/autogen_found_items.m4, src/include/prte_frameworks.*) is
rebuilt by autogen.pl from the directory layout, so it drops prtedl
automatically.

Remove the now-dead references that fall out of the deletion:

- schizo no longer rewrites a generic "--mca dl_foo" into the PRRTE
  "prtedl_foo" namespace (in both the parse_prte scanner and the ompi
  personality's check_prte_overlap).  The PMIx-side mapping to pdl is
  retained, so those directives now correctly route to PMIx's pdl
  instead of a framework that no longer exists.

- prte_info reported dynamic-loading support via PRTE_HAVE_DL_SUPPORT,
  which prtedl's configure.m4 defined.  It now reports
  PMIX_HAVE_PDL_SUPPORT, which reflects the loader PRRTE actually uses.

Verified with a clean --enable-debug build, make check, and a live
prterun -n 2 hostname launch.

Signed-off-by: Ralph Castain <rhc@pmix.org>
GitHub will be charging for its use

Signed-off-by: Ralph Castain <rhc@pmix.org>
Reflect changes to the mapby, rankby, and bindto options.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Update to reflect change in those cmd line options

Signed-off-by: Ralph Castain <rhc@pmix.org>
The odls child code that runs between fork() and execve() rendered
show_help messages, closed file descriptors by scanning /proc/self/fd
with opendir/readdir, allocated argv, and called
getcwd/stat/asprintf/strerror before reporting an execve failure. The
binding code reached through the same path allocated error strings with
asprintf/strdup and even terminated the child with exit() rather than
_exit().

None of that is legal after fork(). fork() copies only the calling
thread and leaves any lock another thread held frozen in the child, so
calling into the allocator, stdio, opendir, or show_help can deadlock
the child intermittently under load - which then blocks the parent that
is waiting on the error pipe. This mirrors the problem fixed on the PMIx
side in openpmix/openpmix#4005.

Restrict the child to async-signal-safe operations and move all
rendering to the parent. Replace the variable-length show_help-string
pipe protocol with a fixed-size record carrying a failure code
(prte_odls_child_err_t) plus errno; the child writes only that record
via the new prte_odls_base_child_fail (fatal, _exit) and
prte_odls_base_child_warn (non-fatal, returns), shared by both the
component's do_child and the base binding routine so the two writers
cannot drift apart. do_parent reads the record and renders the
human-readable diagnostic - including the bad-interpreter stat() check
and the working-directory reconstruction - where allocation and
show_help are safe. Close file descriptors with a plain close() loop
instead of the /proc scan, and default argv in the parent before the
fork so the child never allocates it.

While adding the parent-side rendering, register help topics for the
three binding-warning messages (not bound, memory not bound,
incorrectly bound) that the binding code referenced but that were absent
from every help file, so those warnings rendered as "help not found"
rather than the intended text.

Signed-off-by: Ralph Castain <rhc@pmix.org>
prte_odls_base_set() ran entirely in the forked child before execve()
and, to bind the process, called hwloc_bitmap_alloc, parsed the mapper's
cpuset string with hwloc_bitmap_list_sscanf, invoked hwloc_set_cpubind
and hwloc_set_membind, and - for --report-bindings - rendered a string
and printed it. hwloc allocates, and on Linux hwloc_set_cpubind
CPU_ALLOCs a mask once the logical-CPU count exceeds the fixed cpu_set_t
size (exactly the large-node case). None of that is legal after fork():
the fork happens on an odls spawn-pool worker thread while other spawn
threads and the PMIx progress thread are allocating, so a frozen malloc
lock can deadlock the child.

Split the work. prte_odls_base_prepare_binding() now runs in the parent,
before the fork, from spawn_proc: it parses the cpuset, classifies the
binding, precomputes the memory-binding policy, emits the
--report-bindings output and the "incorrectly bound" warning, and stashes
a ready-to-apply hwloc bitmap on the caddy. prte_odls_base_set() then
runs in the child and only issues the bind syscalls.

For CPU affinity the child now issues a bare sched_setaffinity() using a
raw cpu_set_t that the parent precomputed from the hwloc bitmap - a
syscall is async-signal-safe. This is gated on a new
PRTE_HAVE_SCHED_SETAFFINITY configure check (config/prte_check_setaffinity.m4);
where the function is absent, e.g. macOS, an #else clause falls back to
hwloc_set_cpubind as before. Memory binding still goes through
hwloc_set_membind in the child on all platforms; turning that into a bare
set_mempolicy/mbind syscall would mean reproducing hwloc's NUMA nodeset
handling and is left for a follow-up.

Because the child no longer reads the applied binding,
--report-bindings now renders the binding the mapper requested rather
than the value read back after the bind. For a successful bind the two
are identical, and this keeps the allocating cset2str/pmix_output out of
the async-signal-safe child.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Add Slurm-private tracker objects for release requests that start a DVM
shrink before Slurm resources are returned. Each tracker records its
PRRTE shrink campaign and owns per-Slurm-job release actions.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
Add init/finalize helpers for the Slurm release shrink tracker list and
wire them into the Slurm RAS module lifecycle. Finalization drains any
remaining trackers so their nested release actions are also released.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
Update count-based Slurm release to skip allocations that already have an
active release shrink tracker. The request is accepted only if enough
non-conflicting sessions remain, and the mutation path uses the same
newest releasable session selection.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
Treat the Slurm job that issued a count-based release as removable only
down to one surviving node. This lets count release use spare nodes from
the requesting allocation without ever cancelling that allocation or
removing the current Slurm daemon node.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
Extend Slurm release request parsing to accept PMIX_ALLOC_NODE_LIST and
PMIX_ALLOC_ID in addition to PMIX_ALLOC_NUM_NODES. Node-list releases
resolve exact target nodes, group them by Slurm allocation, and resize
partial allocations with an explicit survivor list.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
Start Slurm release requests by planning the resources to remove and
launching a DVM shrink campaign. Defer Slurm-side cancellation or resize
until the shrink completion callback reports that the targeted daemons
have left.

Track pending release actions privately in the Slurm release component so
overlapping requests for the same Slurm job are rejected while unrelated
release requests can proceed.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
Reject Slurm release requests before parsing or planning when the DVM is
not running in elastic mode. Print a hint telling users to enable the
prte_elastic_mode MCA parameter.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
Track whether Slurm sessions were created dynamically by PRRTE and only
allow full RM-side termination for those sessions. Sessions discovered
from the initial allocation may still be partially shrunk, but explicit
full release requests for them are rejected.

When static sessions are destroyed locally, return success without
asking Slurm to terminate the underlying job.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
Keep the local helper declarations in ras_slurm_modify_release.c on
single lines for a more uniform declaration block.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
Break prte_ras_base_complete_request() into smaller grow and shrink
helpers while preserving the existing public entry point.

The split keeps the shared DVM orchestration in RAS base, but exposes
helpers that can be called independently by other RAS components.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
Use the shared DVM shrink helper for Slurm release requests after Slurm
has planned the scheduler-side release actions.

Keep Slurm-specific tracking and RM-side cancellation or resize work in
the Slurm release component.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
Route Slurm extend completion through the shared RAS base helper that
marks the DVM for growth and activates daemon launch.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
During elastic shrink, Slurm may terminate the srun launcher for a
released dynamic allocation. It can also terminate an srun step whose
session remains but has shrunk down to the HNP node.

Treat these non-zero srun exits as expected when elastic mode is active.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
Return PRTE_ERR_OP_IN_PROGRESS after accepting a Slurm release request
so the module-level modify path can avoid touching the request after
the callback may have released it.

Credits: AccelCom @ Barcelona Supercomputing Center

Signed-off-by: Petter Sandås <petter.sandas@bsc.es>
rhc54 and others added 29 commits July 17, 2026 11:16
Add test/unit/errmgr, wired into make check, covering the parts of the
errmgr framework's structural contract that are reachable without a
live DVM.  It asserts that every module struct -- the log-only default,
the live global, and the dvm and prted role modules -- carries a
non-NULL logfn, the load-bearing invariant that lets prte_errmgr.logfn
be safely called during a very early failure before the framework is
even opened, and that prte_errmgr_base_log survives a normal code,
PRTE_SUCCESS, and an out-of-range "silent" code without dereferencing a
NULL string.  The handlers themselves mutate global runtime state and
drive termination through plm and grpcomm, so they still require a live
DVM and remain covered by the integration and dockerswarm harnesses.

Register the new Makefile in the config file list and .gitignore and
add a Testing section to the framework guide.  Adding the config file
list entry required regenerating configure via autogen.pl.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The remaining early returns in proc_errors -- the pack-failure branches
inside the CALLED_ABORT, TERM_NON_ZERO, keep_going abnormal-termination
report, and all-terminated blocks -- bare-returned after releasing the
data buffer, leaking the state caddy the handler had acquired.  Each of
these eleven paths now routes through the cleanup label so the caddy is
released.  These are rare (they fire only when packing an alert to the
HNP fails), which is why they lingered after the non-error leaks fixed
in the previous commit, but there is no reason to treat them
differently: every exit from the handler owns the caddy release.

The send-failure paths, which release the alert and fall through to
cleanup rather than jumping, are left as-is, and the component guide is
updated to state the invariant that every proc_errors exit reaches
cleanup.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The signal-forwarding callback packs a PRTE_DAEMON_SIGNAL_LOCAL_PROCS
command and sends it to the daemon itself, which unpacks a jobid
followed by an int32 signal number and delivers that signal to the
local application processes.  The 2021 conversion of PRRTE process
names to PMIx process names replaced the packed signal value with
PRTE_JOBID_WILDCARD, a zero-filled nspace, while leaving the pack type
as PMIX_INT32.  The receiver therefore always unpacked signal 0, so
every signal a user asked to forward via ess_base_forward_signals was
delivered as the null signal -- a silent no-op.

Pack the caught signal number (signum) as the code originally did so
the forwarded signal actually reaches the application.

Signed-off-by: Ralph Castain <rhc@pmix.org>
prte_ess_base_setup_signals rejects a signal the system cannot forward
(SIGKILL, SIGTERM, and friends) when the user names it, but the branch
that parses a signal given as an integer never consulted the
can_forward flag.  A request such as "ess_base_forward_signals=15" thus
slipped a non-forwardable signal onto the forward list, installing a
spurious handler that the by-name path would have refused -- for a
catchable signal like SIGTERM that means a second handler competing
with the daemon's own shutdown handler.

Apply the same can_forward gate in the numeric branch so both input
forms behave identically.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The env, slurm, pals, and lsf daemon modules each call their
<rm>_set_name() helper and then proceed straight into
prte_ess_base_prted_setup(), discarding the helper's return code.  A
missing nspace, vpid, or node-offset environment variable therefore
left the daemon with a half-built identity and only surfaced later as a
confusing failure deep in the shared bring-up.  Check the return and
route a failure through the module's error path so it reports the
actual failing step.

The slurm and lsf helpers additionally passed the result of getenv()
straight to atoi() when computing the per-node vpid offset
(SLURM_NODEID, LSF_PM_TASKID).  The launchers always set these, so the
normal path is unaffected, but atoi(NULL) is undefined behavior and
crashes on glibc.  Guard the lookups and return PRTE_ERR_NOT_FOUND when
the variable is absent, matching what pals already did -- now that the
caller checks the return, this becomes a clean error instead of a
crash.

While here, drop a stray empty statement after the return in the lsf
module's rte_finalize.

The component AGENTS.md notes are updated to describe the guarded,
return-checked behavior.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Almost all of the ess framework -- daemon and HNP bring-up -- needs a
live DVM and is exercised by the integration harnesses.  The one piece
that is pure input parsing, prte_ess_base_setup_signals(), can be
tested in isolation, and it produces the very signal numbers the
forwarding callback delivers, so it is worth pinning down.

Add test/unit/ess/test_ess.c, wired into make check.  It drives the
"none" no-op case and a mixed name/duplicate parse, and asserts each
accepted entry carries a real, non-zero signal number -- the field a
recent regression had corrupted.  Because setup_signals latches after
its first non-"none" call, a process can drive only one substantive
parse; the test is structured around that.  The framework AGENTS.md
gains a Testing section describing what is and is not unit-testable
here.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The routing layer notifies every runtime consumer of a daemon fault by
calling prte_grpcomm.fault_handler(), prte_filem.fault_handler(), and
prte_relm.fault_handler() unconditionally from routed_radix.c, with no
NULL guard.  The filem framework's default "none" module -- the module
that stays installed whenever no filem component is selected, which is a
supported configuration -- never initialized its fault_handler slot, so
it was NULL.  The first daemon fault in such a build dereferenced a NULL
function pointer and crashed the DVM.

Add a prte_filem_base_none_fault_handler no-op and wire it into the
"none" module so the slot is always safe to call.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The base filem RML query service (filem_base_receive.c) is scaffolding
that nothing currently starts, but it carries two latent bugs that would
bite whenever it is revived.  prte_filem_base_comm_stop() inverted its
"already stopped?" test -- it read "if (recv_issued) return" instead of
"if (!recv_issued) return", so it bailed out without ever cancelling the
receive.  And filem_base_process_get_remote_path_cmd() leaked the
unpacked filename on the getcwd() failure path by returning directly
instead of branching to its CLEANUP label.

Correct the guard and route the error through CLEANUP.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Several receive-side error paths in the raw component mishandled the
objects they had already placed on a file-scoped list.  recv_ack() freed
the unpacked filename only when it matched an in-flight transfer, leaking
it whenever an ack arrived for an already-positioned file.  In
recv_files(), the chunk-0 dirpath-create failure released the incoming
object without first removing it from incoming_files -- leaving a dangling
pointer the next chunk would walk -- while the two fd-open failures leaked
the incoming object entirely; make all three remove-then-release
consistently.  write_handler() leaked the zero-byte EOF marker output on
every completed transfer and leaked the tar command and dirname strings on
the archive error paths (and left the daemon's cwd changed after a failed
extraction); release the marker and free the strings, restoring the cwd on
failure.

None of these are hot-path, but the dangling incoming pointer is a
latent use-after-free.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Preload staging was broken end to end because create_link() and its
caller pointed the symlinks in the wrong places.

create_link() left rc holding the PMIX_ERR_EXISTS (-11) that
pmix_os_dirpath_create() returns whenever the per-proc session directory
already exists -- the normal case.  The guard tolerated that value but
never reset rc before the symlink, so a link that was created perfectly
still returned -11 (surfacing as PRTE_ERR_IN_ERRNO) and
raw_link_local_files() aborted the launch.  Reset rc to PRTE_SUCCESS once
the directory is known to exist.

The symlink source was also wrong: recv_files() writes staged bytes under
the node's top_session_dir, but create_link() was handed the job
session_dir as the source base, so the link pointed one level too deep at
a path that does not exist.  Pass top_session_dir as the source.

Finally, the link target: a preloaded data file belongs in each proc's
own session directory, but a preloaded binary does not.  --preload-binary
sets PRTE_APP_SSNDIR_CWD, which makes every proc's cwd the job session
directory (see setup_path in odls), so "./<binary>" only resolves if the
executable is linked there.  Select the target by inbnd->type: the job
session dir for PRTE_FILEM_TYPE_EXE, the per-proc dir for everything else.
Also free the file list with PMIx_Argv_free rather than a bare free on the
create_link error path.

Signed-off-by: Ralph Castain <rhc@pmix.org>
… open

raw_preposition_files() threadshifts each file to send_chunk() as it walks
the list, so an already-queued transfer has a live libevent event and an
open fd.  When a *later* file failed to open, the function released the
outbound -- whose destructor drains the queued transfers -- and returned
PRTE_ERROR.  That freed the transfers out from under their pending events
(a use-after-free libevent flags as "event_queue_remove_active ... not on
queue"), leaked their fds, and double-drove PRTE_JOB_STATE_FILES_POSN_FAILED
because the caller treats the error return as a failure while the eventual
completion callback fires as well.

Instead, record PRTE_ERR_FILE_OPEN_FAILURE in outbound->status and stop
queueing: the already-queued transfers complete normally and the completion
callback delivers that status, or -- if nothing was queued -- the empty
list path fires the callback with it.  The loop can now leave entries on
the temporary fileset, so drain it with PMIX_LIST_DESTRUCT.

Signed-off-by: Ralph Castain <rhc@pmix.org>
setup_path() honored PRTE_APP_SSNDIR_CWD by reading the session directory
from app->job.  That back-pointer is only ever set on the HNP (in the
ess/hnp and dynamic-spawn paths); it is not serialized into the launch
message, so on any daemon that rebuilt the job from that message it is
NULL.  --preload-binary sets PRTE_APP_SSNDIR_CWD, so launching a preloaded
binary dereferenced a NULL app->job and segfaulted the daemon.

Pass the job into setup_path() explicitly.  Both call sites already hold
it -- launch_local from jobdat, restart_proc from
prte_get_job_data_object() -- so the back-pointer is never needed.

Signed-off-by: Ralph Castain <rhc@pmix.org>
prte_app_parse.c deliberately leaves app->app.cwd NULL when the user gives
--set-cwd-to-session-dir: the working directory is chosen on the backend
from the PMIx_SET_SESSION_CWD directive rather than fixed at parse time.
The spawn path in prte() then handed that NULL straight to strdup() while
building the PMIx app array, crashing the launcher before the job was even
submitted.

Guard the copy so a NULL cwd stays NULL in the PMIx app, which is exactly
what the session-cwd directive expects.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The staging machinery needs a live DVM, but two pieces of the framework
can be exercised in isolation and are worth pinning down.  test_filem
checks that the process-set, file-set, and request classes construct to
their documented defaults and tear down cleanly, and that the default
"none" module is a set of safe no-ops -- every slot returns success,
preposition_files still fires its completion callback, and fault_handler
is non-NULL and callable (the regression pin for the routed_radix crash).

Wire it into make check alongside the other test/unit suites.

Signed-off-by: Ralph Castain <rhc@pmix.org>
A single-host run cannot prove file staging works, because the source file
is already present in the launch directory -- the app runs even if filem
does nothing.  The multi-node swarm can: compile a marker binary on node1
only, then run it under --preload-binary on node2 and node3, where it can
only execute if the bytes were actually staged across daemons and linked
into the job session directory the session cwd points at.

Add that test to run-tests.sh and describe it in the README.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Every xcast builds an op_t in xcast_nb() to carry the (possibly
compressed) payload, thread-shifts it to begin_xcast(), which packs it
and relays it to the HNP.  The op the master actually tracks and
completes is a fresh one built on receipt, so the initiating op is meant
to be discarded once relayed -- but begin_xcast() never released it, and
xcast_nb() also dropped it on the copy-payload failure path.  Because
xcast underlies every launch, wireup, and daemon command, this leaked an
op_t plus its payload on the hottest collective path.  Release the op on
all of begin_xcast()'s exits and on the xcast_nb() error path.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The group() handler allocates a prte_pmix_grp_caddy_t, thread-shifts it,
and caches only its callback pointers into the long-lived tracker.  The
caddy is therefore consumed by the handler, but its two success returns
(the bootstrap-to-HNP path and the send-to-self path) never released it,
leaking the caddy and its strdup'd groupID on every successful group
construct/destruct.  Release it on both.  While here, close the matching
gap on the error label, which leaked the open grpinfo/endpts info-lists
on every jump to it; NULL them once they are released on the success path
so the post-release send-failure jumps do not double-release.

Signed-off-by: Ralph Castain <rhc@pmix.org>
When the PMIX_GROUP_BOOTSTRAP value failed to convert, group() logged the
error and PMIX_DESTRUCT'd the signature but then fell through and kept
using it -- get_tracker(), pack_signature(), and the relay all ran
against a destructed signature whose groupID/members/addmembers had
already been freed, a use-after-free.  Every other directive-parse
failure in the same loop jumps to the error label; make the bootstrap
case do the same.

Signed-off-by: Ralph Castain <rhc@pmix.org>
fence_recv() unpacks the incoming info array but only freed it on the
non-HNP rollup path.  On the HNP-complete path (which broadcasts the
release via xcast) and on every intermediate contribution that does not
yet complete the rollup, the array was leaked whenever the fence carried
info (e.g. a PMIX_TIMEOUT).  Free it once at the end of the handler; the
non-HNP path already frees and NULLs it before forwarding, and
PMIX_INFO_FREE is a no-op on NULL, so all paths are covered without a
double free.

Signed-off-by: Ralph Castain <rhc@pmix.org>
fence_recv() and grp_recv() both start by unpacking a signature into a
NULL-initialized pointer.  On an unpack failure the helper leaves that
pointer NULL, but both handlers only logged the error and fell through to
code that immediately dereferences the signature -- get_tracker(sig, ...)
in the fence case, sig->op in the group case -- so a truncated or corrupt
RML message crashed the daemon.  Return on the failure, matching the
fence_release/grp_release handlers that already do.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The group-signature constructor's definition carried a trailing backslash
after the parameter list, splicing the opening brace onto the signature
line.  It was harmless but confusing; remove it.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Wire a new test/unit/grpcomm into make check.  The collective algorithms
need a live DVM and are covered by the integration/dockerswarm harnesses,
but the framework's DVM-independent contract can be checked in isolation:
the direct module's vtable is fully wired (every entry point is a
load-bearing "MUST be provided" slot), the component is named "direct",
prte_grpcomm_base.context_id starts at UINT32_MAX (the group context-id
pool counts down from the top), and every signature/tracker/caddy class
constructs with its documented defaults -- all rollup counters zero so a
collective cannot complete before anyone reports, the group tracker's
grpinfo/endpts info-lists opened -- and destructs without leaking.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Record the two invariants a deep review found violated: an entry-point
handler owns the caddy/op it was thread-shifted and must release it on
every exit (the tracker caches only the callbacks, never the caddy), and
a recv handler must return on a signature unpack failure because the code
below dereferences the NULL signature.  Also note the new
test/unit/grpcomm coverage.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Several error and cleanup paths in the ODLS base leaked resources or
dereferenced invalid pointers.  While building the launch message,
get_add_procs_data leaked the PMIX_INFO_LIST and the procs argv when
regex generation failed, and leaked the info array when
PMIx_server_setup_application failed synchronously (its callback, which
frees the array, never fires in that case).  While parsing the message,
construct_child_list leaked the prior-jobs data buffers when a nested
load failed.  spawn_proc leaked the per-child caddy (and its wdir
string) on the PRTE_JOB_DO_NOT_SPAWN mapping-only path.  setup_path
overwrote app->cwd without freeing the previous string.  Finally, if
getcwd() failed at entry to launch_local, the code fell through to the
shared cleanup that chdir()s to the uninitialized basedir buffer.

Free or release each resource on the failing path, and bail directly
from the getcwd() failure instead of using the uninitialized buffer.

Signed-off-by: Ralph Castain <rhc@pmix.org>
When a per-app setup step failed during local launch - setup_fork,
file pre-positioning, the executable/cwd check, or the resource-limit
setup - launch_local flagged only that app's procs as failed and then
jumped out of the launch loop.  Any procs belonging to apps it had not
yet reached were left in the INIT state.  Because a daemon's errmgr
only reports FAILED_TO_LAUNCH once every local proc has been accounted
for (num_terminated == num_local_procs), those stranded procs prevented
the report from ever being sent, hanging a multi-app launch.  The
per-child IOF-setup failure had the same shape, and could strand even a
single-app job.

Give every fatal launch error the same handling the setup_path and
working-directory cases already used: flag the affected procs with
PRTE_ODLS_SET_ERROR and then activate PRTE_JOB_STATE_FAILED_TO_LAUNCH
for the whole job, so the job is torn down promptly and uniformly
regardless of which app or proc tripped the error.  This also corrects
setup_fork, which recorded a proc-state constant in exit_code instead
of the error code, and setup_path, which reported a file-limit error
code for an unrelated failure.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The fork/exec/waitpid/kill lifecycle only runs against real processes
inside a live DVM, but the framework's structural contract can be
checked in isolation.  Add test/unit/odls/test_odls.c, wired into
make check, asserting that the pdefault module vtable is fully wired
and reuses the base get_add_procs_data, that the component names itself
"pdefault", that the PRTE_DAEMON_* command bytes are pairwise unique,
that the child-error enum keeps NONE at zero with every warn code
sorted after every fatal code, and that the two caddy classes
construct with their documented defaults and destruct cleanly.

Register the new test directory in test/unit/Makefile.am and
config/prte_config_files.m4, and ignore its build products.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Record, in the framework guide, the gotchas behind the accompanying
fixes: that every fatal launch error must abort the whole job via
PRTE_ACTIVATE_JOB_STATE rather than failing only one app's procs; that
the child loop variable is invalid outside the per-child loop; that
every spawn_proc exit must release the caddy; that setup_path writes
through to app->cwd; that the getcwd baseline must not be used before
it is established; and that the blocking base functions own their lock
and must not bare-return.  Add a Testing section pointing at the new
structural unit test and the integration smoke tests.

Signed-off-by: Ralph Castain <rhc@pmix.org>
The offline mapper harness crashed with an uncaught ZeroDivisionError
when a case produced output it could not parse into a map: parse_map
returned an empty map, and the ranking checker then divided by the
(zero) node count, aborting the entire multi-thousand-case run with a
cryptic traceback instead of failing the single case.

Raise a ParseError when a JOB MAP block yields no parseable nodes so
the existing per-case parse-error path reports it cleanly, guard the
rank-expectation helper against an empty node set, and wrap the
per-case checker so any unexpected exception fails only that case and
the run continues.

Signed-off-by: Ralph Castain <rhc@pmix.org>
prte_event_alloc() returned a bare malloc'd, uninitialized event, but
every caddy that owns one frees it in its destructor with
prte_event_free() -- libevent's event_free(), which calls event_del()
and dereferences the event's internal fields (ev_base et al.).  On an
uninitialized event those fields are garbage, so the free crashes.

In normal operation this never bites because the owning objects always
event_assign() the event before they are released: PRTE_ACTIVATE_LOCAL_LAUNCH
assigns the odls launch-local caddy's event, and prte_timer_t assigns
its event when the timer is armed.  But an object that is constructed and
then destructed without ever being activated -- as the odls unit test does
when it exercises the launch-local caddy's destructor -- frees an
uninitialized event and segfaults inside event_del().  It went unnoticed on
platforms where a fresh malloc page happens to be zeroed (a zeroed event has
a NULL base, which event_del() tolerates), and surfaced as a CI segfault on
Linux once the allocation landed on recycled, non-zero heap.

Zero-initialize the event at allocation.  A zeroed event is always safe to
event_free(), and a later event_assign() overwrites the zeros, so armed
events are unaffected.  This fixes the launch-local caddy, hardens
prte_timer_t against the same latent hazard, and is strictly safer for every
other caller.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
@hppritcha
hppritcha merged commit 7a11e7a into open-mpi:master Jul 20, 2026
16 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.

3 participants