Requirement set 1 (info limit <name|all>) of the open-ended scheduler-sim-diagnostics spec: requirements refined for effective vs. configured scope and exclusion-group membership, design grounded in the actual limit-plugin/REPL source (accessor boundary, GLOBAL/SHARED-vs-per-host read split, existence-vs-zero-usage distinction), and an 8-subtask implementation plan with full requirements traceability. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
17 KiB
17 KiB
Research & Design Decisions
Summary
- Feature:
scheduler-sim-diagnostics— Requirement set 1 (info limitREPL command) - Discovery Scope: Extension (extends
scheduler-core's existing REPLinfocommand family and reuses the limit plugin's existing library-level formatters) - Key Findings:
- The REPL
infocommand family is a single dispatch function (repl_cmd_info(),repl_info.c) with oneelse ifbranch per subcommand — addinglimitis a mechanical extension of an existing, well-established pattern. - The limit plugin's private state (
limit_plugin_data_t.limits/.instances) is genuinely inaccessible outsidesrc/scheduler/src/plugins/limit.ctoday — no header declares the type — confirming the brief's stated gap. Two new one-line accessor functions close it. - Two library-level formatters already exist and already do most of Requirement 1's work:
fmt_limit()andfmt_consumer()/fmt_group()(src/lib/xcelimit.c,src/lib/group.c), including correct inclusion/exclusion membership rendering (fmt_group()already prefixes excluded members with~). These are adopted directly rather than rebuilt. - The live-usage read path (GLOBAL/SHARED single value vs. per-host breakout) is architecturally identical to the pattern
repl_info_resource_one()already implements for ordinary resources (bug-1593) — the limit's synthetic capacity resource (vres->vres) is a real, already-registeredxce_resource_tcarrying the sameXCE_RESF_GLOBAL/XCE_RESF_SHAREDflags, so the same shared-vs-per-host read logic applies unmodified.
- The REPL
Research Log
Limit plugin data model and matching semantics
- Context: Requirement 1 AC1/AC5/AC6 need to know exactly what "scope dimensions" and "restricted membership" mean at the code level, since an earlier requirements-review pass surfaced a scope-vs-runtime ambiguity.
- Sources Consulted:
src/include/xce/types.h(xce_limit_t,xce_limit_config_t,xce_consumer_t,xce_vres_t),src/lib/xcelimit.c(match_limit,build_vres_name,is_host_based_limit,instantiate_limit),src/scheduler/src/plugins/limit.c(match_job_host_limit_,match_job_global_limit_,update_limit_res_value,limit_prepare_allocation),.wolf/buglog.jsonbug-1784 through bug-1790 (the gitea#46 limit-matching bug chain, 2026-09-07). - Findings:
xce_consumer_type_thas exactly four values (CONSUMER_USER,CONSUMER_QUEUE,CONSUMER_PROJECT,CONSUMER_HOST) — the four scope dimensions in Requirement 1 AC1 are the complete set, no fifth dimension exists.is_host_based_limit()forces per-host live-instance tracking whenever the limit's value is a percentage of a resource that is notXCE_RESF_GLOBAL/XCE_RESF_SHARED— independent of whetherCONSUMER_HOSTwas configured. This is real, current behavior (not hypothetical), confirmed via source and via bug-1789/1790's live-verified fix history for the equivalent allocation-side computation.xce_group_tsupports bothmembersandex_members(exclusion). Confirmed as real production configuration via bug-1789's own note describing a live-configured limit ("limit1... all users except rshen").fmt_group()(src/lib/group.c:388) already renders both lists correctly (~nameprefix for exclusions,all/[]shortcuts for the builtin groups) and is alreadyextern-declared ingroup.h.fmt_consumer()/fmt_limit()(src/lib/xcelimit.c) already composefmt_group()into a full per-limit configuration dump.- The bug-1789/1790 chain (global limits under-counting usage against per-host resources) is fully closed as of 2026-09-07 — the underlying accounting this feature displays is trustworthy across all resource/scope combinations by the time this spec is implemented.
- Implications: Requirement 1's "configured setting" display can adopt
fmt_limit()directly instead of re-deriving formatting logic; only the effective-host-scope annotation (Req 1.6) and the live-instance/usage rendering (Req 2, Req 3) are genuinely new code.
REPL command dispatch and existing info resource precedent
- Context: Need the concrete integration point for a new
info limitsubcommand and a working precedent for the GLOBAL/SHARED-vs-per-host live-value read split Requirement 2/3 require. - Sources Consulted:
src/scheduler/src/repl.c(repl_dispatch),src/scheduler/src/repl_info.c(repl_cmd_info,repl_info_resource,repl_info_resource_one),src/scheduler/src/plugin.c(plugin_register,plugin_register_core),src/scheduler/include/plugin.h, bug-1593 (repl_info_resource_one's original GLOBAL/SHARED-vs-host-based display bug and fix). - Findings:
repl_cmd_info()is a flatstrcmpchain ontokens[1]; every existing subcommand (job,queue,worker,resource, ...) follows the identical<name>|allargument shape Requirement 4 asks for.repl_info_resource_one()(post bug-1593) reads a GLOBAL/SHARED resource's value fromsim->core.sharedInst/sharedInUseatr->idx, and explicitly declines to fabricate a value for a host-based resource, deferring toinfo worker <name>instead. This is the exact pattern Requirement 2/3's per-instance read path reuses.- Plugins are registered into
core->plugins, a name-keyed handle (key_add(core->plugins, plugin->name, plugin)); the limit plugin registers under the fixed name"xce_sched_limit"(limit.c:557). No plugin is ever renamed or re-registered under a different name at runtime. - Synthetic limit-capacity resources (
vres->vres, created byadd_vres()) are inserted into the samecore.resmgrtableinfo resource/info workeralready iterate, flaggedXCE_RESF_LIMIT(already a named flag inrepl_resource_flags_str's table). A GLOBAL-scoped limit's synthetic resource can therefore already appear ininfo resource all/info resource <name>today, labeled with theLIMITflag — this is pre-existing behavior, unrelated to and unmodified by this spec.
- Implications: New command wiring is a single new branch in
repl_cmd_info()plus one new static functionrepl_info_limit()inrepl_info.c, followingrepl_info_resource()'s two-level (_alliterates,_oneprints) shape.
Architecture Pattern Evaluation
| Option | Description | Strengths | Risks / Limitations | Notes |
|---|---|---|---|---|
| Direct struct exposure | Move limit_plugin_data_t into a shared header, let repl_info.c include it directly |
Fewer functions | Breaks the plugin's existing data encapsulation; every other plugin's private state stays file-local — this would be the only exception | Rejected |
| Opaque query API (iterator_t out-params, per-limit filtered iteration) | New multi-function query interface returning filtered iterators | Nice call-site ergonomics | New abstraction with only one consumer today; no existing accessor in this codebase does filtered iteration — the rest (sched_get_job, core->plugins, core->jobs) hand back a raw kvmgr/xlist handle and let the caller iter_make/iter_next |
Rejected — over-built for current scope |
| Selected: two raw-handle accessors | limit_get_configured_limits(core) / limit_get_usage_instances(core) return the plugin's existing var (kvmgr/xlist) handles, or NULL if the plugin isn't registered; caller iterates with the same iter_make/iter_next/iter_destroy idiom used everywhere else in this codebase |
Matches every existing accessor pattern (core->jobs, core->queues, core->plugins); minimal new surface (2 functions); no new types |
Caller must filter usage instances by vres->limit == target_limit itself (pointer comparison) since there is no per-limit index — acceptable, this table is small (configured-limit count × scope-instance count, not job count) |
Selected |
Design Decisions
Decision: Plugin accessor shape
- Context:
limit_plugin_data_t(limits,instances) is a file-local type inlimit.c; Requirement 1/2/3 need read access to both fromrepl_info.c. - Alternatives Considered:
- Expose the struct itself in a shared header.
- Build a filtered query API (per-name lookup, per-limit instance iteration) as new plugin-owned functions.
- Expose the two raw
varhandles as accessor functions, encapsulating the plugin-name lookup ("xce_sched_limit") inside the accessor itself sorepl_info.cnever needs to know it.
- Selected Approach: (3).
limit_get_configured_limits(xce_sched_core_t* core)andlimit_get_usage_instances(xce_sched_core_t* core), both declared insrc/scheduler/include/plugin.hnext toregister_limit, defined inlimit.c. Each does its ownkey_find(core->plugins, "xce_sched_limit", &plugin)and returnsplugin->data's relevant field (orNULLif the plugin is not registered or not yet initialized). - Rationale: Matches the codebase's existing accessor idiom exactly (raw handle + shared iterator trio), keeps the plugin-name string fully inside
limit.c(the one place that already owns it viaregister_limit()), and adds the minimum surface needed — no speculative per-limit-name lookup function, since both AC1's "specific name" and AC2's "all" cases are satisfiable by iterating the same handle and filtering in the caller. - Trade-offs:
repl_info_limit()finds a limit by name via a directkey_find()(privdata->limitsis a name-keyed kvmgr — confirmed by bug-1685's duplicate-name-EEXISThandling, so this is O(1)-ish, not a scan) and a linear scan + pointer-equality filter to find that limit's usage instances inprivdata->instances(keyed by vresname, not by owning limit — no index avoids this scan; accepted, same scale argument as below). - Follow-up: None — this is a stable, minimal boundary; a future capability under this same open-ended spec (e.g. decision-reasoning trace) does not need to touch this accessor.
Decision: Reuse fmt_limit()/fmt_group() for configured-setting display
- Context: Requirement 1 AC1/AC5 need to print a limit's name, per-dimension scope, and inclusion/exclusion membership.
- Alternatives Considered:
- Write new formatting code in
repl_info.cmirroring the otherrepl_info_*_one()functions' inlinefprintfstyle. - Call the existing
fmt_limit()(which internally callsfmt_consumer()/fmt_group()) into a stack buffer and print it, then append the REPL-specific effective-scope note and live-usage lines the library formatter cannot know about.
- Write new formatting code in
- Selected Approach: (2).
- Rationale:
fmt_limit()already produces correct, exclusion-aware output and is already used/tested at the library level (src/lib/testsuite/test_limit.c); duplicating that logic inrepl_info.cwould both waste effort and risk drifting out of sync with the library's own definition of "configured setting." Build-vs-adopt: adopt. - Trade-offs:
fmt_limit()'s output format (multi-line,PER_USER = [...]style) is fixed by the library; the REPL wraps it with a[sim]prefix per line rather than reformatting it, consistent with how other multi-lineinfooutput is already prefixed in this codebase (e.g.info jobs). - Follow-up: None.
Decision: Effective-scope annotation is REPL-layer, not library-layer
- Context: Requirement 1 AC6 (implicit host-scoping for a percent-based limit on a non-shared resource) is not something
fmt_limit()knows about — it only reflects the configuredconsumers[]array. - Alternatives Considered:
- Change
fmt_limit()itself to callis_host_based_limit()and inject a syntheticPER_HOSTline. - Keep
fmt_limit()unchanged (it is a general-purpose library formatter used elsewhere, e.g. by any future non-REPL consumer that wants the raw configured shape) and haverepl_info_limit()callis_host_based_limit()itself, appending an explanatory note only whenconsumers[CONSUMER_HOST] == NULLand the check returns true.
- Change
- Selected Approach: (2).
- Rationale:
is_host_based_limit()is alreadyextern-declared inxcelimit.hand used exactly this way (a yes/no runtime check) elsewhere; the "effective scope" concept is specific to what an operator inspecting the REPL needs to know, not a general property of the limit's configured shape — keeping it at the REPL layer avoids changing a shared library formatter's contract for one caller's needs. - Trade-offs: None material.
- Follow-up: None.
Risks & Mitigations
- Risk: Linear scan over
privdata->instancesto filter byvres->limit == target_limit(this table is keyed by vresname, not by owning limit, so no index avoids the scan) could be surprising if a deployment ever configures an unusually large number of distinct scope instances (e.g. thousands of users × per-user limits). — Mitigation: Accepted for this spec; the existingmatch_job_host_limit_/match_job_global_limit_matching path already does akey_findper job per limit against the same table, so this table's size is already load-bearing elsewhere in the scheduler's hot path — a read-only REPL scan over it is not a new scaling concern this spec introduces. - Risk: Because
is_host_based_limit()takes the plugin'sresmgrhandle (avar), calling it fromrepl_info.crequires reachingsim->core.resmgr— already public (used directly byrepl_info_resource()today), so no new exposure needed. — Mitigation: N/A, confirmed no new risk. - Risk:
build_vres_name()never encodes host in the vresname it builds — an implementer could reasonably assume "onexce_vres_tper (user, queue, project, host)" and design storage/iteration around a cardinality that does not exist. — Mitigation:design.md's Data Models section states the correct cardinality explicitly (onexce_vres_tper (user, queue, project); host is produced by a per-worker fan-out over that singlexce_vres_t's shared resource index, not by additionalxce_vres_tinstances) and cites this finding. - Risk: Build wiring for the new white-box test —
test_repl_resource_table.c's existing#includeset omitsplugins/limit.candresource.c; naively adding a limit-info test there, or to a careless new file, reproduces the exact undefined-reference class bug-1762 already hit once for a sibling REPL feature. — Mitigation:design.md's Testing Strategy calls this out explicitly and directs a new sibling test file (test_repl_limit_table.c) with its own white-box include set, real (not stubbed)resource.csince the test needs non-trivial accounted-usage values.
Additional Findings (design review pass)
xce_sched_worker_tcarries two separate zarrays per resource idx:.resvals(capacity) and.resInUse(accounted usage) — confirmed viasched_check_host_satisfy_()(resource.c), which reads both the identical way this design's per-host instance display needs to.instantiate_limit()andupdate_limit_res_value()run back-to-back inside the samematch_job_host_limit_/match_job_global_limit_call (plugins/limit.c) — a matchedxce_vres_t's capacity entry is therefore always populated by the time thexce_vres_titself is observable via the new accessors; only its in-use entry can legitimately be absent (genuine zero usage, not "no instance").resource-accounting.md's reservation-vs-actual-usage reconciliation is explicitly scoped to resources flagged bothXCE_RESF_DYNAMICandXCE_RESF_CONSUMABLE. A limit's synthetic capacity resource (add_vres()) is flaggedXCE_RESF_LIMIT | XCE_RESF_CONSUMABLE | XCE_RESF_RELEASEplusGLOBAL|SHAREDorDEFAULT_HOST— neverXCE_RESF_DYNAMIC— so that steering document does not apply here; the capacity/in-use pair this design reads is an exact scheduler-computed accounting number, not an OS-reconciled estimate.src/scheduler/testsuite/test_no_bare_printf.cregression-tests every file undersrc/scheduler/src/for bareprintf()calls (must usefprintf(stdout, ...)); bug-1599 documents a known scanner false-positive on comment prose, not an exemption for real code.
References
.wolf/buglog.jsonbug-1784–bug-1790 (gitea#46 limit-matching chain, 2026-09-07) — internal, not a URL..wolf/buglog.jsonbug-1593 (info resourceGLOBAL/SHARED-vs-host-based display fix, 2026-08-19) — internal, not a URL..wolf/buglog.jsonbug-1685 (limit plugin double-free; also the evidence thatprivdata->limitsis name-keyed) — internal, not a URL..wolf/buglog.jsonbug-1762 (build-wiring precedent for white-box REPL test files) — internal, not a URL..wolf/buglog.jsonbug-1599 (bare-printf regression scanner false-positive) — internal, not a URL..kiro/steering/resource-accounting.md— reservation-vs-actual-usage policy, scoped toXCE_RESF_DYNAMIC; confirmed not applicable to this design.