16 KiB
Requirements Document
Introduction
Engineers maintaining the XCE scheduler who need to determine whether two scheduler allocation or pending-reason objects represent the same decision currently have no shared way to do so, and when two objects differ nothing tells them where. The structural equality comparators for the shared data structures xce_alloc_t, xce_host_alloc_t, and xce_job_alloc_t (all defined in libxce's src/include/xce/types.h) live as static functions inside the scheduler's simulation-verification module src/scheduler/src/sim_verify.c, written for --sim-verify replay checking, beside a pending-reason comparator for xce_job_preason_t that deliberately compares only the main reason code (bug-1845). They return only yes/no, so a --sim-verify mismatch log cannot say which field differs (bug-1881: 43 mismatch lines in one replay, none showing the difference). Three scheduler test files (test_sim_equivalence.c, test_repl_equivalence.c, test_repl_limit_table.c) reuse two of these comparators only because they white-box #include "../src/sim_verify.c" and can therefore call the static functions directly.
This should change so that:
- the allocation equality comparators become public libxce API —
alloc_eq/host_alloc_eq/job_alloc_eqinxcealloc.{c,h}(the module that already constructs and deep-copies them); - libxce gains a new, full-structural-match pending-reason comparator
job_preason_eqinpreason.{c,h}(new behavior, not a relocation: bug-1845 narrowed the simulator's comparator to main-reason-only); - every comparator can, on request, return a newly allocated description of the first difference it found, and
--sim-verifyputs that description in its mismatch log; - an allocation's reservation start time
frombecomes part of allocation equality. Todaymake_alloc()stamps it with the wall clock (time(NULL)), so a replay can never reproduce it and the simulator had to ignore it (bug-1838-followup). Instead the scheduler stampsfromwith the start time of the scheduling session that made the allocation, a value a replay reproduces exactly.
The decision-level dispatcher (sim_decision_eq), its main-reason-only pending-decision check, and mismatch reporting (sim_log_mismatch) are simulator-specific and stay in sim_verify.c. The controller also carries these structs (persist.c, resync.c, job.c) and is a likely future consumer, but adopting the API there is not part of this feature.
Boundary Context
- In scope: moving the three allocation comparators from the scheduler simulator into public libxce API with a documented "same allocation decision" contract that compares
fromand excludes onlyinstid; adding a new full-structural-match pending-reason comparator to libxce; an optional out-parameter on every comparator that returns a newly allocated description of the first difference, owned by the caller; stampingfromwith the scheduling session's start time, taken from the one timestamp that is also recorded as the session'sbeginRound; using the difference description in--sim-verifymismatch logs (resolves bug-1881); migrating the simulator and the three scheduler test files that call the static comparators to the public API; libxce unit tests for the new functions; updating comments that name the removedsim_verify_*_eqfunctions. - Out of scope: moving the decision-level comparison dispatcher (
sim_decision_eq) or mismatch reporting (sim_log_mismatchand its describe helpers) out of the scheduler; changing the simulator's main-reason-only pending-decision check (bug-1845);merge_job_alloc()and itssame_alloc_slot()helper inxcealloc.c(bug-1863), which answer a different question — whether two records are the same reservation slot to merge, whereinstidis significant — and are neither replaced by nor reimplemented on top ofalloc_eq(their NULL-dereference defect, bug-1882, is already fixed); removing the three test files' white-box#include "../src/sim_verify.c", which they still need because they also white-box-includesim_loop.c, which callssim_decision_eq/sim_log_mismatch; any new comparison call site beyond the existing consumers, including the controller; any change to how the controller or worker interpretfrom; any change to the.replaywire format; removing--sim-speedand making the simulator clock move only at recorded event and round timestamps, which is the separate specscheduler-sim-discrete-clock. - Adjacent expectations: this feature depends on spec
scheduler-sim-discrete-clockfor Requirement 4.3 (a replayed session starts at exactly the recordedbeginRound); the.replaywire format and thereplay_decision_t/xce_decision_tdecision types are unchanged; thexce/types.hstruct layouts are unchanged, andfromkeeps its unit (seconds since the Unix epoch); libxce's deep-copy and merge helpers (dup_job_alloc,merge_job_alloc) keep copyingfromunchanged; the controller's Cap'n Proto persistence round-trips for these structures are unchanged. Recordings made before this change carry wall-clockfromvalues; replaying them with--sim-verifymay report a DISPATCH mismatch when an allocation's originaltime(NULL)fell in a later second than its session's start. That is an accepted limitation for old recordings, and the new difference description namesfromas the cause.
Requirements
Requirement 1: Shared equality API for allocation structures
Objective: As a scheduler engineer who needs to compare two allocation objects, I want a public libxce equality function for each allocation structure, so that I can compare them correctly by linking libxce rather than reimplementing the comparison or calling another module's private helpers.
Acceptance Criteria
- The library shall provide a public equality function for
xce_alloc_tthat reports equal if and only if the two records have the same resource name, reserve, value, reservation start time (from), and duration, wherevalueis compared exactly (bit-for-bit==), not within a floating-point tolerance. - The library shall provide a public equality function for
xce_host_alloc_tthat reports equal if and only if the two records have the same host name and, in order, the same per-resourcexce_alloc_trecords. - The library shall provide a public equality function for
xce_job_alloc_tthat reports equal if and only if the two records have the same job id, the same per-hostxce_host_alloc_trecords in order, and the same shared-resourcexce_alloc_trecords in order. - The allocation equality functions shall answer "do these two records describe the same allocation decision" and shall not consider a record's allocation-instance id (
instid), because it is taken from a per-process counter (sched_get_id()) whose value depends on how many allocations that process built before, so a recording and its replay give the same allocation differentinstidvalues (bug-1844). This contract is distinct frommerge_job_alloc()'s slot matching, whereinstidis significant; the library's documentation ofalloc_eqshall state that difference. - When the two arguments to an allocation equality function are the identical pointer (including two NULL pointers), the function shall report equal; when exactly one of the two arguments is NULL at the top level, or exactly one of two corresponding list entries or string fields is NULL at any nested level, the function shall report not-equal without crashing.
- A record whose entry count is non-zero while its entry array pointer is NULL is malformed; the allocation equality functions are not required to handle it, and the library's documentation of these functions shall state that precondition.
- The allocation equality functions shall not modify either argument.
Requirement 2: Shared equality API for the pending-reason structure
Objective: As a scheduler engineer who needs to compare two pending-reason objects, I want a public libxce equality function that reports a full structural match, so that I can determine whether two pending reasons are identical by linking libxce rather than reimplementing the comparison.
Acceptance Criteria
- The library shall provide a public equality function for
xce_job_preason_tthat reports equal if and only if the two records have the same main reason code, the same host-based reason entries in order, and the same resource-based reason entries in order. This is new comparison behavior; it is intentionally stricter than the simulator's existing main-reason-only check. - The pending-reason equality function shall compare each host-based reason entry by its host name and reason code, and each resource-based reason entry by its resource name, host name, and reason code.
- The pending-reason equality function shall handle NULL arguments and malformed records the same way as the allocation equality functions (Requirement 1, criteria 5 and 6) and shall not modify either argument.
Requirement 3: Description of the first difference
Objective: As an engineer debugging a mismatch, I want an equality function to tell me which field differs and the two values, so that I can diagnose the mismatch without re-running it under a debugger.
Acceptance Criteria
- Each public equality function (Requirements 1 and 2) shall take an optional out-parameter
char** why. Whenwhyis NULL, the function shall behave exactly as without it: no description is produced and nothing is allocated. - When
whyis non-NULL and the two arguments are not equal, the function shall set*whyto a newly allocated, NUL-terminated, human-readable description of the first difference found, in comparison order, naming: the path to the differing field (for example the host-list index and host name, the resource-list index and resource name), the field name, and the two differing values (or which side is NULL, or the two list lengths). The function sizes the string itself; the description is never truncated. - When
whyis non-NULL and the two arguments are equal, the function shall set*whyto NULL. - Ownership of a returned description passes to the caller, who releases it with
free(). The function shall never read or free the value*whyheld on entry. - If allocating the description fails, the function shall set
*whyto NULL and still return the correct equal/not-equal result; the comparison result never depends on whether a description was produced. - Producing the description shall not log.
Requirement 4: Reproducible reservation start time
Objective: As an operator relying on --sim-verify, I want an allocation's from to be a value a replay reproduces exactly, so that from can be part of allocation equality without causing false mismatches.
Acceptance Criteria
- The scheduler shall set each allocation's
fromto the start time of the scheduling session that produced the allocation, in whole seconds since the Unix epoch (the session start in nanoseconds, divided by 10^9 and rounded down), instead of the wall-clock time at which the record was built. - In normal mode, the session start time used for
fromshall be the same single clock reading that the session records as itsbeginRoundwhen recording is enabled; the scheduler shall not read the clock separately for the two. - In simulation mode, the session start time used for
fromshall be exactly the recorded session'sbeginRound, so that a replayed allocation'sfromequals the recorded one. This is provided by specscheduler-sim-discrete-clock(the simulator clock does not advance between recorded timestamps); this feature shall not add its own simulator clock mechanism. - All allocations produced in one session, including every primary of a COMPOUND request merged by
merge_job_alloc(), shall carry the samefrom. - Deep copies (
dup_job_alloc()) and merges (merge_job_alloc()) shall keep copyingfromfrom their source, as they do today.
Requirement 5: Simulator verification behavior
Objective: As an operator relying on --sim-verify replay checking, I want the simulator's decision verification to keep catching real divergence without new false mismatches, and to tell me what differs, so that a mismatch is both trustworthy and diagnosable.
Acceptance Criteria
- The simulator shall compare a recorded DISPATCH decision against a live one with the libxce allocation comparison (Requirement 1), which now also compares
from, rather than defining its own. - The simulator shall keep its pending-decision comparison as a simulator-local main-reason-only check, and shall not use the full-match
job_preason_eq, because a recorded cache-hit pending reason carries no host/resource detail while a live replayed one always does (bug-1845). This asymmetry is a simulator concern, not a property of pending-reason equality. - When the simulator reports a verification mismatch, the log line shall keep its existing logical-time and decision context and shall add the description of the first difference (Requirement 3), or, for a PEND mismatch, the two jobids or the two main reason codes, or, when one side has no decision, which side is missing. Only if allocating the description fails (Requirement 3.5) may the line be logged without it. This resolves bug-1881.
- Replaying a recording made after this change against its own previously-recorded output with verification enabled shall report zero mismatches.
Requirement 6: Single shared implementation across all consumers
Objective: As a maintainer, I want the allocation comparisons and the full pending-reason comparison to exist exactly once, in libxce, so that future changes to their semantics are made in one place and every consumer picks them up.
Acceptance Criteria
- The simulator and the three scheduler test files that currently call the static comparators (
test_sim_equivalence.c,test_repl_equivalence.c,test_repl_limit_table.c) shall obtain the allocation comparison from the libxce public API, and no scheduler source or test shall retain a copy of it. The simulator's main-reason-only pending-decision check (Requirement 5, criterion 2) is the single documented simulator-local pending-reason comparison. - The three test files shall compare pending reasons with the full-match
job_preason_eq. This makes their pending-decision checks stricter than today's main-reason-only comparison. The change is valid because both sides of each of their comparisons are produced without the scheduler's unsatisfiable-allocation cache (none of these tests registersunsatisfiable_lookup_cb), so the detail lists are built by the same full per-host computation on both sides and are expected to match; each test file shall document this reasoning where it callsjob_preason_eq. Their allocation comparisons also become stricter (fromis compared). Hand-built test fixtures that stand in for a recording shall carry the recorded session's start time asfrom(Requirement 4.1), like a real recording. If a test fails after these changes, the failure shall be investigated as a real divergence, not resolved by loosening the comparison. - A consumer shall be able to call the public comparators by including
xcealloc.h/preason.hand linking libxce, without white-box-including any scheduler source. The three test files may keep their existing white-box#include "../src/sim_verify.c", since they need it for the decision-level functions thatsim_loop.ccalls, but shall no longer call any comparator that is private tosim_verify.c. - The library shall provide unit test coverage for each new equality function, covering: a match; a mismatch on each compared field, with the expected difference description; that an
instiddifference does not affect allocation equality; that afromdifference does; that list order matters (the same entries in a different order compare not-equal); a list-length mismatch; the NULL-handling cases at the top level and at nested entries and string fields; a NULLwhy;*whyset to NULL on equal; and a failed description allocation (*whyNULL, same result). - The scheduler shall have tests showing that allocations made in one session carry that session's start time as
from, and that the recordedbeginRoundequals that start time. - Adding these functions shall not alter the behavior of any existing libxce function other than the
fromstamping required by Requirement 4, and shall not altermerge_job_alloc()'s slot matching. - Comments in scheduler sources and tests that name the removed
sim_verify_alloc_eq,sim_verify_host_alloc_eq,sim_verify_job_alloc_eq, orsim_verify_job_preason_eqfunctions, or that describefromas excluded from comparison, shall be updated.