Skip to content

Fork Sync: Update from parent repository#172

Merged
hppritcha merged 31 commits into
open-mpi:masterfrom
openpmix:master
Jun 30, 2026
Merged

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

Conversation

@github-actions

Copy link
Copy Markdown

No description provided.

rhc54 and others added 2 commits June 30, 2026 05:49
Record the project workflow as a golden rule in the agent orientation
guide: topic branches must be pushed to a personal fork remote and the
pull request opened from there, never pushed directly to the shared
upstream `origin` (openpmix/prrte).

CLAUDE.md previously duplicated the entire orientation guide and had to
be kept in sync with AGENTS.md by hand. Replace it with a symlink to
AGENTS.md so the two can never drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
Testing PRRTE's elastic-DVM grow and shrink paths requires a multi-node
DVM, and the only place those paths can be exercised without a real PMIx
scheduler has been an ad-hoc set of containers rebuilt by hand each
session. Capture that setup in the repo so a future contributor can clone
upstream and bring up a working test swarm quickly.

contrib/dockerswarm/ provides a build script, a Dockerfile, a four-node
docker-compose definition, the PMIx tool client that drives the size
changes, and a README documenting the workflow and its pitfalls. build.sh
exports this repo's committed tree via git archive (so the image contains
exactly the code under test, with no host build artifacts and no
architecture mismatch) and the Dockerfile clones PMIx and compiles both
into the image. The README calls out the one non-obvious requirement that
otherwise silently wastes time: the DVM must be started with
--prtemca prte_elastic_mode 1, or grow and shrink never complete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
(cherry picked from commit b4e427c)
@github-actions

Copy link
Copy Markdown
Author

Hello! The Git Commit Checker CI bot found a few problems with this PR:

049db08: contrib: add a Docker harness for elastic-DVM grow...

  • check_cherry_pick: contains a cherry pick message that refers to a commit that exists, but is in an as-yet unmerged pull request: b4e427c

Please fix these problems and, if necessary, force-push new commits back up to the PR branch. Thanks!

rhc54 and others added 27 commits June 30, 2026 11:04
Add docs/plans/ as a new top-level section for in-progress feature
implementation plans.  Move and update the DVM launch-fence plan
(previously an untracked file under docs/how-things-work/) to
docs/plans/elastic_dvm/dvm_launch_fence.rst.

Also commit the companion state_machine.rst (was untracked) and wire
both new sections into the appropriate index files.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Introduce a global launch fence that serialises app-job dispatch
with in-progress daemon grow and shrink campaigns, closing two race
windows:

  Race 1 – a new app job reaches VM_READY → MAP while daemons are
  still launching (grow) or terminating (shrink).  It could be mapped
  to a node whose daemon does not yet exist or is dying.

  Race 2 – an app job completes MAP before a shrink is initiated and
  then enters prte_plm_base_launch_apps() while a targeted daemon
  is dying between MAP and the send.

The mechanism is a single integer counter (prte_dvm_launch_fence)
that is incremented at the start of every daemon launch campaign
(+1 per grow, +m per shrink targeting m daemons) and decremented
back to zero as each campaign completes.  When the fence is nonzero,
app jobs are parked in one of two held-job arrays and released when
the fence reaches zero.

prte_shrink_campaigns is a pmix_list_t of prte_shrink_campaign_t
entries (one per PMIX_ALLOC_RELEASE request), each tracking the
array of target daemon ranks and a pending-ACK count.  Multiple
concurrent shrink campaigns are fully supported.

-- New types and globals --

prte_globals.h / prte_globals.c
  prte_dvm_launch_fence    – running count of open campaign slots
  prte_held_jobs           – jobs parked at the VM_READY → MAP gate
  prte_prelaunch_held_jobs – jobs parked at the LAUNCH_APPS gate
  prte_shrink_campaign_t   – pmix_list_item_t subtype carrying the
                             target rank array, ntargets, and the
                             pending-ACK count; destructor frees the
                             array
  prte_shrink_campaigns    – pmix_list_t of active shrink campaigns

prte_init.c    – construct both pointer arrays and the campaign list
prte_finalize.c – destruct all three

-- New PLM type constants --

plm_types.h
  PRTE_JOB_STATE_WAITING_FOR_DAEMONS (17)
    Marker state set on parked jobs so that debug output makes the
    reason for the park visible.  No callback is registered for it.
  PRTE_PLM_SHRINK_ACK_CMD (7)
    PLM command sent daemon → HNP over PRTE_RML_TAG_PLM to confirm
    that the daemon has exited due to a shrink request.

error_strings.c – add "WAITING FOR DAEMON CAMPAIGN" for the new state

-- New PLM base functions --

plm_private.h declares; plm_base_launch_support.c defines:

  prte_plm_base_fence_release(bool success)
    Called when prte_dvm_launch_fence reaches zero.  Drains
    prte_held_jobs (re-activating VM_READY or NEVER_LAUNCHED),
    drains prte_prelaunch_held_jobs (re-activating LAUNCH_APPS,
    remapping via MAP if any of the job's procs were assigned to a
    shrink target, or NEVER_LAUNCHED on failure), and does a safety
    sweep of any residual campaign entries.

  prte_plm_base_job_needs_remap(jdata)
    Returns true if any proc in jdata was mapped to a node whose
    daemon rank appears in any active shrink campaign.

  prte_plm_base_reset_proc_map(jdata)
    Removes all proc objects from their nodes, decrementing both
    node->num_procs and node->slots_inuse (mirroring the +1/+1
    done by prte_rmaps_base_claim_slot; tool procs, which do not
    count against slots_inuse, are handled with the PRTE_APP_FLAG_TOOL
    check).  Clears jdata->num_procs and jdata->num_launched so the
    mapper can re-create proc objects on surviving nodes.

-- Grow fence --

plm_base_launch_support.c – prte_plm_base_setup_virtual_machine
  Increment prte_dvm_launch_fence inside the existing
  "if (0 < map->num_new_daemons)" block, after setting
  PRTE_JOB_LAUNCHED_DAEMONS.  All four PLM backends (ssh, slurm,
  pals, lsf) call this common path, so one insertion covers every
  extension scenario.  The attribute and the fence are always set
  on the daemon job.

state_dvm.c – vm_ready
  The fence decrement is placed just before the closing brace of
  the outer PRTE_JOB_LAUNCHED_DAEMONS block, after the inner
  "!DO_NOT_LAUNCH && num_daemons > 1" xcast block.  This covers
  both the xcast path and the DO_NOT_LAUNCH / single-daemon path.
  Each inner error-exit path (nidmap_create, PMIx_Get, two
  PMIx_Data_pack calls, and the xcast itself) gets its own
  fence decrement + fence_release(false) before calling
  PRTE_ACTIVATE_JOB_STATE(NULL, FORCED_EXIT).

  Before preposition_files (the app-job branch), park the job in
  prte_held_jobs when prte_dvm_launch_fence > 0 and set its state
  to PRTE_JOB_STATE_WAITING_FOR_DAEMONS.

state_dvm.c – check_complete
  Inside the "if (NULL == jdata || PMIX_CHECK_NSPACE(...))" block
  (the daemon-job-only path), check whether PRTE_JOB_LAUNCHED_DAEMONS
  is set and prte_dvm_launch_fence > 0 before any of the early
  returns.  If so, decrement and call fence_release(false).  This
  is the safety net for the case where the daemon job reaches
  TERMINATED rather than going through the vm_ready error paths.

errmgr_dvm.c – proc_errors
  After marking a daemon as gone and decrementing num_daemons,
  check whether a grow campaign is in progress
  (prte_dvm_launch_fence > 0 && PRTE_JOB_LAUNCHED_DAEMONS set
  on the daemon job).  If so, decrement the fence, call
  fence_release(false) if the fence hits zero, and remove the
  PRTE_JOB_LAUNCHED_DAEMONS attribute to prevent double-decrement
  if a second daemon in the same campaign crashes.

-- Shrink fence --

ras_base_allocate.c – prte_ras_base_complete_request (PMIX_ALLOC_RELEASE)
  Before free(ranks) (which precedes the xcast): allocate a
  prte_shrink_campaign_t, memcpy the rank array into it, set
  ntargets = pending = m, append to prte_shrink_campaigns, and
  increment prte_dvm_launch_fence by m.  If the subsequent xcast
  fails, remove the just-appended campaign from the list and
  restore the fence to prevent held jobs from parking forever.

prted_comm.c – PRTE_DAEMON_SHRINK_CMD handler
  After PRTE_PMIX_WAIT_THREAD(&lk) completes the JOB_END
  notification but before activating DAEMONS_TERMINATED:
  allocate a buffer with PMIX_DATA_BUFFER_CREATE, pack
  PRTE_PLM_SHRINK_ACK_CMD and PRTE_PROC_MY_NAME->rank, and send to
  PRTE_PROC_MY_HNP->rank on PRTE_RML_TAG_PLM using
  PRTE_RML_RELIABLE_SEND.  The rank is included so the HNP can
  match the ACK to the correct campaign entry without relying on
  sender metadata.

plm_base_receive.c – PRTE_PLM_SHRINK_ACK_CMD case
  Unpack the sender rank, iterate prte_shrink_campaigns to find
  the entry whose targets array contains that rank, decrement
  campaign->pending and prte_dvm_launch_fence, remove and release
  the campaign entry when pending hits zero, and call
  fence_release(true) when the fence hits zero.  Logs unrecognised
  ranks at verbose level 1.

plm_base_launch_support.c – prte_plm_base_launch_apps
  After the job-state guard and state update, check
  !pmix_list_is_empty(&prte_shrink_campaigns) (not
  prte_dvm_launch_fence > 0, so that a concurrent grow does not
  hold jobs already mapped to surviving nodes).  If nonempty, set
  PRTE_JOB_STATE_WAITING_FOR_DAEMONS, PMIX_RETAIN the job, add it
  to prte_prelaunch_held_jobs, release the caddy, and return.

errmgr_dvm.c – proc_errors (shrink crash fallback)
  After the grow-fence check, iterate prte_shrink_campaigns with
  PMIX_LIST_FOREACH_SAFE looking for the dead daemon's rank.  When
  found, decrement campaign->pending and prte_dvm_launch_fence,
  remove and release the campaign if pending reaches zero, and call
  fence_release(true) if the fence reaches zero.  This prevents
  held jobs from parking indefinitely when a shrink target crashes
  before it can send its ACK.

Signed-off-by: Ralph Castain <rhc@pmix.org>
Fix unknown Pygments lexer for m4 code block by using 'autoconf'
instead.

Signed-off-by: Ralph Castain <rhc@pmix.org>
A shrink campaign previously completed when each targeted daemon sent a
PRTE_PLM_SHRINK_ACK_CMD to the HNP just before exiting, with the errmgr
comm-failure path kept only as a fallback for daemons that crashed before
acking. That design was unsound. An ack announces a daemon's intent to
leave while it is still a live participant in the DVM, but the state the
launch fence protects against -- a job being mapped onto, or having launch
data sent to, a departing daemon -- is only safe once the daemon's routes,
num_daemons count, and node state have been torn down. That teardown
happens on the comm-failure path, not when the ack is sent, so releasing
held jobs on ack receipt could unpark them into a DVM that still believed
the departing daemon was present.

Worse, the two decrement paths double-counted. Because nothing marked a
target as already counted and a campaign was only removed once its pending
count hit zero, a target could be decremented once when its ack arrived and
again when its subsequent death was detected. For a two-target campaign the
first daemon's ack-then-death could drive pending to zero on its own,
completing the campaign and releasing the fence while the second target was
still present -- reopening the very race the fence exists to close.

The reason a daemon departs carries no useful information: a clean shrink
exit and a crash have identical consequences, since the node is being
removed either way and the processes beneath the daemon die with it. So the
ack is removed entirely -- the daemon-side send, the PRTE_PLM_SHRINK_ACK_CMD
constant, and the HNP-side handler. Campaign completion is now driven solely
by actual daemon departure on the comm-failure path, which is both the
authoritative event and the point at which the relevant cleanup has
occurred. Each matched target slot is stamped PMIX_RANK_INVALID once counted
so a daemon that emits more than one failure event cannot decrement twice.

The implementation plan and state-machine documentation are updated to match,
and a design-decision section records why the ack approach was abandoned.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
A RAS component that handles a grow or shrink request and fires
PRTE_JOB_STATE_LAUNCH_DAEMONS itself bypasses the bookkeeping that the
DVM launch fence depends on: setting PRTE_JOB_EXTEND_DVM and resetting
the nidmap flag on grow, and recording the shrink campaign and raising
prte_dvm_launch_fence before any daemon is asked to leave on shrink.
Skipping that common handling leaves the fence out of step with the
campaign it is meant to gate, reopening the daemon-launch race.

Document that modify results must be routed through
prte_ras_base_complete_request(), and note that the Slurm scheduler-push
path currently fires the state directly, so readers understand the
hazard and new modules follow the correct pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The DVM grow (daemon-launch) path encoded an in-progress launch as a
single PRTE_JOB_LAUNCHED_DAEMONS boolean on the daemon job with a
one-per-campaign launch-fence bump. Because that token carried no
identity, two faults followed. An unrelated daemon dying while a grow
was in flight consumed the campaign's token: it released the jobs held
at the VM_READY to MAP boundary early, reopening the race the fence
exists to close, and cleared the token so vm_ready then skipped the
WIREUP xcast, leaving the genuinely new daemons unwired. Two overlapping
grows shared the single token, so a failure could leave the fence stuck
above zero and the held jobs parked indefinitely.

Track each grow campaign explicitly, recording the specific daemon ranks
being launched in a prte_grow_campaign_t, mirroring the rank-tracked
prte_shrink_campaign_t already used by the shrink path. The campaign's
fence contribution is held in full until the campaign is drained as a
unit: on success in vm_ready, after the WIREUP xcast, so jobs held at
VM_READY are admitted only once the new daemons are wired up; or on
failure when one of the launched ranks dies. A daemon loss only affects
the fence when its rank belongs to an in-progress campaign, so unrelated
deaths no longer consume it, and concurrent campaigns are independent
objects that cannot wedge the fence.

A separate prte_grow_campaigns list is used rather than unifying with
prte_shrink_campaigns because the LAUNCH_APPS hold and remap-on-release
logic must remain shrink-only; a grow must not stall jobs already mapped
onto existing nodes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The launch-fence plan still described the grow path's fence accounting as a
single PRTE_JOB_LAUNCHED_DAEMONS boolean plus a bare prte_dvm_launch_fence
increment and decrement, with per-error-exit decrements in vm_ready, the
errmgr, and check_complete. That design was superseded by the per-campaign,
rank-tracked accounting now documented in the grow-campaign plan and
implemented in the tree, so the old text contradicted the code: error-exits
deliberately leave the fence to DVM teardown, failures route through
prte_plm_base_grow_target_failed(), and check_job_complete drains via
prte_plm_base_grow_drain().

Reduce the grow-specific steps, the grow file-change table, and the grow
design invariants to pointers into the grow-campaign plan, keeping only the
shared fence mechanism and the shrink-path content that remain accurate.
Also fix a dangling cross-reference that pointed at a nonexistent Step 12.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
A rebase conflict resolution left the implementation-plans toctree listing
per_app_mapping/index.rst twice, which Sphinx flags as a duplicate document
reference. Remove the stray second entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The elastic_dvm plan directory carried only implementation plans for the
DVM launch fence and grow-campaign tracking, with no companion document
stating the externally observable contract those mechanisms uphold.  The
node_reservation directory pairs each plan with such a spec, and the same
separation is wanted here: a reader (or a future implementor) needs to
know what the runtime guarantees a caller, independent of how the fence
and campaign accounting deliver it.

Add elastic_dvm_spec.rst describing the job-admission and placement
guarantees in effect while the DVM grows or shrinks: non-destructive
deferral of a job submitted during a size change, the two placement
hazards that are closed, the remap-onto-survivors behavior, completion
driven by actual daemon departure, the failure and concurrency
semantics, and the single observable artifact (the
WAITING_FOR_DAEMONS job state).  List it first in the directory index and
have both plans reference it as authoritative for observable behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The elastic-DVM admission spec previously said only that a job whose grow
dependency fails is aborted; it was silent on the fate of the daemons the
failed grow had already started.  Leaving them running would strand the
DVM in a half-extended state — a partial, un-wired set of new daemons
that no job can safely use and that no later event reclaims.

Specify that a failed grow is rolled back as a whole: the campaign's
already-started daemons are terminated and the nodes it was adding leave
the DVM, restoring the exact membership the DVM had before the campaign
began.  The rollback is scoped to the failed campaign, so a concurrent
grow and all pre-existing daemons are untouched.  Record the guarantee in
the spec's grow-failure behavior, failure-semantics table, scope, and
conformance summary, and reconcile grow_campaign.rst with it: the
targets array now also enumerates the daemons to tear down, the failure
drain step rolls the campaign back via the shrink-path machinery before
draining held jobs, and a new section details the teardown including the
FAILED_TO_START edge case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
PR #2472 review observed that draining a shrink campaign one daemon at a
time drives a separate routing-tree repair per departure, so removing
several daemons along one branch of the radix tree can trigger repeated
promotions that may prove costly for a large shrink.

Record the suggested collective alternative as an explicit follow-up in
the launch-fence plan: confirm the broadcast once, then report all of the
campaign's targets as failed in a single batch repair, with the doomed
daemons exiting on lifeline disconnect rather than self-exiting.  Explain
why this preserves the plan's "act on teardown, not on intent" invariant
and is therefore distinct from the per-daemon acknowledgement the plan
already rejected, and why it is deferred out of the launch-fence work: it
is an optimization rather than a correctness fix, needs a new
xcast-completion callback, relocates the completion trigger, and exercises
the less-tested whole-branch tree-repair path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
A dynamic allocation that grows or shrinks the DVM is answered as soon as
the request has been processed, not when the resulting daemon launch or
teardown actually finishes.  The spec previously described only the
job-admission side of a size change and was silent on how the requester
learns that the asynchronous operation completed — leaving "the allocation
is complete" and "the runtime is ready" conflated.

Separate the two points explicitly.  Specify a two-phase model: a
synchronous response on request acceptance, followed by exactly one
asynchronous, requester-directed terminal event when the DVM operation
finishes — PMIX_DVM_IS_READY on success and PMIX_ERR_DVM_MOD, carrying the
underlying cause, on failure.  Define each event's allocation-identifying
payload, the delivery guarantees (no event for a phase-one rejection, for
a no-op request, or for a requester-less scheduler push), and propose the
two new PMIx event codes the contract requires, gated by a PMIx capability
check.  Broaden the document accordingly: retitle it from "Job Admission"
to cover both contracts, add the supporting definitions, and record the
clean degradation when the underlying PMIx lacks the codes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The single dvm_launch_fence.rst plan mixed the shared launch-fence
infrastructure with the shrink path's specifics while the grow path lived
in its own file, an asymmetry that made the shrink design hard to find and
left no parent tying the three together.  The spec also now requires a
phase-two completion event that neither plan described emitting.

Restructure the plan tree for symmetry and reconcile it with the spec.
Rename dvm_launch_fence.rst to elastic_dvm_plan.rst as the parent, holding
only the shared mechanism — the fence counter, held-job arrays, the
VM_READY hold point, the fence-release helper, and a new shared
completion-event helper — and linking to the two campaign plans.  Extract
the shrink-specific design into shrink_campaign.rst alongside the existing
grow_campaign.rst, renumbering its steps and pointing its shared
references at the parent.

Thread the spec's two-phase completion contract through both campaign
plans: each campaign object records the requester, allocation id, and
request id, and emits PMIX_DVM_IS_READY when its operation completes or
PMIX_ERR_DVM_MOD (with the underlying cause) when it fails, via a shared
prte_plm_base_dvm_mod_notify() helper gated by a PMIx capability check.
Update the index toctree and retarget the spec and state-machine
cross-references at the new labels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
…p flag

The plan and spec said the PMIX_DVM_IS_READY / PMIX_ERR_DVM_MOD event
codes would be gated by the PRTE_CHECK_PMIX_CAP capability-flag idiom.
That is heavier than needed: PMIx status codes are plain preprocessor
"#define"d pmix_status_t values, so whether the installed PMIx supplies a
code is decided entirely by the symbol's presence in the headers, which
the preprocessor can test directly.  A PMIX_CAP_* flag is for behavioral
features a single compile-visible symbol cannot capture; PRRTE only emits
these events and a receiver opts in by registering for the code, so no
such flag applies.

Reword both documents to guard the helper and its call sites with a simple
preprocessor existence check.  To keep the project's "#if FOO" discipline,
recommend a one-line m4 probe that sets PRTE_HAVE_DVM_MOD_EVENTS to 0/1
from the symbols' presence, while noting a direct "#if defined(...)" guard
is equally correct, and mark the config/prte_setup_pmix.m4 touch as
optional.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The plan left the completion-event guard as a choice between a
PRTE_HAVE_DVM_MOD_EVENTS macro and a direct "#if defined(...)".  Settle on
the macro form so the project keeps a single "#if FOO" guard whose typo is
a compile error, and make the plan prescriptive: give the concrete
AC_PREPROC_IFELSE probe that defines PRTE_HAVE_DVM_MOD_EVENTS to 0 or 1
from the symbols' presence, drop the "optional" marking on the
config/prte_setup_pmix.m4 touch, and note the autogen.pl re-run.

Localise the guard at the same time.  Give the shared notify helper a
"bool success" parameter instead of an event-code argument, so the two new
status codes are named only inside the helper body; call sites pass a bool
and a cause and need no guard, leaving just the helper body under
"#if PRTE_HAVE_DVM_MOD_EVENTS".  Update the grow and shrink call sites
accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The elastic-DVM spec promises (conformance guarantee #4) that a daemon
failure affects only the jobs waiting on the campaign that failure
belongs to.  The implementation plan released held jobs through a single
prte_plm_base_fence_release(bool success) keyed on the global fence
reaching zero, which could not honor that promise when a grow failed
while a shrink was still in progress: a single global success flag cannot
express "abort the grow's waiters but leave the shrink's waiters parked,"
and deferring the abort until the fence reached zero let a later
shrink-success release admit a job whose grow dependency had already
failed.

Split the release into a success-only prte_plm_base_fence_release() and a
grow-failure prte_plm_base_abort_premap_held() that fails only the pre-map
held jobs, immediately and independent of the fence, leaving the
shrink-only pre-launch held jobs parked.  Pre-map jobs wait on every
in-progress campaign including the grow; pre-launch jobs wait only on a
shrink, so this is exactly what guarantee #4 requires.

Also guard shrink-campaign creation on a nonzero target count so an empty
release cannot leave prte_shrink_campaigns permanently non-empty and wedge
every later job at the LAUNCH_APPS hold, mirroring the grow path's
num_new_daemons guard; consolidate the grow helpers into
plm_base_launch_support.h with the other launch-fence helpers; and have
the defensive campaign sweep cover both the grow and shrink lists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The launch-fence plans named src/mca/plm/base/plm_base_launch_support.h
as the home for the shared and per-campaign helper declarations, but no
such header exists.  The shared helpers are called from the errmgr, ras,
and state components, which live outside plm-base and therefore include
the public plm-base header src/mca/plm/base/base.h (where the existing
prte_plm_base_* entry points such as prte_plm_base_launch_apps are
declared); plm_private.h is private to plm-base and not visible to them.

Point every declaration reference at base.h so the plan matches the tree
and the callers can actually see the prototypes.  The definitions remain
in plm_base_launch_support.c, which does exist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The previous commit moved the helper declarations to base.h on the
premise that the errmgr, ras, and state callers could not otherwise see
them.  That premise was wrong: the existing launch-fence implementation
already declares these helpers in src/mca/plm/base/plm_private.h, and
errmgr_dvm.c and state_dvm.c already include that header to reach the
existing prte_plm_base_* entry points.  Point the plans at the header the
code actually uses, so the documentation matches the tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
…ease

Bring the launch-fence implementation up to the revised elastic-DVM
plans.  Two things were missing relative to the spec, and one shared
helper conflated outcomes it must keep separate.

First, the two-phase completion contract had no realization: an accepted
grow or shrink returned its synchronous allocation response but never
told the requester, asynchronously, that the DVM operation had actually
finished.  Add prte_plm_base_dvm_mod_notify(), which directs a
PMIX_DVM_IS_READY event to the requester on success or PMIX_ERR_DVM_MOD
(carrying the underlying cause) on failure, mirroring the custom-range
delivery used for PMIX_ALLOC_TIMEOUT_WARNING.  The two status codes are
plain #defines, so a configure probe defines PRTE_HAVE_DVM_MOD_EVENTS
from their presence in the installed PMIx headers; when absent the helper
compiles to a no-op and the runtime simply omits the notification.  Both
campaign objects now record the requester / PMIX_ALLOC_ID /
PMIX_ALLOC_REQ_ID; the shrink path captures them from the originating
PMIX_ALLOC_RELEASE and emits on completion (errmgr) and on xcast-failure
(ras).  The grow path carries the fields but leaves have_requester false
for now, because the originating allocation request is not yet threaded
through to setup_virtual_machine; wiring that is a follow-up and does not
affect fence accounting.

Second, prte_plm_base_fence_release(bool success) could not honor the
spec when a grow failed while a shrink was still in progress: a single
global success flag cannot abort the grow's waiters while leaving the
shrink's waiters parked, and gating the failure on the fence reaching
zero let a later shrink-success release admit a job whose grow dependency
had already failed.  Split it into a success-only fence_release() and a
grow-failure abort_premap_held() that fails only the pre-map held jobs,
immediately and independent of the fence; the pre-launch (shrink-only)
held jobs are never touched by a grow failure, per the spec's
campaign-scoped failure guarantee.

Also guard shrink-campaign creation on a nonzero target count so an empty
release cannot leave the campaign list non-empty forever and wedge later
jobs at the LAUNCH_APPS hold, and have the defensive campaign sweep cover
both the grow and shrink lists.

Because config/prte_setup_pmix.m4 changed, autogen.pl must be re-run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The grow campaign carried the requester fields for the phase-two
completion event but always left have_requester false, so a grow emitted
no PMIX_DVM_IS_READY / PMIX_ERR_DVM_MOD even when it was driven by a
dynamic allocation request.  The missing link was that the originating
request is not passed to setup_virtual_machine(), where the campaign is
created.

It does not need to be passed explicitly: the RAS reservation machinery
already records the driving request on the session and back-points every
reserved node at it (add_nodes_to_session sets node->session, and the
session carries requestor, alloc_refid, and user_refid).  Read those from
the first new daemon's node when building the grow campaign.  The initial
DVM bring-up and a scheduler-driven push have no such requestor — the
default session, or an invalid requestor rank — so have_requester stays
false and no event is emitted for them, exactly as the spec requires.

With this, both the grow and shrink paths deliver the spec's phase-two
completion event to the process that requested the size change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
Update the grow and shrink plans to describe what was actually built.

The grow campaign records its requester indirectly: the RAS reservation
machinery back-points each reserved node at the session that carries the
driving request, so setup_virtual_machine() reads the requestor and the
allocation ids from the first new daemon's node->session rather than
having the request threaded into the launch path.  Document that, and the
parallel shrink capture from req->tproc / req->info, replacing the earlier
pseudocode that referenced locals the code does not have and line numbers
that have since drifted.

Note that the failure cause in PMIX_ERR_DVM_MOD is carried under
PMIX_JOB_TERM_STATUS, and that the completion event is delivered as a
directed custom-range notification.

Most importantly, mark the grow failure rollback as not yet implemented:
grow_target_failed() currently only drains the fence and aborts the
pre-map held jobs, so a failed grow can still leave the DVM half-extended
and does not yet satisfy the spec's conformance guarantee #5.  The
"Rollback on failure" section is retained as the specification for that
remaining work and is now the primary item in the grow Follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
A grow that lost a daemon previously drained the fence and aborted the
held jobs but left the partially-launched daemons running, so a failed
grow could leave the DVM half-extended — violating the spec's conformance
guarantee #5.  Worse, the errmgr's general daemon-loss path would abort
the entire DVM when a routeless new daemon failed to start, killing a
running DVM over a grow that should simply have been rolled back.

Make grow_target_failed() roll the matched campaign back out of the DVM.
The new grow_rollback() repairs routing for the triggering rank, then for
each other target either terminates it with the same PRTE_DAEMON_SHRINK_CMD
xcast the shrink path uses (when the daemon actually started) or reverts
its launch-time daemon-count bump (when it never started), and clears each
node's daemon backpointer so the mapper can no longer place a job there —
the new nodes carry no application procs, since the jobs that would have
used them were held at the fence and never launched.  The teardown is
campaign-scoped, so a concurrent grow keeps its daemons and completes
normally.

grow_target_failed() now returns whether the loss was a grow target, and
the errmgr gotos its cleanup on true so the general daemon-loss handling —
which would abort the whole DVM — is skipped; the rollback has already
absorbed the loss.  The failure is now per-campaign rather than draining
every grow at once, so an unrelated concurrent grow is no longer dragged
down with the failed one.

Two edges remain in the rarely-exercised launch-failure path, documented
in the plan: a slow-to-start target is treated as never-started, and nodes
are detached via node->daemon rather than removed from prte_node_pool (so
num_nodes is not decremented), matching how the shrink path leaves the
pool.  None of this is yet validated against a real multi-node allocation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The launch-fence, campaign-tracking, and grow/shrink completion machinery
was active on every DVM.  That changed fixed-size DVM behavior: initial
bring-up launches daemons, which created a grow campaign and raised the
fence, and a daemon loss during a grow was then absorbed as a campaign
event — most consequentially, grow_target_failed() would report the loss
as handled and the errmgr would skip its normal abort, so a daemon dying
on an ordinary DVM no longer brought the DVM down as it used to.

Gate the whole feature on the pre-existing prte_elastic_mode MCA
parameter (off by default), which already governs the related decision to
pass SLURM --no-kill so daemon losses are tolerated.  The gate is applied
where the fence is raised — grow-campaign creation in
setup_virtual_machine() and shrink-campaign creation in the
PMIX_ALLOC_RELEASE handler — so outside elastic mode the fence stays zero
and the campaign lists stay empty, leaving every downstream check inert.
The consumer sites (the VM_READY and LAUNCH_APPS holds, the vm_ready
drain, and grow_target_failed()) also carry an explicit guard so the
non-elastic path is provably identical to the pre-feature behavior:
grow_target_failed() now returns false immediately when elastic mode is
off, so a daemon loss on a fixed-size DVM follows the ordinary errmgr
abort path exactly as before.

The PMIX_ALLOC_RELEASE xcast still fires when not in elastic mode, so an
explicit release retains its legacy fire-and-forget behavior; only the
campaign/fence/parking is suppressed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The grow path's phase-two completion event (PMIX_DVM_IS_READY) was never
delivered to the requester. prte_ras_base_complete_request() registered the
newly reserved nodes with their destination session by calling
add_nodes_to_session(&ndlist, dest) -- but it did so only after
prte_ras_base_node_insert(&ndlist, NULL), which drains ndlist (it removes and
releases every item as it folds them into the global node pool). The
registration loop therefore iterated an empty list and set no session
backpointer at all.

That backpointer is the sole carrier of the requestor identity from the RAS
reservation across to the launch-side grow campaign: setup_virtual_machine
reads the new daemon's node->session to populate the campaign's requester, and
grow_drain() emits the completion event only when a requester is present. With
the backpointer perpetually NULL, have_requester stayed false and the event was
silently dropped, leaving any client that issued the allocation request waiting
forever for a completion it would never receive.

Snapshot the node names before the insert and resolve the pool objects by name
in add_nodes_to_session(), which already used prte_node_match() rather than the
list objects themselves. This was found and verified with a multi-node
container DVM: the requester now receives PMIX_SUCCESS followed by
PMIX_DVM_IS_READY.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
prte_shrink_campaign_t and prte_grow_campaign_t were declared with a NULL
constructor, so PMIX_NEW returned an object whose alloc_id and req_id pointers
held uninitialized memory. The shrink creator in prte_ras_base_complete_request
sets those two fields only when the release request actually carries
PMIX_ALLOC_ID / PMIX_ALLOC_REQ_ID; a release that names nodes but omits one of
them therefore left the corresponding pointer pointing at garbage. When the
campaign was later released, campaign_destruct() called free() on that garbage
pointer and corrupted the heap, aborting the HNP with a malloc consolidation
error mid-shrink. The grow path happened to escape this only because
setup_virtual_machine assigns every field by hand.

Give both campaign classes a constructor that NULLs targets, alloc_id, and
req_id (and zeroes the counts and requester) so an unset identifier is a safe
free(NULL) rather than a wild pointer. Found with a multi-node container DVM:
releasing a node now leaves the HNP healthy and removes only the named daemon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
A tool's PMIX_ALLOC_NEW/EXTEND/RELEASE request reaches the DVM master and is
dispatched across the selected RAS modules by prte_ras_base_modify. The
ras/pmix module forwards such requests to an external PMIx scheduler, but when
none is attached prte_pmix_set_scheduler() fails and the module returned that
hard error (e.g. PMIX_ERR_UNREACH) directly. Because the modify loop treats any
non-SUCCESS, non-TAKE_NEXT status as a fatal error and breaks, that aborted the
whole request before the lower-priority ras/hosts module was ever consulted --
so an elastic DVM running without a scheduler could not grow or shrink at all,
and the only way to drive it was the ras/pmix "simulate" shortcut, which
discards the request's node list and allocation ids and so cannot target a
specific reservation for release.

Make ras/pmix yield with PMIX_ERR_TAKE_NEXT_OPTION when no scheduler is
reachable, allowing the loop to fall through to the next module, and teach
ras/hosts -- the lowest-priority, last-resort local resource authority -- to
claim the PMIX_ALLOC_NEW/EXTEND/RELEASE directives so the base
complete_request logic runs with the original request info intact. Scheduler
deployments are unaffected: ras/pmix still forwards whenever a scheduler is
attached, and ras/hosts is reached only after it declines. This makes
schedulerless grow and shrink testable with real node lists and reservation
ids instead of the simulate shortcut.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
The ras/pmix simulate option faked a scheduler interaction by injecting a fixed
node list (ras_pmix_simulate_nodelist) in place of the request, returning it as
though a scheduler had supplied it. It was only ever a stand-in for exercising
the elastic grow path without a real scheduler, and it was a poor one: because
it overwrote the request, it discarded the caller's node list and allocation
ids and so could not target a specific reservation for release.

Now that ras/hosts satisfies node-list grow and shrink locally, with the
original request info intact, the simulate shortcut is redundant. Remove the
two MCA parameters (ras_pmix_simulate, ras_pmix_simulate_nodelist), their
component fields, and the simulate branch in modify(); a schedulerless DVM is
driven directly through ras/hosts instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
rhc54 and others added 2 commits June 30, 2026 11:04
The shrink-campaign design doc carried a lengthy follow-up note describing
a possible optimization: repairing the routing tree once per shrink campaign
instead of once per departing daemon. This is an optimization rather than a
correctness fix and is not part of the launch-fence work, so it does not
belong in the design doc going forward.

Move the note's content to #2492 so it is tracked as actionable
work, and leave a short pointer to that issue in its place.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
A PMIX_ALLOC_RELEASE that shrinks the DVM can name nodes drawn from more
than one underlying allocation, so no single RAS component owns the whole
set of departing resources. To let each component hand back the share it
manages, add a shrink_complete entry point to the ras module interface
and a prte_ras_base_shrink_complete() helper that cycles across every
active module, passing the completed shrink campaign so the module can
identify which release finished.

The errmgr drives this at the campaign-completion point: once the last
targeted daemon has departed, it offers the campaign to every module
before emitting the requester's completion notification. The runtime
guarantees only the ordering — each component is given the opportunity to
release before PMIX_DVM_IS_READY fires — not that any particular resource
is returned, which remains the component's decision. No component
implements the hook yet, so the cycle is currently a no-op.

The elastic-DVM spec and plan are updated to document the multi-allocation
case and this new step in the shrink workflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Ralph Castain <rhc@pmix.org>
@github-actions

Copy link
Copy Markdown
Author

Hello! The Git Commit Checker CI bot found a few problems with this PR:

049db08: contrib: add a Docker harness for elastic-DVM grow...

  • check_cherry_pick: contains a cherry pick message that refers to a commit that exists, but is in an as-yet unmerged pull request: b4e427c

Please fix these problems and, if necessary, force-push new commits back up to the PR branch. Thanks!

@hppritcha
hppritcha merged commit d29a3ce into open-mpi:master Jun 30, 2026
17 of 21 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