14. Known gaps and deferred work
Everything on this page is open. It inventories the issues found during the deep reviews of the PMIx source tree that were deliberately not fixed at the time they were found, the code that is believed correct but that no test reaches, and the directories no review has read. Each entry records what the problem is, why it was left alone, and what closing it would take.
Nothing here is closed, and no entry is a retrospective. Closed entries, the dated log of what each review pass found and retired, the list of things that look like bugs but are by design, and the things that are real and will not be done all live in Deep review notes. They are kept, because they stop the same ground being re-covered; they are kept there, so that this page reads as a work list.
Four kinds of entry appear here:
Open decisions — a real defect whose fix requires a judgement that the review was not entitled to make on its own (a change to a published attribute, a released API’s behavior, or the PMIx Standard).
Deferred work — a real defect whose fix is larger, riskier, or more entangled than the change it was found alongside.
Coverage gaps — code that is believed correct but that no test reaches, usually because reaching it needs fault injection or hardware the test environments do not have.
Review coverage — the directories the deep review has not yet read, and those whose code has moved far enough since their review that the coverage no longer holds.
Note
Each directory’s AGENTS.md carries the full reasoning for
the items found in it. Those files are orientation maps, not
work logs, so this page is the place that records an item as
still open.
Re-verify an entry before acting on it: an entry here is a lead, not a finding. Entries have been retired by a fresh read of the tree more than once — the code moves, and an entry written from a plausible reading of it rather than from watching it run is exactly the kind that goes stale. Every entry below was last checked against the tree on 2026-08-28.
14.1. At a glance
Open decisions — 3
Deferred work — 5
Coverage gaps — 21. No CI race detector; the switchyard’s
out-of-memory and finalize-race arms; a multi-namespace
PMIx_Disconnect; a spawn hosted by simptest; the
PMIx_Compute_distances reply path; two src/tool fixes (SIGCONT
stdin, late finalize reply); the PMIx_Init debugger-wait teardown;
leak validation of the process-set and resolve examples; pps against
a live process table; the compressed half of preg; psensor/file
drop counts; a pcompress module that fails to start; a blocking
psec handshake; psec/munge’s failed encode; plog/smtp (never
run at all); the pnet fabric calls; pnet/simptest’s end-to-end
launch; the TSD finalize ordering; gds/shmem3 on macOS; the
client-side tombstone generation; and three src/hwloc findings.
Each is listed in full under Coverage gaps.
Review coverage — 3 directories unread, plus src/common and four
lower-priority directories whose code has moved since their review.
Nothing outside src/ has been reviewed at all.
14.2. Open decisions
14.2.1. A fabric plane claimed asynchronously is not recorded
Found in the src/mca/pnet review (2026-08-19).
pmix_pnet_base_register_fabric records a tracker for a plane only when
a module answers PMIX_OPERATION_SUCCEEDED — the “already done, inline”
status. A module that claims the plane the asynchronous way, returning
plain PMIX_SUCCESS and calling back later, is never recorded, yet both
callers — src/server/pmix_server_fabric.c and
PMIx_Fabric_register_nb — read PMIX_SUCCESS as “claimed, wait for
the callback”. A later update or deregister on that plane then answers
PMIX_ERR_BAD_PARAM, because the scan finds nothing.
Which half is wrong is the decision, and it cannot be settled by reading
the tree: no shipped component implements register_fabric at
all — grep the four component directories and the slots are unset — so
there is nothing to test a change against, and choosing the async
contract now pins down the interface a real fabric component would have
to meet. Two loose ends belong to the same decision. fabric->module
is deliberately never set, so the branches in update_fabric and
deregister_fabric that cast it back to a pmix_pnet_fabric_t * are
dead and every lookup goes through the index/name scan — a tracker
pointer parked in a caller’s object would dangle the moment the fabric
was deregistered. And nothing frees pmix_pnet_fabric_t.payload:
ftdes releases only name, and the base never tells a component
its tracker died, so a component that parks an allocation there must free
it from its own deregister_fabric.
See “The fabric path is scaffolding” in src/mca/pnet/AGENTS.md.
14.2.2. Who owns an mca_base_* parameter
Found in the src/mca/pmdl review (2026-08-19). pmdl re-prefixes
every value it reads out of an MCA param file so it reaches the library
that will look for it — PMIX_MCA_, PRTE_MCA_ or OMPI_MCA_ —
and decides which by the parameter’s first segment. Two segments name
something in more than one library: mca (all three have an MCA base)
and pmix (OPAL carried a pmix framework of its own).
Half of that overlap is now settled, because one side of it was plainly
wrong: parse_file_envars was claiming those names out of the list
PMIx read from its own param files, so a value the user set for PMIx
— pmix_hwloc_topo_file is the concrete one — was renamed
OMPI_MCA_* and reached neither library. A parameter PMIx claims is
now left alone there.
The other half is left as it stands. process_param_file reads
openmpi-mca-params.conf, tests for a PMIx parameter first, and so
forwards mca_base_component_path from Open MPI’s file as
PMIX_MCA_mca_base_component_path. Note which name that is: PMIx
registers the variable as project pmix, framework mca, component
base, and the full name a param file and an envar carry leaves the
project off — so mca_base_component_path is PMIx’s own spelling of
it, and pmix_mca_base_component_path is the project-qualified long
name, which a param file also accepts (see var_set_from_file, which
matches either). Open MPI spells its equivalent the same way, which is
the whole difficulty: the unqualified name is genuinely ambiguous, and
nothing in the line says which library it was meant for. Claiming it for
PMIx has been the behavior for releases and a site may be relying on it.
Deciding it means deciding whether the two mca_base namespaces are
one setting or two.
The ompi component’s guide records the precedence as it stands.
14.2.3. A blocking PMIx_IOF_pull hands back no handle
Found re-reviewing src/common/pmix_iof.c (2026-08-27).
PMIx_IOF_pull’s registration id reaches the caller through
regcbfunc, and passing a NULL regcbfunc is what selects the
blocking form. So a caller who registers synchronously is never told
the id, and PMIx_IOF_deregister, whose first parameter is that id,
can never be called for it. The registration lives in
pmix_globals.iof_requests for the life of the process.
Closing it means either adding an OUT parameter to a released API —
which the backward-compatibility rules forbid outright — or defining an
attribute that carries the id back in the caller’s directives array,
which is the mechanism the Standard prefers but which is a Standard
change, not a library one. Recorded rather than repaired for that
reason.
14.3. Deferred work
14.3.1. The legacy resolve_peers() branch never fetches at WILDCARD
The pre-v3.2 branch of resolve_peers() no longer carries a dead
store — it assigned PMIX_RANK_WILDCARD and was then overwritten with
PMIX_RANK_UNDEF before anything read it, and that assignment is
gone — but what it meant to do is still not done. The branch now
varies only the key and ninfo, which is what it always really
did, and the legacy path resolves because try_fetch() retries an
UNDEF rank as WILDCARD. Fetching at WILDCARD
directly, as the branch intended, is still untried — that is a
behavior change on a path only a pre-v3.2 server exercises, and there is
none to test against.
14.3.2. PMIX_GET_POINTER_VALUES is honored by three shortcuts and nothing else
Found reviewing src/client/pmix_client_get.c (2026-08-22).
The attribute asks that “any pointers in the returned value point
directly to values in the key-value store”. process_request() obeys
it for the two requests it answers out of pmix_globals —
PMIX_PROCID returns &pmix_globals.myidval and PMIX_RANK
returns &pmix_globals.myrankval — and, inconsistently, not for
PMIX_VERSION_NUMERIC, which allocates. Every other path, local hit
or server round trip, hands back an allocated copy the caller must
release.
Closing it is not a fix but a change to the ownership contract
PMIx_Get(3) states, and one that has to answer a question the
shortcuts do not raise: a pointer into the datastore is only safe while
the datastore holds it, and gds/hash rewrites its tables on the
progress thread. The two shortcuts are safe because they point at
process-lifetime globals. Left as recorded behavior; the smaller,
separable piece is making PMIX_VERSION_NUMERIC agree with its two
neighbours.
14.3.3. A compressed blob’s length prefix is taken on trust
Found in the src/mca/pcompress review (2026-08-20) and left alone
deliberately.
Every component allocates the uncompressed length the blob’s 4-byte
prefix claims, before inflating anything. The prefix generally came
off a peer’s wire, so a five-byte blob whose prefix reads 0xFFFFFFFE
asks for a four-gigabyte allocation, which then fails or succeeds and is
thrown away when the payload turns out not to decode. The review closed
the case that was a memory error — a blob too short to hold the prefix
at all — but not this one, which is a resource question rather than a
correctness one.
The obvious guard is a maximum expansion ratio, and that is exactly why it was not written: DEFLATE tops out near 1032:1 while zstd’s is far higher, so any single cap either fails to constrain zstd or rejects legitimate zlib output. A per-component cap is possible; whether it is worth the interoperability risk is a policy decision, not a bug fix. Note also that the caller has already read the whole blob into memory by the time it gets here, so the amplification is bounded by what the PTL was willing to accept.
14.3.4. Fabric inventory collection is a stub
Found in the src/mca/pnet review (2026-08-19). No pnet component
collects inventory. opa’s collect_inventory carries a comment
about searching the topology for OPA NICs and then returns
PMIX_SUCCESS having added nothing; deliver_inventory returns
PMIX_SUCCESS. nvd’s goes one step further — it confirms that a
matching NIC exists on the node — but still reports no contents. The
plumbing is real, so a host that asks for fabric inventory gets a
successful, empty answer rather than an error; treat the capability as
unfinished rather than working.
Finishing it needs a decision the review was not entitled to make: what a
fabric inventory record contains, and how a component that has nothing to
report says so. The base has no decline convention on this path —
unlike allocate and setup_fork, it PMIX_ERROR_LOGs any
non-PMIX_SUCCESS return and abandons the fan-out to every component
behind it — so today “nothing here” and “collected everything” are the
same answer.
14.3.5. A server-wide envar hook that nothing fills
Found reviewing src/server/pmix_server.c (2026-08-23).
pmix_server_globals.genvars is declared as “argv array of envars given
to me for passing to all clients”, is statically initialized to NULL, and
is read in exactly one place — setup_fork_body() replays it into every
child’s environment. Nothing in the tree ever writes it. There is no
directive, no API argument and no environment variable that reaches it, so
a host has no way to say “add these to every client I fork” and the replay
loop is dead code.
This is an unbuilt feature rather than a defect, which is why it is here
rather than fixed: closing it means choosing the interface. The obvious
candidate is PMIx_server_register_resources — it already carries
host-supplied job-wide information into pmix_server_globals.gdata —
with the PMIX_SET_ENVAR / PMIX_ADD_ENVAR / PMIX_PREPEND_ENVAR
family that src/common/pmix_pfexec.c already parses for a spawn. That
also decides the harder half: whether a later registration replaces or
appends, and whether PMIx_server_deregister_resources has to take an
envar back out of children that have already been forked (it cannot).
The comment above setup_fork_body used to assert that the registration
path sets it; that has been corrected, and src/server/AGENTS.md says
the read site is not evidence of a writer.
14.4. Coverage gaps
Nothing in CI can detect a data race. The sanitizer job builds with
-fsanitize=address; ASan finds no races, and there is no ThreadSanitizer build anywhere. This became visible closing thePMIx_server_initordering question (2026-08-23): the library’s central threading invariant — thatpmix_globals, thegdstracker lists andpmix_server_globalsare touched only from the progress thread — is held up by reading code, and every claim that a particular path honors it is argued rather than measured.gds/hashtakes no lock at all, so a violation is heap corruption rather than a stale read, and the interleavings that would expose one are measured in microseconds and need two processes. A TSan configuration exercised by the tool and server tests is the missing instrument; until there is one, treat “this runs on the progress thread” as an assertion to be re-checked by hand whenever the code around it moves.The out-of-memory and finalize-race arms of the server switchyard’s host callbacks.
op_cbfunc,op_cbfunc2andresop_cbfuncinsrc/server/pmix_server_switchyard.ceach have two arms that do not thread-shift. Both were fixed to release the caddy they own, but neither is reachable from a unit test without fault injection — a failed allocation, or the progress thread stopping while a host completion is in flight. The same gap now covers the 32 dispatch arms that answerPMIX_ERR_NOMEMwhenPMIX_GDS_CADDYcannot allocate, and thePMIX_ERR_NOMEMarm ofPMIX_SERVER_QUEUE_REPLY(2026-08-24): both replaced a NULL dereference that killed the server, and both need an allocator hook to reach. An allocation-failure injection facility — a debug-build counter that makes the nthpmix_mallocfail — would close a good part of this directory’s untestable set at once; it is the single highest-value piece of test infrastructure the review has wanted.A multi-namespace
PMIx_Disconnectis not reached bymake check.test/test_cd.c— what the--test-connectruns drive — disconnects a process from its own namespace, so the loop indisconnect_cbfunc()that drops the other participants’ cached data never executes. Reaching it needs live processes in two namespaces, and therefore a real launcher, for the reason in the next entry. The path is verified today by runningexamples/dynamic.cunder a PRRTE DVM withPMIX_MCA_gds_base_verbose=1forwarded to the clients, so theGDS DEL NSPACElines can be seen coming from a client rather than only from the server’s deregistration. Closing this means a test that stands up a server and two client namespaces in-process, in the white-box style oftest/unit/server_fence.c.test/simple/simptestcannot host a spawn. Itsspawn_fncallsPMIx_server_setup_application(), a thread-shifting API, and then waits on the result — butspawn_fnis the host callback and runs on the server’s progress thread, so the fake launch deadlocks. Spawn coverage therefore lives in the multi-node suite rather than inmake check. Do not add arun_*.plthat spawns until that path is made asynchronous.The reply handling of the
PMIx_Compute_distancespair is not covered anywhere, and cannot be: two of the three paths are allocation failures and the third needs a server reply whose declared count exceeds its payload. Reaching the receive path at all requires a client whose local hwloc computation failed.Two fixes found in
src/toolship without regression tests because the conditions cannot be arranged inmake check: the SIGCONT handler for forwarded stdin needs a tty, and the late-finalize-reply guard needs a server that answers the finalize handshake more than five seconds late. Only the second is still insrc/tool; the stdin read event and its SIGCONT handler moved tosrc/common/pmix_iof.cwhen the tool was given the library’s forwarding instead of its own, and are untested there for the same reason.The debugger-wait teardown in
PMIx_Initis not exercised. Reaching it needs the debugger-stop key set on the namespace and thePMIX_READY_FOR_DEBUGnotification to then fail, which no test environment can arrange without fault injection.The process-set and resolve examples have never been leak-validated. Both hang in the test environments used so far, so valgrind is killed before
PMIx_Finalizeruns and the report is inconclusive.ppshas never been validated against a live process-table server. Its no-connect paths are covered by the tools smoke test; the proc-table rendering is not.A
pcompressmodule that fails to start has nothing to fail it with.pmix_compress_base_select()now degrades to the base’s no-op stubs when the winning module’sinit()returns an error, rather than failingPMIx_Init. No component implementsinit— all five leave the slotNULL— so reaching that branch needs a component written to fail on purpose. The behavior it replaced was equally unreachable; the branch is written for the first module that does implementinit.The compressed half of
pregis invisible to a build with no compression library.preg/compressdisables itself whenpcompresshas no module, so on such a build — a stock macOS developer tree among them —rawwins every encode, and neither theblob:framing inpreg_base_legacy.cnor thecompresscomponent is ever reached by a round trip.test/unit/pregcovers the framing with hand-built vectors, which is what a bounded decoder can be tested with, but the encode-decode pair only meet on a build that can compress:test_legacy_largesays which encoding it got, so a thin run reports itself rather than passing quietly. The real round trip was run undercontrib/dockerswarm(Linux, all four compressors), where the 5000-node case does take the blob path.The
psensor/filedrop-count and baseline semantics have no automated check. The fix stops a monitor from alerting before it has ever recorded a miss — a request with noPMIX_MONITOR_FILE_DROPSused to trip on the first sample of a perfectly healthy file. Distinguishing the fixed behavior from the broken one takes a monitor with a zero drop allowance watching a file that is being kept fresh, and zero tolerance means any scheduling hiccup or coarse filesystem timestamp granularity fails it. The heartbeat drop allowance is covered instead (run_monitor.pl’s capped monitor), because a request can be given an allowance no run can spend; there is no equivalent for “must not alert on the first look”.A handshake-model psec module blocks the progress thread for as long as its peer takes to answer.
PMIX_PSEC_SERVER_HANDSHAKE_IFNEEDruns inside theptlconnection handler, on the progress thread, with the socket deliberately still in blocking mode; a peer that connects and then stops writing pins that thread until the socket errors out. This is intrinsic to the wayptlsequences the connection handshake rather than anythingpsecchooses, and today the only handshake-model module isdummy_handshake, which is test-only. It becomes a real availability question the moment a genuine one is written, and the fix belongs inptl— a timeout on the handshake exchange, the wayhandshake_wait_timealready bounds the connect-ack. Recorded here so a new mechanism does not inherit it silently.psec/munge’s failed-encode path is still not executed. The rest of the component now is:test/unit/psec_credentials.cdrives every active credential module rather than a fixed list, so on a host with libmunge and a livemungedit examinesmungeon the same terms asnative. The PRRTE tree’scontrib/slurmswarmimage is such a host — it installslibmunge-devbefore it builds PMIx, so the component is compiled, and its entrypoint startsmunged— and the suite passes 62/62 there, valgrind-clean, with theinfo[n]defect confirmed to segfault it when reinstated. What no test reaches is the branch wheremunge_encodefails midway through a refresh, which is where the danglingmycredlived; provoking it means makingmungedfail on demand between twoPMIx_Get_credentialcalls.The plog
smtpcomponent has never been run. It builds only where libesmtp is present, and exercising it needs a reachable SMTP server on top of that, so the fixes from thesrc/mca/plogreview (2026-08-20) — an uninitializedmessage_status_t, acrnl()that emitted LF where it meant CR, a message callback that ended the message before the body when no prefix was configured — were verified by reading and compile-checked with--enable-test-buildagainst the shim header, not by sending mail. The shim makes every libesmtp call a stub, so a test-build tree cannot exercise them either. The first real user of this component should expect to find more.Nothing exercises the pnet fabric calls.
register_fabric,update_fabricandderegister_fabricare wired all the way throughsrc/mca/pnet/base, but no shipped component implements any of them, sopmix_pnet_globals.fabricsis never populated in a stock build and every base fabric call ends inPMIX_ERR_NOT_SUPPORTED/PMIX_ERR_NOT_FOUND/PMIX_ERR_BAD_PARAM. Covering it means writing a component that claims a plane, which is the decision recorded above. (test/unit/server_fabriccovers the server-sidePMIx_Compute_distanceshandler, not this path.)pnet/simptest’s end-to-end launch path is not inmake check.test/unit/pnet_simptest_mapdrivesallocatethroughPMIx_server_setup_applicationagainst a topology file it writes, but the other half — a client fetchingPMIX_FABRIC_ENDPTby rank andPMIX_FABRIC_COORDINATESat the node level — still has to be run by hand, becausetest/simple/simptestgenerates its node map from the local host and so needs a topology file naming that host. See “Running it” insrc/mca/pnet/simptest/AGENTS.md.The TSD finalize-ordering fix has no automated test.
pmix_tsd_keys_destructdeletes the process’s pthread keys, so it may only run once every other thread is joined; it used to run six lines above thepmix_progress_thread_stopinpmix_rte_finalizethat joins the progress thread, and has been moved below it. Neither half of what that fixed is testable from a unit program: reaching a deleted key needs the progress thread to print a process name inside a window a few calls wide, and the key-slot leak needs the same window and then shows up only as aPTHREAD_KEYS_MAXexhaustion many init/finalize cycles later.test/unit/threads_primitives.ccovers the registry’s own contract — including that a destructor is not run for a key the finalizing thread never set a value for — but the ordering itself is held only by the comment at the call site and bysrc/threads/AGENTS.md.gds/shmem3gets no coverage at all on macOS, of any kind. Itsconfigure.m4gates on a 64-bit non-Apple host with no|| test "$pmix_testbuild" = "1"escape, so a Mac does not even compile it —--enable-test-builddoes not help, and a change made there has not been compile-checked until it has been built on Linux.test/unit/gds_datastore’stest_shmem3_job_segment()askspmix_gds_base_assign_module()for the component by name and printsSKIPwhere it is absent, so the case is honest rather than missing, and on Linux it drives the segment build end to end in one process. Everything beyond one process — a fence, a second modex generation, a client attaching at a fixed address, and every cross-node fetch — is reachable only throughcontrib/dockerswarm/run-gds-tests.sh.The client-side tombstone generation fix is not what
examples/delete_key.cdiscriminates on. The example now re-publishes a deleted key and requires every rank to read the new value back, which pins the documented behavior — a deletion is not permanent — but it passes both before and after the fix: on a client the keyed get misses locally and is answered by the server, which never had the bug. Catching the client-side half needs a NULL-key read that actually reaches the modex, which takes the local table to miss for the rank or an explicitPMIX_REMOTEscope, and nothing arranges that today.Three findings from the
src/hwlocreview have no test. Their reasoning is insrc/hwloc/AGENTS.mditems 21, 22 and 29; what is open is the coverage, not the fix.A process that adopted its topology from shared memory and was told to share it must publish the XML, since
hwloc_shmem_topology_write()takes SIGBUS on an adopted topology. Reaching that needs a second-level server between a daemon and its clients, so it belongs incontrib/dockerswarm/run-topology.sh, which has no such shape today.A
NULLunder the deprecatedPMIX_TOPOLOGYkey needs the info arrayPMIx_server_initwas called with, and topology acquisition runs once per process — so it needs a test binary of its own, the waytest/unit/hwloc_setup_fail.cdid. That is the cheapest of the three to close.The shmem address and size were
uint64_twhere one becomes avoid *. Only a build where a pointer is narrower than 64 bits shows it, and none of the test environments is one.
14.5. Review coverage
What the deep review has not reached. Move an entry out of “Not yet reviewed” as its review lands, and refresh the churn figures in “Reviewed, but changed materially since” when a re-review closes one. The directories that are reviewed and current are listed in Deep review notes.
Note
An AGENTS.md is not evidence of a review. Every
directory under src/ has one; most were written in the July
2026 orientation sweep, before any review started. What marks
a reviewed directory is a run of repair commits — “Repair…”,
“Screen…”, “Close the … holes”, “Sweep the leftovers in…” —
and a commit that records the result in the guide.
14.5.1. Not yet reviewed
Each of these has an orientation guide and nothing else: no findings were ever recorded in it, and only drive-by fixes have landed. Ordered by size, which is a rough proxy for how much there is to find.
src/mca/pdl,src/mca/pinstalldirs— about 2200 lines between them, and the lowest risk of the group.src/util/keyval— 138 lines of flex source (keyval_lex.l); the 2327-linekeyval_lex.cbeside it is a generated build product and is not review material. It has noAGENTS.mdof its own, but thesrc/utilre-review gave it a section in that directory’s guide and covered its only driver,pmix_keyval_parse.c, so what is left unread is the.litself.
Outside src/, nothing has been reviewed: examples/ (16678 lines,
leak-swept only), test/simple (11011), test/unit/util and
test/unit/mca, and the public headers in include/.
14.5.2. Reviewed, but changed materially since
Ordered by how much of the directory the review no longer covers.
Figures are against the commit that recorded each review, measured
2026-08-24. src/util stood at the head of this list until
2026-08-26 and src/hwloc until 2026-08-27; the per-file re-reviews
closed both.
src/common— reviewed 2026-08-02. The two files that had moved furthest since,pmix_iof.c(1215 changed lines: the tool being given the library’s stdin forwarding instead of its own, the hold on a spawned job’s early output, and the SIGCONT handler that moved here out ofsrc/tool) andpmix_pfexec.c(335, most recently the removal of the library’s signal traps and the per-holder reference on a pfexec child), were re-reviewed on 2026-08-27. The other fifteen files are close to what the 2026-08-02 review read.
Lower priority, with current guides and churn that is mostly the reviews’
own work coming back through: src/event (+414/-29 since 2026-08-01,
nearly all of it the notification fan-out and cached-event replay that
the src/server and event-forwarding reviews drove), src/mca/ptl
(+617/-365 since 2026-08-07, the connection handler plus deletions
elsewhere), src/mca/gds/hash (+495/-152 since 2026-08-03, chiefly the
single job-level store the PMIx_Get review forced), and
src/mca/base (+17/-2 since 2026-08-16).