Carry out the session control directives PRRTE can honor - #2596
Merged
Conversation
prte_plm_base_prted_signal_local_procs() packed the address of its own parameter as the target namespace. A pmix_nspace_t is an array type, so as a function parameter it has decayed to a char* - and &job is therefore the address of that pointer variable, not of the name. Packing PMIX_PROC_NSPACE copies PMIX_MAX_NSLEN+1 bytes from whatever it is handed, so what went on the wire was a fragment of the caller's stack. Every daemon receiving the command then compared that garbage against its local children, matched none of them, and delivered the signal to nothing at all. The command was accepted, the xcast succeeded, and the target processes never saw the signal - which is why this survived: the only symptom is silence. Copy the name into real storage before packing it. The equivalent code in pmix_server_job_ctrl.c has always done so, which is why signals sent through PMIx_Job_control were unaffected. Signed-off-by: Ralph Castain <rhc@pmix.org>
prte_ras_base_teardown_reservation(), when told to hand a reservation's nodes back to the scheduler, collected the daemon rank of every member node and broadcast a shrink command naming them all. Nothing excluded the master's own daemon. A reservation may legitimately contain the head node - a single-node DVM whose whole allocation is carved into one session is the extreme case, but any allocation that includes the head node reaches it - and ordering our own daemon out of our own DVM terminates the DVM, taking every other job still running on it with it. The failure looks like the DVM simply vanishing at the moment an unrelated reservation is released. Skip our own rank when collecting shrink targets. The head node is detached from the reservation either way, so it reverts to the general pool, which is the correct outcome for the node the master is standing on. Signed-off-by: Ralph Castain <rhc@pmix.org>
pmix_server_alloc_request_resp() is how a non-master daemon completes an allocation or session-control request the DVM master serviced on its behalf. It invoked the requester's callback with the request TRACKER as the callback data, where the caller's own cbdata belongs. PMIx uses that value to find the operation it is completing, so it was handed a pointer it had never issued: the client sat in its PMIx call until it timed out, or the library faulted on the unrecognized pointer and took the daemon with it. Every allocation request and every session-control request issued from a process attached to a non-master daemon went this way. It survived because both APIs are normally driven from the head node, where the master answers directly and this relay never runs. Pass req->cbdata. Also clear the tracker's slot on the no-callback branch, which released the request but left the array pointing at it, and add traces on both halves of the relay - the response arriving and the request it names being gone are the two things worth seeing when a client hangs here. Signed-off-by: Ralph Castain <rhc@pmix.org>
prte_pmix_set_scheduler() is called at the head of every allocation and every session-control request the DVM master handles, and when no scheduler has attached it re-ran PMIx_tool_attach_to_server every time. That call blocks. It runs on the PRRTE progress thread, which is the only thread driving the DVM, and it hunts the filesystem for a rendezvous and tries to connect to whatever it finds. On a DVM with no scheduler - the ordinary case, and the one where it can only ever fail - that put a filesystem scan and a connect attempt in front of every such request, on the one thread that must never be spent. Look exactly once and remember the answer. Nothing is lost: a scheduler that appears later announces itself by attaching to us, and the tool-connect upcall records it, so the probe is only ever a startup convenience for a scheduler that was already there. Signed-off-by: Ralph Castain <rhc@pmix.org>
PMIx_Session_control is how a scheduler creates, operates on and reclaims a session - PRRTE's allocation object. PRRTE accepted the call and did almost none of it: every operational directive returned PMIX_ERR_NOT_SUPPORTED, and an instantiation built a session and a job object but never launched anything. A scheduler driving PRRTE therefore had no way to start work in a session it had created, and no way to pause, signal, extend or terminate one afterwards. Implement the whole surface. Instantiation now gives the session the nodes it was described with, extends the DVM across any that are new, and launches the applications it was given into it, answering with the session id, the allocation id and the launched namespace. Pause and resume stop and continue every job in the session; preempt and restore do the same for the jobs a PMIX_NSPACE qualifier names; signal delivers an arbitrary signal; extend adds nodes or revises the time limit; terminate kills the jobs and reclaims the session once they have retired. A session id of UINT32_MAX addresses every session the caller controls. Two of these cannot be done as the attribute describes them, and the deviations are deliberate. Preemption is suspension-based: recovering a preempted job's resources would require checkpointing it out and restoring it later, which PRRTE cannot do, and terminating it instead would make the paired PMIX_SESSION_RESTORE impossible to honor at all. And PMIX_SESSION_COMPLETE is refused as an inbound directive, because it is what an RTE says to its scheduler - PRRTE is the only party that knows when the jobs have finished. PRRTE now sends it, carrying each job's termination status, when a session the scheduler instantiated is reclaimed. A session created here is DETACHED by default, which is the opposite of an allocation reservation and is the point of the distinction. A reservation is nodes a tool took for its own use and dies with that tool; a session is named, addressable by anyone authorized, and destroyed by an explicit terminate, by its own time limit, or - when it was created to run apps - when those apps finish. Following the requester instead makes the API unusable for what it exists to do: the requester is a tool, its namespace lasts only as long as the request, and the session would be reclaimed out from under its own jobs the moment the caller disconnected. PMIX_SESSION_SEP given as false opts back into requester lifetime. The request is parsed in full before anything acts on it. An info array has no defined order, and the previous single-pass version mis-handled an instantiation that arrived ahead of its own PMIX_SESSION_JOB: the job was built and then never attached to the session. Conflicting operations are now refused rather than silently applied in array order. A DVM with no scheduler answers for itself instead of failing. The scheduler owns sessions when there is one, but refusing session control outright when there is not left the capability unreachable on an ordinary prte DVM - and unable to be tested. Requests are gated on the same ownership check that governs spawning into a reservation, so a caller can only act on an allocation it owns. The relay from a non-master daemon makes the same decision, so a caller gets the same answer whichever daemon it is attached to; that path runs the directive on the progress thread rather than inside the RML receive, and hands the request over holding the second reference send_alloc_resp's two releases consume. A scheduler may also impose a lifetime on a session (PMIX_ALLOC_TIME or PMIX_TIMEOUT); when it expires the session is terminated exactly as an explicit request would, and an extend re-arms it. What becomes of a reclaimed session's nodes follows its PMIX_ALLOC_INHERITANCE disposition, defaulting to the DVM's general pool: handing them back to the scheduler is the destructive choice and has to be asked for. Provisioning and any resource description that asks PRRTE to choose machines are refused, since PRRTE runs on the nodes it is given and is itself the thing being scheduled. examples/sessionctrl.c drives all of this against a running DVM, and the container harness uses it for the parts that only exist across nodes - a reservation actually withholding its nodes, a request relayed from a non-master daemon, a signal reaching the session's jobs everywhere they run, and the two ways a session ends. Signed-off-by: Ralph Castain <rhc@pmix.org>
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.
Fixes #2589
The problem
PMIx_Session_controlis the API a scheduler uses to create a session, operate onit while it runs, and reclaim it. PRRTE accepted the call and did almost none of
it: every operational directive returned
PMIX_ERR_NOT_SUPPORTED, and aninstantiation built a session and a job object but never launched anything.
So a scheduler driving PRRTE had no way to start work in a session it had just
created, and no way to pause, signal, extend or terminate one afterwards. The
placeholder in
docs/how-things-work/schedulers/flow_of_control.rsthas beenwaiting for this.
What this does
Implements the whole surface. Instantiation gives the session the nodes it was
described with, extends the DVM across any that are new, and launches the
applications it was given into it — answering with the session id, the allocation
id, and the launched namespace. Pause and resume stop and continue every job in
the session; preempt and restore do the same for the jobs a
PMIX_NSPACEqualifier names; signal delivers an arbitrary signal; extend adds nodes or revises
the time limit; terminate kills the jobs and reclaims the session once they have
retired. A session id of
UINT32_MAXaddresses every session the caller controls.A scheduler may impose a lifetime on a session (
PMIX_ALLOC_TIME/PMIX_TIMEOUT);when it expires the session is terminated exactly as an explicit request would.
What becomes of a reclaimed session's nodes follows its
PMIX_ALLOC_INHERITANCEdisposition, defaulting to the DVM's general pool — handing them back to the
scheduler is the destructive choice and has to be asked for.
The user-facing description of every directive, including where PRRTE deviates and
why, is the new
docs/how-things-work/schedulers/session_control.rst.Two deliberate deviations
Preemption is suspension-based. The attribute describes preemption as halting
the jobs and recovering their resources. Recovering them means checkpointing the
jobs out and restoring them later, which PRRTE cannot do — and terminating them
instead would make the paired
PMIX_SESSION_RESTOREimpossible to honor at all.So a preempted job stops and holds its resources, and a restored job resumes where
it was. A scheduler that genuinely needs the nodes back should terminate the
session.
PMIX_SESSION_COMPLETEis outbound only. It is what an RTE says to itsscheduler, not the other way round: PRRTE is the only party that knows when the
jobs have finished and the nodes are free. Inbound it is refused; PRRTE now sends
it — carrying each job's termination status — when a session the scheduler
instantiated is reclaimed.
Two design points worth review
A session created through this API is detached by default, which is the
opposite of an allocation reservation. A reservation is nodes a tool took for its
own use and dies with that tool. A session is named, addressable by anyone
authorized, and destroyed by an explicit terminate, by its own time limit, or —
when it was created to run apps — when those apps finish. Following the requester
instead makes the API unusable for what it exists to do: the requester is a tool,
its namespace lasts only as long as the request, and the session was being
reclaimed out from under its own jobs the moment the caller disconnected (this is
not hypothetical — it is what the multi-node harness caught first).
PMIX_SESSION_SEPgiven as false opts back into requester lifetime.A DVM with no scheduler answers for itself. The scheduler owns sessions when
there is one, but refusing session control outright when there is not left the
capability unreachable on an ordinary
prteDVM — and impossible to test. Requestsare gated on the same ownership check that governs spawning into a reservation, so
a caller can only act on an allocation it owns. The relay from a non-master daemon
makes the same decision, so a caller gets the same answer whichever daemon it
happens to be attached to.
Pre-existing bugs this uncovered
Each is a separate commit ahead of the feature, so they can be reviewed and
bisected on their own. The middle two were only ever reachable from a non-master
daemon, which is why they had survived: both APIs are normally driven from the
head node, where the master answers directly and the relay never runs.
prte_plm_base_prted_signal_local_procs()packed the wrong bytes as thetarget namespace. A
pmix_nspace_tis an array type, so as a parameter ithas decayed to a
char *— and&jobis the address of that pointer variable,not of the name.
PMIX_PROC_NSPACEcopiesPMIX_MAX_NSLEN+1bytes fromwhatever it is handed, so a stack fragment went on the wire, every daemon
matched no job, and the signal was silently dropped. Every signal delivered
through
prte_plm.signal_job()was lost;PMIx_Job_controlsignals wereunaffected (that path uses a real local array).
pmix_server_alloc_request_resp()answered a relayed request into the void.It passed the request tracker as the callback data where the original caller's
cbdatabelongs. PMIx uses that value to find the operation it is completing,so it was handed a pointer it had never issued: the client sat in its PMIx call
until it timed out, or the library faulted on the unrecognized pointer and took
the daemon with it. This affected every allocation request as well as every
session-control request issued from a non-master daemon.
prte_pmix_set_scheduler()re-probed on every request.PMIx_tool_attach_to_serveris a blocking call that hunts the filesystem for arendezvous and tries to connect to what it finds — and it ran on the PRRTE
progress thread, the only thread driving the DVM, in front of every allocation
and every session-control request. On a DVM with no scheduler (the ordinary case,
where it can only ever fail) that is pure cost on the one thread that must not be
spent. It now looks once; a scheduler that appears later announces itself by
attaching to us.
prte_ras_base_teardown_reservation()could shrink the master out of its ownDVM. When handing a reservation's nodes back to the scheduler it collected the
daemon rank of every member node, with nothing excluding our own. A reservation
may legitimately contain the head node, and ordering our own daemon to depart
terminates the DVM, taking every other job with it.
Testing
test/unit/prted/. (Writing it caught thatPMIx_Argv_splitdiscards emptytokens, so
":30"and"1::2"were being silently reinterpreted as validdurations rather than refused.)
make check— all suites pass.session_control.rstwired into thescheduler-integration section.
examples/sessionctrl.cdrives the full lifecycle:instantiate + launch, pause (procs observably in
T), resume, preempt/restore,signal, time-limit expiry, terminate, and the error cases.
test_sessionphase incontrib/dockerswarm(14 cases): a reservation actually withholding its nodes from a general job, a
request relayed from a non-master daemon, both nodes' procs stopping and
resuming, a signal reaching the session's jobs on every node they occupy, a
session created to run apps being reclaimed when they end, and a standing
reservation persisting until explicitly terminated.
Full suite: 395 passed, 0 failed, 2 skipped (both skips pre-existing PMIx
capability gates, unrelated).
The multi-node run is what found items 1–3 above, plus a self-deadlock of my own
making:
_send_alloc_respreleases the request twice — explicitly, and againthrough its caddy's destructor — so a new caller has to hand it over holding the
extra
PMIX_RETAINthe allocation branch already takes, or the last release runsthe destructor while the object lock is held. That path also has to be
thread-shifted out of the RML receive before it drives the PLM, as the allocation
branch does.
🤖 Generated with Claude Code