fix(scheduler): cache hits keep the full pend reason; sim uses the cache (bug-1932)

The session-level unsatisfiable-resreq cache stored only the exhausted
attempt's main reason (an int). The allocators record shortfalls as
per-host/per-resource entries and leave main_reason at 0, so every cache
hit in the live scheduler pended its job with an empty reason. The
simulator registered no cache at all (its table lived on xce_scheduler_t),
so it searched every identical pending job again and its traces differed
from production.

- The cache table moves to xce_sched_core_t; cache_lookup/insert/clear and
  the hook adapters take the core, with no cast to a driver struct.
  sched_init() builds it, cache_clear() rebuilds it per session,
  xce_sched_core_fini() destroys it.
- A cache entry is a deep copy of the attempt's full xce_job_preason_t
  plus the job that proved the request unsatisfiable (core->alloc_jobid,
  set by sched_allocator()). A hit moves a copy into ctx->reason; a miss
  leaves ctx->reason untouched.
- main.c passes the same three adapters to the simulator through new
  sched_sim_args_t fields; sched_sim_driver_start() sets them on the sim
  core (plain field writes: white-box tests include sim.c alone).
- Decision trace: a hit renders "no plan generated: skipped, same request
  as <job> proved unsatisfiable earlier this session" (new
  XCE_TRACE_NOPLAN_KNOWN_UNSAT via sched_trace_known_unsat()); the outcome
  shows the restored reasons.
- sim_verify keeps its main-only PEND compare (old recordings carry
  main-only hits); comments and the scheduler-sim-live-allocation design
  note updated.

Tests: full-reason assertions in the cache dispatch tests (RED before:
"a cache hit must restore the original attempt's FULL pend reason"),
test_alloc_cache.c ported to the new API (deep copy, clear frees, -EEXIST
frees), a sim-registered clear test, a renderer test, and a sim e2e
scenario with two identical unsatisfiable jobs (204, 205) where 205 is
skipped with 204's full reasons (RED before: no skip line).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-09 08:02:18 +00:00
parent cff3304f5d
commit 0317c7af98
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
27 changed files with 982 additions and 737 deletions

View file

@ -73,97 +73,53 @@ typedef int (*xce_core_decision_cb_t)(void*, xce_decision_t*);
/*
* xce_core_cache_clear_cb_t - driver-registered per-session cache-clear
* hook (scheduler-allocation spec, task 5.2 round 2, Req 7.2):
* `void (*)(xce_sched_core_t*)`. Mirrors xce_core_decision_cb_t's
* driver-registered-hook idiom just above, but generic on purpose -- no
* `void* driver_ctx` companion field, because the sole real
* implementation (alloc_cache.c's xce_alloc_cache_clear_cb() -- see that
* function's own doc comment for why it lives in alloc_cache.c rather
* than driver.c) recovers its owning xce_scheduler_t* via the
* pointer-to-first-member idiom this codebase already uses for this exact
* struct (scheduler.h: `xce_sched_core_t core` is xce_scheduler_t's first
* member), not via an opaque context pointer.
* `void (*)(xce_sched_core_t*)`, called by core_begin_session_internal()
* (core_session.c) when non-NULL. Both drivers register alloc_cache.c's
* xce_alloc_cache_clear_cb() (main.c; sched_sim_driver_start() from the
* caller's sched_sim_args_t), which empties THIS core's own
* unsatisfiable_cache (bug-1932).
*
* WHY a hook instead of a direct call (task 5.2 round 1's CRITICAL defect):
* core_begin_session_internal() (core_session.c) is reached from BOTH
* driver.c's xce_sched_driver_run_normal_mode_round() (a real
* xce_scheduler_t*) and sim_loop.c's sched_sim_driver_run() (a
* structurally different xce_sched_sim_driver_t*, sim.h -- Req 5 AC 17
* "Simulation isolation" -- which has NO unsatisfiable_cache field at
* all). Round 1 called alloc_cache.c's real, strong
* cache_clear(xce_scheduler_t*) unconditionally for both callers (routed
* through a same-named weak fallback that the strong definition always
* overrides at link time in the production binary) -- since link-time
* weak/strong resolution picks ONE winner process-wide, not per call
* site, every simulation-mode session reinterpreted whatever field sits
* at unsatisfiable_cache's byte offset in xce_sched_sim_driver_t
* (alt_events_file, a live FILE*) as a kvtab_t* and clobbered it.
*
* A per-core-instance function pointer fixes this at the data-ownership
* level: each xce_sched_core_t only ever carries the hook ITS OWN driver
* installed via xce_core_set_cache_clear_cb() below. main.c installs
* alloc_cache.c's xce_alloc_cache_clear_cb() on the real xce_scheduler_t's
* core; sim_loop.c never calls the setter at all, so a simulation-mode core's
* hook stays NULL (xce_sched_core_init()'s memset() default) and
* core_begin_session_internal() treats NULL as "this driver has no cache
* to clear" and skips the call entirely -- a true no-op, not an
* accidental type-confused one.
* WHY a hook instead of a direct call: task 5.2 round 1 called a
* cache_clear(xce_scheduler_t*) unconditionally for both drivers, so every
* simulation-mode session reinterpreted xce_sched_sim_driver_t's
* alt_events_file FILE* as the cache and clobbered it. The table now lives
* on the core and no hook casts the core to a driver struct, but the hook
* stays: white-box tests that link core_session.c without alloc_cache.c
* leave it NULL, and NULL means "no cache to clear".
*/
typedef void (*xce_core_cache_clear_cb_t)(xce_sched_core_t*);
/*
* xce_unsatisfiable_lookup_cb_t / xce_unsatisfiable_populate_cb_t -
* driver-registered, OPTIONAL per-session unsatisfiable-resreq cache
* consult/populate seam (scheduler-sim-live-allocation spec, task 1.1,
* Req 5.2). Mirrors xce_core_cache_clear_cb_t's own shape and rationale
* immediately above exactly: a bare function pointer taking
* xce_sched_core_t* directly, no wrapper struct, no opaque "impl"/
* user-data pointer -- single registrant (main.c's normal-mode branch),
* no foreseeable second implementation.
* driver-registered, OPTIONAL unsatisfiable-resreq cache consult/populate
* seam (scheduler-sim-live-allocation spec, task 1.1, Req 5.2) around the
* allocation dispatch chokepoint, sched_make_primary_alloc_()
* (scheduler.c). `obj` is the object cache_lookup()/cache_insert()
* (sched/alloc_cache.h) key on: its resreq fingerprint plus user/queue/
* project.
*
* Parameter shape mirrors the REAL cache's own existing entry points
* (alloc_cache.c's cache_lookup()/cache_insert(), design.md's
* "Unsatisfiable-Cache Callback Seam" component) rather than inventing a
* new bare-integer "fingerprint" type: xce_sched_obj_t* is the exact
* object cache_lookup()/cache_insert() already key on (its embedded
* xce_resreq_fingerprint_t plus user/queue/project identity, sched/
* alloc_cache.h) -- this seam adds no new type, only a NULL-safe
* indirection over the existing one. xce_sched_obj_t is already visible
* here as a forward-declared pointer type (sched/types.h, included
* above); only a pointer, never the full struct, is needed in this
* header.
* bug-1932: a hit must report the original attempt's FULL pend reason,
* not only its main reason (in production the main reason is usually 0
* and the real cause is per-host, so a main-only hit pended later jobs
* with an empty reason). Lookup returns 1 on a hit and replaces
* *out_reasons with a copy of the stored reason, *out_jobid with the job
* whose search proved the request unsatisfiable; 0 on a miss (outputs
* untouched). Populate stores a copy of `reasons` (NULL: empty) for
* `jobid`.
*
* WHY a hook instead of a direct call (mirrors xce_core_cache_clear_cb_t's
* own documented incident above): the allocation dispatch chokepoint this
* seam wraps (sched_make_primary_alloc_(), scheduler.c) is reached
* identically by both drivers once a later task's Core-Safe Allocation
* Chain migration lands -- but the real cache
* (xce_scheduler_t.unsatisfiable_cache) exists ONLY on a normal-mode
* xce_scheduler_t, never on a simulation-mode xce_sched_sim_driver_t. A
* NULL-checked, driver-registered callback keeps that difference behind
* an explicit seam instead of a struct-prefix cast into mode-specific
* state from code both drivers share -- the same anti-pattern
* xce_core_cache_clear_cb_t's own doc comment documents a real, prior
* memory-corruption incident from.
*
* NULL (the xce_sched_core_init() memset() default, unless a driver
* registers one) means "always compute fresh" for BOTH callbacks -- never
* an error, never a degraded-correctness path, only a degraded-
* performance one. Wiring the actual consult/populate call sites into
* sched_make_primary_alloc_() is a LATER task's job (tasks.md 2.2,
* boundary "Core-Safe Allocation Chain, Unsatisfiable-Cache Callback
* Seam") -- this task adds only the seam (these two fields, their
* adapters in alloc_cache.c, and main.c's normal-mode-only registration),
* not that call site. Every simulation-mode core leaves both fields NULL
* forever (sim_loop.c never registers them, Req 5 AC 17 "Simulation
* isolation"), so a simulation-mode session never touches the real cache
* implementation at all.
* Both drivers register alloc_cache.c's adapters (bug-1932: the
* simulator used to register none, so it searched every identical pending
* job again and its traces differed from production). NULL (the
* xce_sched_core_init() memset() default) means "always compute fresh" --
* a performance difference only, never a correctness one.
*/
typedef int (*xce_unsatisfiable_lookup_cb_t)(xce_sched_core_t* core,
xce_sched_obj_t* obj,
int* out_reason_category);
typedef void (*xce_unsatisfiable_populate_cb_t)(xce_sched_core_t* core,
xce_sched_obj_t* obj,
int reason_category);
typedef int (*xce_unsatisfiable_lookup_cb_t)(xce_sched_core_t* core,
xce_sched_obj_t* obj,
xce_job_preason_t* out_reasons,
uint64_t* out_jobid);
typedef void (*xce_unsatisfiable_populate_cb_t)(
xce_sched_core_t* core, xce_sched_obj_t* obj,
const xce_job_preason_t* reasons, uint64_t jobid);
/* Reserved jbuf sentinel for the Recorded-Decision Reconciliation
* allocator-thread relay (bug-1837) -- see xce_sched_core_s's reconcile_req/
@ -238,25 +194,25 @@ struct xce_sched_core_s {
xce_core_decision_cb_t on_decision;
/* Req 7.2 (scheduler-allocation spec, task 5.2 round 2): driver-
* registered per-session cache-clear hook -- see
* xce_core_cache_clear_cb_t's own doc comment above for the full
* rationale. NULL until a driver registers one (xce_core_set_
* cache_clear_cb()); core_begin_session_internal() (core_session.c)
* treats NULL as "this driver has no cache to clear" and skips the
* call -- true for every simulation-mode xce_sched_core_t, which never
* registers this hook. */
* xce_core_cache_clear_cb_t above. NULL until a driver registers one
* (xce_core_set_cache_clear_cb()). */
xce_core_cache_clear_cb_t cache_clear_cb;
/* Req 5.2 (scheduler-sim-live-allocation spec, task 1.1): driver-
* registered, OPTIONAL unsatisfiable-resreq cache consult/populate
* seam -- see xce_unsatisfiable_lookup_cb_t/xce_unsatisfiable_
* populate_cb_t's own doc comment above for the full rationale. NULL
* (xce_sched_core_init()'s memset() default) until main.c's
* normal-mode branch registers both, alongside cache_clear_cb just
* above. A simulation-mode core never registers either, so
* consulting/populating this seam is always a true no-op for sim
* mode ("always compute fresh"), never a fallback onto real cache
* state. */
/* Req 5.2 (scheduler-sim-live-allocation spec, task 1.1): the
* unsatisfiable-resreq cache consult/populate seam -- see
* xce_unsatisfiable_lookup_cb_t above. NULL until a driver registers
* both (both drivers do, bug-1932). */
xce_unsatisfiable_lookup_cb_t unsatisfiable_lookup_cb;
xce_unsatisfiable_populate_cb_t unsatisfiable_populate_cb;
/* bug-1932: the session-level unsatisfiable-resreq cache itself
* (sched/alloc_cache.h), owned by the core so both drivers have one.
* Built and emptied by cache_clear() at every session start, destroyed
* by xce_sched_core_fini(); NULL until the first clear. Allocator
* thread only, plus the clear between sessions. */
var unsatisfiable_cache;
/* bug-1932: the job sched_make_allocation() is working on, set by
* sched_allocator() (sched_election.c) so a cache populate can record
* which job proved a request unsatisfiable. 0 when unknown. */
uint64_t alloc_jobid;
_Atomic int session_active; /* 1 during scheduling session, 0 between */
_Atomic int eval_in_progress; /* 1 while eval_select loop runs */
_Atomic int staging_merge_complete; /* 1 after staging merge completes */

View file

@ -13,11 +13,13 @@
*
* Key shape: xce_resreq_fingerprint_t (sched/types.h, already populated on
* xce_primary_schedobj_t.resreq_fp at job-ingest time -- see task 1.2)
* composed with job identity (user/queue/project). Value: the ORIGINAL
* exhausted attempt's pend-reason category (an int code), not a bare
* present/absent marker -- Req 7.5 requires a cache hit to report the same
* reason category the original miss determined, so operators are not
* misled about why a later job was skipped.
* composed with job identity (user/queue/project). Value: a deep copy of
* the ORIGINAL exhausted attempt's full pend reason (main, per-host and
* per-resource entries) plus the job whose search proved the request
* unsatisfiable -- Req 7.5 requires a cache hit to report the same reason
* the original miss determined, and bug-1932 found that keeping only the
* main-reason int pended later jobs with an empty reason whenever the real
* cause was per-host.
*
* Backed by this codebase's existing keyed-table infrastructure (kv.h's
* kvtab_type/key_find()/key_add()) via a THIRD kv_key_ops_t instance
@ -28,23 +30,16 @@
* (cache_key_assign_(), below) and returns >=0 on success, -EEXIST if the
* exact key is already present.
*
* This header intentionally exposes only what design.md's Service
* Interface specifies (cache_lookup/cache_insert/cache_clear) plus the
* key type and key ops -- no separate cache-construct/-destroy wrapper.
* xce_scheduler_t.unsatisfiable_cache (scheduler.h) is constructed and
* destroyed directly by sched_init()/sched_fini() (scheduler.c) via
* makeInstance(&kvtab_type, &params)/destroy(), using the exported
* cache_key_ops below and the XCE_UNSAT_CACHE_NSLOTS slot count -- the
* exact same convention xce_select.c's sel_node_table_init() already
* establishes for its own composite-struct-keyed kvtab_t
* (sel_node_key_ops). This cache's own unit test (test_alloc_cache.c)
* constructs/destroys its fixture's table identically, so the two never
* drift.
* The table is xce_sched_core_t.unsatisfiable_cache (core.h, bug-1932:
* moved off xce_scheduler_t so the simulation driver has one too).
* cache_clear() builds and empties it at every session start, through the
* per-session clear hook both drivers register; xce_sched_core_fini()
* destroys it. Its datakiller frees each entry.
*/
#include <kv.h>
#include "scheduler.h"
#include "core.h"
/*
* xce_cache_key_t - full cache key: the pre-computed resreq fingerprint
@ -63,100 +58,87 @@ typedef struct {
/*
* cache_key_ops - the kv_key_ops_t (kv.h) binding hash/assign/cmp/destroy
* for xce_cache_key_t. Exported so scheduler.c's sched_init()/sched_fini()
* (the real construct/destroy call sites for xce_scheduler_t.
* unsatisfiable_cache) and this cache's own test can build an identical
* kvtab_t independently.
* for xce_cache_key_t; cache_clear() builds the table with it.
*/
extern kv_key_ops_t cache_key_ops;
/*
* XCE_UNSAT_CACHE_NSLOTS - slot count for the kvtab_t backing
* xce_scheduler_t.unsatisfiable_cache. Shared by every construction call
* site (sched_init(), cache_clear()'s destroy+recreate, and this cache's
* own test) so they never drift apart. kv.c's kvtab_init() requires
* xce_sched_core_t.unsatisfiable_cache. kv.c's kvtab_init() requires
* nslots > 1.
*/
#define XCE_UNSAT_CACHE_NSLOTS 257
/*
* cache_lookup - Req 7.4: before starting full allocation for a job, look
* up its resreq fingerprint + identity in the cache.
* up its resreq fingerprint + identity in core's cache.
*
* Preconditions: obj->tag == XCE_RESREQ_PRIMARY; obj->u.primary->resreq_fp
* already populated (job-ingest time, core_build_primary_schedobj(),
* core_events.c -- task 1.2).
*
* Returns:
* 1 - hit; *out_reason_category is set to the reason category the
* ORIGINAL exhausted attempt stored at cache_insert() time (Req
* 7.5). Caller pends the job using this category, without
* invoking the plan/host iterators or preemption for this attempt.
* 0 - miss (including: cache not yet constructed).
* 1 - hit; *out is erased and refilled with a copy of the reason the
* ORIGINAL exhausted attempt stored (Req 7.5, bug-1932) and
* *out_jobid set to that attempt's job. Caller pends the job with
* it, without invoking the plan/host iterators for this attempt.
* A copy that runs out of memory part way keeps the entries copied
* so far.
* 0 - miss (including: cache not yet built); *out untouched.
* <0 - error (NULL argument, or obj is not a populated primary resreq
* scheduling object).
*/
extern int cache_lookup(xce_scheduler_t* sched, xce_sched_obj_t* obj,
int* out_reason_category);
extern int cache_lookup(xce_sched_core_t* core, xce_sched_obj_t* obj,
xce_job_preason_t* out, uint64_t* out_jobid);
/*
* cache_insert - Req 7.3: when a job's primary resreq's plan iterator is
* exhausted without a successful allocation, populate the cache with that
* resreq's fingerprint + identity and the exhausted attempt's own final
* pend-reason category (the exhausted plan iterator's own final pend
* reason's main_reason), so a later cache hit for the same key reports the
* same reason (Req 7.5).
* resreq's fingerprint + identity, a deep copy of the attempt's pend
* reason (`reasons`; NULL stores an empty one) and `jobid`, so a later hit
* for the same key reports the same reason (Req 7.5).
*
* Returns 0 on success, <0 on error (NULL argument; obj is not a populated
* primary resreq scheduling object; -EEXIST if this exact key is already
* cached; the cache is not yet constructed).
* Returns 0 on success, <0 on error (NULL core/obj; obj is not a
* populated primary resreq scheduling object; -EEXIST if this exact key
* is already cached; -ENOMEM; the cache is not yet built). Nothing is
* stored on error.
*/
extern int cache_insert(xce_scheduler_t* sched, xce_sched_obj_t* obj,
int reason_category);
extern int cache_insert(xce_sched_core_t* core, xce_sched_obj_t* obj,
const xce_job_preason_t* reasons, uint64_t jobid);
/*
* cache_clear - Req 7.2 (wiring into core_begin_session_internal() is a
* later task -- tasks.md 5.2, not this one): empty every entry from the
* cache. Called once at the start of every scheduling session.
* cache_clear - Req 7.2: empty every entry from core's cache, building the
* table on first use. Called once at the start of every scheduling
* session through xce_alloc_cache_clear_cb().
*
* kv.h's intf_kv_t exposes only add_key/del_key/find_key -- no batch
* clear-all primitive exists on kvtab_t. This implementation destroys the
* existing backing table and constructs a fresh, empty one with the same
* cache_key_ops/XCE_UNSAT_CACHE_NSLOTS in its place, which is functionally
* equivalent to clearing every entry. If reconstruction fails (e.g.
* existing backing table (its datakiller frees every entry) and constructs
* a fresh, empty one in its place. If reconstruction fails (e.g.
* makeInstance() returns NULL on -ENOMEM), the cache is left NULL:
* cache_lookup() treats a NULL cache as an unconditional miss and
* cache_insert() returns an error, so a failed clear degrades to "cache
* always misses" rather than crashing or operating on a stale table.
*/
extern void cache_clear(xce_scheduler_t* sched);
extern void cache_clear(xce_sched_core_t* core);
/*
* xce_alloc_cache_clear_cb - the xce_core_cache_clear_cb_t (core.h)
* adapter for cache_clear() above (scheduler-allocation spec, task 5.2
* round 2, Req 7.2). Registered on the real xce_scheduler_t's core via
* xce_core_set_cache_clear_cb() (main.c). See alloc_cache.c's own doc
* comment on this function for the full rationale, including why it lives
* xce_alloc_cache_clear_cb / xce_alloc_cache_lookup_cb /
* xce_alloc_cache_populate_cb - the xce_core_cache_clear_cb_t /
* xce_unsatisfiable_lookup_cb_t / xce_unsatisfiable_populate_cb_t (core.h)
* adapters for cache_clear()/cache_lookup()/cache_insert() on the given
* core. Both drivers register all three (main.c directly; the simulation
* driver from its sched_sim_args_t). See alloc_cache.c for why they live
* here rather than in driver.c.
*/
extern void xce_alloc_cache_clear_cb(xce_sched_core_t* core);
/*
* xce_alloc_cache_lookup_cb / xce_alloc_cache_populate_cb - the
* xce_unsatisfiable_lookup_cb_t / xce_unsatisfiable_populate_cb_t (core.h)
* adapters for cache_lookup()/cache_insert() above (scheduler-sim-live-
* allocation spec, task 1.1, Req 5.2). Registered on the real
* xce_scheduler_t's core via direct field assignment in main.c's
* normal-mode-only startup branch, alongside xce_alloc_cache_clear_cb()
* just above. See alloc_cache.c's own doc comment on these functions for
* the full rationale, including why they live here rather than in
* driver.c.
*/
extern int xce_alloc_cache_lookup_cb(xce_sched_core_t* core,
xce_sched_obj_t* obj,
int* out_reason_category);
extern void xce_alloc_cache_populate_cb(xce_sched_core_t* core,
xce_sched_obj_t* obj,
int reason_category);
extern int xce_alloc_cache_lookup_cb(xce_sched_core_t* core,
xce_sched_obj_t* obj,
xce_job_preason_t* out_reasons,
uint64_t* out_jobid);
extern void xce_alloc_cache_populate_cb(xce_sched_core_t* core,
xce_sched_obj_t* obj,
const xce_job_preason_t* reasons,
uint64_t jobid);
#endif

View file

@ -132,7 +132,8 @@ typedef struct {
typedef enum {
XCE_TRACE_NOPLAN_DECLINED, /* allocator accepts() declined */
XCE_TRACE_NOPLAN_GENERATOR_FAILED, /* plan_init failed */
XCE_TRACE_NOPLAN_NONE_GENERATED /* generator produced no plan */
XCE_TRACE_NOPLAN_NONE_GENERATED, /* generator produced no plan */
XCE_TRACE_NOPLAN_KNOWN_UNSAT /* bug-1932: unsatisfiable-cache hit */
} xce_sched_trace_noplan_t;
typedef enum {
@ -314,6 +315,7 @@ typedef struct sched_trace_entry_s {
} check_end;
struct { /* NO_PLAN */
xce_sched_trace_noplan_t why;
uint64_t src_jobid; /* KNOWN_UNSAT: the job that proved it */
} no_plan;
struct { /* PLAN_BEGIN, PLAN_DISCARDED, PLAN_NOT_TRIED */
int plan_no; /* 1-based, in generation order per requirement */
@ -630,6 +632,10 @@ extern void sched_trace_check_end(xce_sched_trace_rec_t* rec, int rc,
* later in the same requirement makes no statement and counts nothing. */
extern void sched_trace_no_plan(xce_sched_trace_rec_t* rec,
xce_sched_trace_noplan_t why);
/* bug-1932: the same, for a request src_jobid (0: unknown) proved
* unsatisfiable earlier this session -- the cache skipped the search. */
extern void sched_trace_known_unsat(xce_sched_trace_rec_t* rec,
uint64_t src_jobid);
/* Plans, numbered in generation order within the current requirement. */
extern void sched_trace_plan_begin(xce_sched_trace_rec_t* rec,

View file

@ -175,27 +175,6 @@ typedef struct {
typedef struct {
xce_sched_core_t core;
xcecli_ctx_t apictx;
/* Req 7.1 (scheduler-allocation spec, task 2.2): the per-session
* unsatisfiable-resreq fingerprint cache -- a kvtab_t (kv.h, RTTI `var`)
* keyed by xce_cache_key_t (sched/alloc_cache.h), value = a pend-reason
* category boxed as (void*)(intptr_t)category (Req 7.5). Constructed in
* sched_init(), destroyed in sched_fini() (scheduler.c) -- both minimal
* construct/destroy calls only, using the exported cache_key_ops
* (sched/alloc_cache.h), mirroring xce_select.c's sel_node_table_init()
* convention for a composite-struct-keyed kvtab_t. Lookup/insert
* (cache_lookup()/cache_insert(), alloc_cache.c) are confined to the
* allocator thread; NULL until sched_init() constructs it.
*
* NOTE (task 2.2 CONCERN, for a later task's attention): Req 7.2
* requires cache_clear() to run inside core_begin_session_internal()
* (core_session.c), which only receives xce_sched_core_t* core, not
* xce_scheduler_t* -- this field lives on the outer driver struct per
* this task's own scope, not on `core` above. Wiring AC 2's clear call
* is explicitly a later task (tasks.md 5.2), not this one; whoever
* implements it will need to either reach the owning xce_scheduler_t
* from `core` (core is this struct's first member, so the standard
* pointer-to-first-member idiom applies) or relocate this field. */
var unsatisfiable_cache;
xce_cluster_config_t
cluster_cfg; /* HA candidate list. Empty (candidates_count==0) only
* transiently before sched_init() runs, when no

View file

@ -164,6 +164,15 @@ typedef struct {
bool interactive; /* --interactive / -i (task 12) */
const char* log_path; /* --logfile FILE (optional; NULL means
* SCHED_DEFAULT_LOG_PATH) */
/* bug-1932: the unsatisfiable-resreq cache hooks (core.h), registered
* on the simulation core exactly as main.c registers them on the
* normal-mode one, so the simulator skips a request already proved
* unsatisfiable this session the way production does. main.c passes
* alloc_cache.c's adapters; NULL (white-box tests that do not link
* alloc_cache.c) leaves the core computing every attempt fresh. */
xce_core_cache_clear_cb_t cache_clear_cb;
xce_unsatisfiable_lookup_cb_t unsat_lookup_cb;
xce_unsatisfiable_populate_cb_t unsat_populate_cb;
} sched_sim_args_t;
/*