feat(scheduler-allocation): add resreq fingerprinting for the cache
Adds rusage_hash to xce_primary_resreq_t (computed once by parse_resreq_primary() via a new hash_rusages() helper, hashing rusage content rather than raw struct bytes since resname is a non-interned strdup'd pointer), the xce_resreq_fingerprint_t value type, and resreq_fp on xce_primary_schedobj_t, populated once at real job-ingest time in core_build_primary_schedobj(). Task 1.2 (scheduler-allocation spec). Requirement 7.1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
This commit is contained in:
parent
d5d2c1caf5
commit
5a6706761d
9 changed files with 550 additions and 4 deletions
|
|
@ -30,10 +30,26 @@ typedef struct {
|
|||
struct xce_sched_obj_;
|
||||
typedef struct xce_sched_obj_ xce_sched_obj_t;
|
||||
|
||||
/* Req 7.1 (scheduler-allocation spec): pre-computed resreq portion of the
|
||||
* unsatisfiable-resreq cache key -- resource-usage shape only, independent
|
||||
* of job identity (user/queue/project, which the full xce_cache_key_t adds
|
||||
* at lookup time). Value object, immutable once computed, fitting in a
|
||||
* single cache line. See requirements.md's Requirement 7 "New types". */
|
||||
typedef struct {
|
||||
xce_sched_obj_t* owner;
|
||||
xce_primary_resreq_t* resreq;
|
||||
xce_pendreason_t* reason;
|
||||
int min_tasks;
|
||||
int max_tasks;
|
||||
uint32_t select_node_id; /* CSE-interned select-root node_id; 0 if select is NULL */
|
||||
uint64_t rusage_hash; /* copied from xce_primary_resreq_t.rusage_hash */
|
||||
} xce_resreq_fingerprint_t;
|
||||
|
||||
typedef struct {
|
||||
xce_sched_obj_t* owner;
|
||||
xce_primary_resreq_t* resreq;
|
||||
xce_pendreason_t* reason;
|
||||
/* Req 7.1: populated once on job ingest (core_build_primary_schedobj(),
|
||||
* src/scheduler/src/core_events.c), read by every later cache lookup
|
||||
* for this job so the fingerprint is never recomputed. */
|
||||
xce_resreq_fingerprint_t resreq_fp;
|
||||
} xce_primary_schedobj_t;
|
||||
|
||||
typedef struct {
|
||||
|
|
|
|||
|
|
@ -123,6 +123,17 @@ static void core_build_primary_schedobj(xce_sched_obj_t* obj,
|
|||
primary->reason = NULL;
|
||||
primary->owner = obj;
|
||||
|
||||
/* Req 7.1 (scheduler-allocation spec): compute the resreq fingerprint
|
||||
* once, here, at real job-ingest time -- the only construction site
|
||||
* for xce_primary_schedobj_t. Every later unsatisfiable-resreq cache
|
||||
* lookup for this job reuses this value instead of recomputing it. */
|
||||
primary->resreq_fp.min_tasks = resreq->minTasks;
|
||||
primary->resreq_fp.max_tasks = resreq->maxTasks;
|
||||
primary->resreq_fp.select_node_id =
|
||||
(resreq->select != NULL && resreq->select->root != NULL)
|
||||
? resreq->select->root->node_id : 0;
|
||||
primary->resreq_fp.rusage_hash = resreq->rusage_hash;
|
||||
|
||||
obj->u.primary = primary;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1473,6 +1473,78 @@ static void test_config_push_decomposes_queues_resources_and_resvals(void)
|
|||
TEST_ASSERT_EQUAL_PTR(found_queue, g_counts.last_queue_update);
|
||||
}
|
||||
|
||||
/*
|
||||
* Req 7.1 regression (scheduler-allocation spec, task 1.2, round 2):
|
||||
* "The resreq fingerprint is stored on the scheduling object
|
||||
* (xce_primary_schedobj_t) and computed once on job ingest." Round 1 added
|
||||
* xce_resreq_fingerprint_t (sched/types.h) and rusage_hash (resreq.h/
|
||||
* resreq.c) but never actually wired population into the real job-ingest
|
||||
* construction site, core_build_primary_schedobj() (this file, right below
|
||||
* the white-box #include) -- obj->u.primary->resreq_fp was left calloc'd
|
||||
* to all-zero forever. This test calls the REAL core_build_primary_schedobj()
|
||||
* (visible here via this file's white-box #include of ../src/core_events.c,
|
||||
* same convention as every other static-function test in this file) with a
|
||||
* REAL xce_primary_resreq_t produced by the REAL parse_resreq_primary()
|
||||
* (src/lib/resreq.c, linked from libxce) -- not a mirror struct like
|
||||
* src/lib/testsuite/test_resreq_fingerprint.c's own fingerprint_from_resreq()
|
||||
* helper had to use (that file cannot reach src/scheduler/include's real
|
||||
* xce_resreq_fingerprint_t/xce_primary_schedobj_t -- see its header
|
||||
* comment) -- so this is the first test anywhere that proves the real
|
||||
* struct is actually populated at the real ingest call site, not just that
|
||||
* the standalone hashing logic works in isolation.
|
||||
*
|
||||
* select_node_id-nonzero case intentionally NOT covered here: producing a
|
||||
* real interned CSE select-root (root->node_id != 0) requires
|
||||
* sel_node_table_init() + sel_optimize_set(1) + UNIT_TEST-gated
|
||||
* sel_optimize_test_reset() teardown -- machinery no existing fixture in
|
||||
* this file sets up, and this file's tests are never torn down by design
|
||||
* (see file header comment), so enabling CSE mode here would leak into
|
||||
* every other test in this binary via the process-global select intern
|
||||
* table. The underlying select_node_id assignment logic itself is already
|
||||
* fully covered by test_resreq_fingerprint.c's
|
||||
* test_resreq_fingerprint_select_node_id_stable_for_identical_select --
|
||||
* this is a documented gap, not a missed case, per task 1.2's own escape
|
||||
* hatch for exactly this situation.
|
||||
*/
|
||||
static void
|
||||
test_core_build_primary_schedobj_populates_resreq_fp_from_parsed_resreq(void)
|
||||
{
|
||||
xce_sched_obj_t obj;
|
||||
xce_primary_resreq_t resreq;
|
||||
char* errmsg = NULL;
|
||||
int rc;
|
||||
|
||||
memset(&obj, 0, sizeof(obj));
|
||||
memset(&resreq, 0, sizeof(resreq));
|
||||
|
||||
rc = parse_resreq_primary(&resreq,
|
||||
(char*)"rusage[mem=20:duration=300:decay=1]",
|
||||
NULL, &errmsg);
|
||||
TEST_ASSERT_TRUE(rc >= 0);
|
||||
TEST_ASSERT_NULL(resreq.select);
|
||||
/* Non-degenerate: a real hash over non-empty rusage content -- rules
|
||||
* out a trivial "both sides are 0" false pass below. */
|
||||
TEST_ASSERT_NOT_EQUAL_UINT64(0, resreq.rusage_hash);
|
||||
|
||||
core_build_primary_schedobj(&obj, &resreq);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(XCE_RESREQ_PRIMARY, obj.tag);
|
||||
TEST_ASSERT_NOT_NULL(obj.u.primary);
|
||||
TEST_ASSERT_EQUAL_PTR(&resreq, obj.u.primary->resreq);
|
||||
|
||||
/* The fix under test: resreq_fp must mirror the parsed resreq's
|
||||
* min/max tasks, select_node_id (0 -- no select expression here), and
|
||||
* rusage_hash -- not sit at calloc's all-zero default. */
|
||||
TEST_ASSERT_EQUAL_INT(resreq.minTasks, obj.u.primary->resreq_fp.min_tasks);
|
||||
TEST_ASSERT_EQUAL_INT(resreq.maxTasks, obj.u.primary->resreq_fp.max_tasks);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, obj.u.primary->resreq_fp.select_node_id);
|
||||
TEST_ASSERT_EQUAL_UINT64(resreq.rusage_hash,
|
||||
obj.u.primary->resreq_fp.rusage_hash);
|
||||
|
||||
kill_primary_resreq(&resreq);
|
||||
FREEUP(errmsg);
|
||||
}
|
||||
|
||||
/* Session-active rejection (Req 5 AC 8 / design.md precondition):
|
||||
* core_push_events() is only valid between sessions. */
|
||||
static void
|
||||
|
|
@ -1558,6 +1630,7 @@ int main(void)
|
|||
RUN_TEST(test_job_rusage_applies_state_when_hook_is_null);
|
||||
RUN_TEST(test_job_rusage_reconciles_mem_reservation_against_usage);
|
||||
RUN_TEST(test_config_push_decomposes_queues_resources_and_resvals);
|
||||
RUN_TEST(test_core_build_primary_schedobj_populates_resreq_fp_from_parsed_resreq);
|
||||
RUN_TEST(test_push_events_rejects_with_ebusy_when_session_active_no_mutation);
|
||||
RUN_TEST(test_push_events_null_core_returns_einval);
|
||||
RUN_TEST(test_push_events_null_events_with_positive_nevents_returns_einval);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue