Repository navigation
grpcomm: a survivable job's fences stop waiting for departed processes - #2881
Merged
Merged
Conversation
A fence over a whole namespace - PMIx_Fence(NULL, 0), and so MPI_Finalize - counts every daemon hosting one of its processes, and a daemon contributes once its PMIx server has heard from every local participant. The PMIx server takes a terminated client out of that count, so a daemon that loses some of its processes still contributes. One that loses all of them has nobody left to call the fence and never contributes, so the rollup waited for it forever. A job that ends at its first loss never noticed; in a survivable one - a ULFM application - every survivor hung in MPI_Finalize. Each daemon now reports the termination of a process it hosts up the routing tree (PRTE_RML_TAG_FENCE_DEPARTED), and passes up what its children report. A rollup short of its count then asks who it is still waiting for: a participant is awaited unless it has departed, its daemon has failed, or its subtree has reported. Up the tree rather than by broadcast because a process that called the fence and then died has its daemon's contribution on the same link, ahead of the news of its death; and a tracker records which subtrees reported, so a subtree that reported and then lost its processes is never counted twice. The HNP records only the processes it hosts, for the same ordering reason. Only for PRTE_JOB_RECOVERABLE or PRTE_JOB_CONTINUOUS jobs, and the fence's status is left alone: a departed process is no longer a participant, as the PMIx server treats a whole-job fence started after a client died, and failing the fence would make every survivor discard the data the survivors contributed. Nothing is spent when nothing has departed. A recovery restart resends the set to the new parent. test_fence_departed in test/unit/grpcomm covers it over a hand-built routing tree; with the new rule disabled seven of its checks fail. Signed-off-by: Ralph Castain <rhc@pmix.org>
A daemon told PMIx nothing when one of its clients terminated, and left it to the socket close - which the server reads as a loss only when the client had not finalized. A rank that finalized and then exited therefore stayed an expected participant in its job's collectives until the whole job was deregistered, and a whole-job fence its peers made meanwhile waited for it. The HNP has always deregistered the clients it hosts; the daemons now do the same, without blocking, from the termination accounting. Only against a PMIx that advertises PMIX_CAP_DEREG_DEPARTS. Against one that does not, deregistering a client before the server has read its closed socket marked the rank finalized without taking it out of any collective - the very hang this avoids - so there the daemon leaves it to the socket close, as before. Signed-off-by: Ralph Castain <rhc@pmix.org>
state/prted's job_teardown gives up a job's map when this daemon's own processes of the job have all terminated, and keeps the job object - the job goes on elsewhere. But the daemon still relays the job's collectives for its subtree, and create_dmns() refused any fence over a job with no map on a daemon other than the controller. So the contribution of every daemon below one whose processes had finished was dropped with "Not found", and a fence over the job hung for good. Only a wildcard needs the map: a named rank is already resolved through the job's processes. Resolve a wildcard the same way when the map is gone - every process stays placed on its node, and so on its daemon, until the DVM says the job is over. A daemon that has not been told of the job's processes yet has nothing to walk and is refused as before. Seen on a four-daemon DVM with rml_base_radix 2 and a survivable job whose rank on daemon 1 - the parent of daemon 3 - was killed: the survivors' next whole-job fence never completed. test_fence_tracker_unmapped_daemon fails without this. Signed-off-by: Ralph Castain <rhc@pmix.org>
The HNP drops an emptied node from a job's map itself, so a DVM in which it is every daemon's parent - anything up to rml_base_radix + 1 daemons, 65 by default - never showed the hang the departed set fixes. Record that, the relay-map fix beside it, and how to reproduce both on four nodes. Signed-off-by: Ralph Castain <rhc@pmix.org>
A process the runtime restarts under its old rank is a participant again, but the departed set still holds its departure - and that is the dangerous error: a survivable job's fence would stop waiting for the live process and complete without it. prte_grpcomm_proc_restarted() clears the departure and sends a return up the routing tree, on the same links the departure took, so it reaches every daemon that heard of it and reaches each ahead of anything the new incarnation contributes. restart_proc calls it before the new process exists, and also registers the rank with the PMIx server again: termination deregisters the client - on the HNP as on a daemon - and the server refuses a rank it does not have, so without this the restarted process could not initialize; with a PMIx that has PMIX_CAP_DEREG_DEPARTS it is also what counts the rank back into the server's collectives. A return can be lost with a failed daemon, leaving a stale departure on an ancestor that a repaired tree may make our new parent. So a recovery now rebuilds the set rather than topping it up: every daemon drops what others reported, keeps its own processes' departures and reports those again. Notices carry the recovery epoch, so one from before the recovery is dropped and one from after it that overtakes our own notice of the recovery is applied after our rebuild, not wiped by it. The termination hooks move outside the RECORDED guard on a daemon, and the grpcomm one on the HNP: each is a no-op the second time for one incarnation, and a restarted process's next termination has to be reported whether or not the restart reset RECORDED, which is left to whoever rewires restart_proc. test_fence_departed covers the return, the rebuild and the epoch; with either half disabled, its checks fail. Signed-off-by: Ralph Castain <rhc@pmix.org>
This was referenced Nov 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The DVM half of making a survivable job's fences stop waiting for processes that have gone - a ULFM job that lost a process hung in
MPI_Finalize. The PMIx half is openpmix/openpmix#4391.A daemon whose processes have all departed
A whole-namespace fence counts every daemon hosting one of the namespace's processes, and a daemon contributes once its PMIx server has heard from every local participant. The PMIx server takes a dead client out of that count, so a daemon that loses some of its processes still contributes. One that loses all of them has nobody left to call the fence and never contributes, and its parent's rollup waited for it forever.
Each daemon now reports the termination of a process it hosts up the routing tree (
PRTE_RML_TAG_FENCE_DEPARTED), and a rollup short of its count asks who it is still waiting for: a participant is awaited unless it has departed, its daemon has failed, or its subtree has reported. Up the tree rather than by broadcast, because a process that called the fence and then died has its daemon's contribution on the same link, ahead of the news of its death; a tracker records which subtrees reported, so nothing is counted twice. OnlyPRTE_JOB_RECOVERABLE/PRTE_JOB_CONTINUOUSjobs, nothing is spent when nothing has departed, and the fence's status is left alone - failing it would make every survivor's PMIx server discard the data the survivors contributed.Why it went unseen: the HNP already drops an emptied node from its own copy of the job map, so in any DVM where it is every daemon's parent - up to
rml_base_radix + 1daemons, 65 by default - the hang never showed.A daemon whose own share of the job has ended
job_teardowngives up a job's map when the daemon's processes of the job are done, while the job runs on elsewhere - andcreate_dmns()refused any fence over a job with no map on a daemon other than the controller, dropping the contributions of every daemon below it. A wildcard is now resolved through the job's processes, as a named rank always was. This one is not specific to failures.Retiring and restarting clients
PMIX_CAP_DEREG_DEPARTS(new configure check); against an older one, deregistering before the server reads the closed socket causes the very hang this fixes.restart_procnow reports the return up the tree (prte_grpcomm_proc_restarted()) and registers the rank with PMIx again, before the new process exists - termination deregisters it, on the HNP today, and the server refuses a rank it does not have. A recovery restart rebuilds the departed set from each daemon's own processes rather than topping it up, so a return lost with a failed daemon cannot leave a stale departure; notices carry the recovery epoch. The termination hooks sit outside theRECORDEDguard, so a restarted process's next death is reported whether or not the restart resets it - that, andnum_terminated, are left for whoever rewires the restart path (noted insrc/mca/odls/AGENTS.md).Testing
test/unit/grpcomm:test_fence_departed(completion on departure, a restart, the rebuild, the epoch) andtest_fence_tracker_unmapped_daemon; with each fix disabled its cases fail.make check: 29 pass, 0 fail.--prtemca rml_base_radix 2 --rtos recoverable, one rank SIGKILLed after a fence, the survivors then fencing over the whole job: on master the survivors time out with nothing completed; with this, they complete withPMIX_SUCCESSwhichever rank is killed. No-death and the default-radix three-node case complete; a job that is not recoverable still aborts at its first loss. The harness's core and server suites pass.Not covered: the group collective over a whole namespace has the same shape, but whether it may complete without a member is the caller's (
PMIX_GROUP_FT_COLLECTIVE), so it needs its own policy. And a restart is unit-tested only, since nothing invokesrestart_procyet.