feat(scheduler-controller-msg-gap): store job rusage on the job + free leaked op payloads (tasks 5.5, 5.8; bug-765 G-5/G-3)

5.5 (Req 6 / G-5): the JOB_RUSAGE notify path called the plugin but never
stored the usage data — xce_sched_job_t had no rusage field (UB-6.1 unmet).
- Add xce_job_rusages_t* rusages to xce_sched_job_t.
- New sched_apply_job_rusage() stores the report ON the job (ownership
  transferred), replacing any prior via free_JobRusages_ptr; -ENOENT unknown,
  -EINVAL NULL. Dequeue NULLs the request pointer on success; kill_sched_job
  releases job->rusages.

5.8 (G-3): sched_pend_update_killer left five new-op payloads on empty
break cases — a per-message leak since ownership is transferred on enqueue.
- Free each with the exact generated deep-freer: free_QueueConf_ptr,
  free_Resource_ptr[], free_ResourceVal_ptr[], free_ResValList_ptr,
  free_JobRusages_ptr. Safe: handlers nullify on enqueue, makeResource copies
  via makeInstance, dequeue copies values (no aliasing).

Tests: sched_job_update_test 7/7 (3 update + 4 rusage), regression
sched_test 15/15, ASAN clean (no bad-free/double-free).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-06-19 03:33:28 +00:00
parent 5c1f715ccf
commit 93124c5601
6 changed files with 155 additions and 13 deletions

View file

@ -42,6 +42,10 @@ xce_sched_project_t* sched_get_project(xce_scheduler_t* s, char* n)
}
int plugin_job_update(void* s, xce_sched_job_t* j) { (void)s; (void)j; return 0; }
int plugin_queue_update(void* s, xce_sched_queue_t* q) { (void)s; (void)q; return 0; }
int plugin_job_rusage_update(void* s, xce_sched_job_t* j, xce_job_rusages_t* r)
{
(void)s; (void)j; (void)r; return 0;
}
xce_job_account_t* make_job_account(xce_job_alloc_t* a) { (void)a; return NULL; }
void free_job_account(xce_job_account_t* a) { (void)a; }
int sched_add_user(xce_scheduler_t* s, xce_sched_user_t* u) { (void)s; (void)u; return 0; }
@ -221,6 +225,76 @@ test_update_null_payload_returns_einval(void)
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
}
/* Job rusage storage (Req 6 / bug-765 G-5) */
static xce_job_rusages_t* make_rusages(uint64_t jobid)
{
/* count==0 / usages==NULL keeps free_job_rusages a clean structural free. */
xce_job_rusages_t* r = (xce_job_rusages_t*)calloc(1, sizeof(*r));
r->jobid = jobid;
return r;
}
static void
test_rusage_stored_on_existing_job(void)
{
xce_scheduler_t sched;
fixture_init(&sched);
xce_sched_job_t* job = seed_job(&sched, 42);
xce_job_rusages_t* ru = make_rusages(42);
int rc = sched_apply_job_rusage(&sched, ru);
TEST_ASSERT_EQUAL_INT(0, rc);
/* stored ON the job (ownership transferred to the job) */
TEST_ASSERT_EQUAL_PTR(ru, job->rusages);
/* the job object itself is unchanged identity */
TEST_ASSERT_EQUAL_PTR(job, sched_get_job(&sched, 42));
/* job owns ru now; kill_sched_job/free_job_rusages will release it */
}
static void
test_rusage_replace_frees_prior(void)
{
xce_scheduler_t sched;
fixture_init(&sched);
xce_sched_job_t* job = seed_job(&sched, 7);
xce_job_rusages_t* ru1 = make_rusages(7);
xce_job_rusages_t* ru2 = make_rusages(7);
TEST_ASSERT_EQUAL_INT(0, sched_apply_job_rusage(&sched, ru1));
TEST_ASSERT_EQUAL_PTR(ru1, job->rusages);
/* second report replaces the first (prior freed, no leak/crash) */
TEST_ASSERT_EQUAL_INT(0, sched_apply_job_rusage(&sched, ru2));
TEST_ASSERT_EQUAL_PTR(ru2, job->rusages);
}
static void
test_rusage_unknown_job_returns_enoent(void)
{
xce_scheduler_t sched;
fixture_init(&sched);
xce_job_rusages_t* ru = make_rusages(999); /* no such job */
int rc = sched_apply_job_rusage(&sched, ru);
TEST_ASSERT_EQUAL_INT(-ENOENT, rc);
/* on -ENOENT the caller retains ownership */
free_JobRusages_ptr(&ru);
}
static void
test_rusage_null_returns_einval(void)
{
xce_scheduler_t sched;
fixture_init(&sched);
TEST_ASSERT_EQUAL_INT(-EINVAL, sched_apply_job_rusage(&sched, NULL));
}
int main(int argc, char* argv[])
{
(void)argc; (void)argv;
@ -228,5 +302,9 @@ int main(int argc, char* argv[])
RUN_TEST(test_update_existing_job_mutates_in_place, 0);
RUN_TEST(test_update_unknown_job_returns_enoent_no_create, 0);
RUN_TEST(test_update_null_payload_returns_einval, 0);
RUN_TEST(test_rusage_stored_on_existing_job, 0);
RUN_TEST(test_rusage_replace_frees_prior, 0);
RUN_TEST(test_rusage_unknown_job_returns_enoent, 0);
RUN_TEST(test_rusage_null_returns_einval, 0);
return UNITY_END();
}