fix(scheduler): release interned queue resreq with dereference, not free (bug-1858)

q->resreq holds an interned makeString() value (pointer past the xstring
object header). core_upsert_queue() and sched_update_existing_queue()
released the old value with FREEUP()/free() when a queue update carried a
new resreq, an ASAN bad-free. Seen as HA soak replay-parity errors=1: the
sim replay of a snapshot-restored queue followed by a config push aborted
in core_upsert_queue.

Both sites now dereference() the old value (NULL-guarded). Regression
tests cover each site; the existing queue-update test had skipped the
resreq branch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-24 14:21:20 +00:00
parent 441008cd30
commit 9e9f2798a0
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
4 changed files with 65 additions and 2 deletions

View file

@ -457,6 +457,40 @@ test_push_events_fires_each_hook_once_per_event_with_picker_idle(void)
TEST_ASSERT_EQUAL_PTR(&rusages, sched_get_job(&core, 42)->rusages);
}
/* bug-1858: a QUEUE_UPDATE carrying a resreq for an existing queue whose
* resreq is already set must dereference() the old interned makeString()
* value, not free() it (ASAN bad-free in core_upsert_queue, first seen
* replaying an HA-soak recording). */
static void
test_queue_update_replaces_existing_interned_resreq(void)
{
xce_sched_core_t core;
xce_core_event_t ev;
xce_queue_t wire_queue;
xce_sched_queue_t* q;
fixture_init(&core, &g_full_plugin);
memset(&wire_queue, 0, sizeof(wire_queue));
wire_queue.queue = "normal";
wire_queue.status = QUEUE_STAT_OPEN;
memset(&ev, 0, sizeof(ev));
ev.type = XCE_CORE_EVENT_QUEUE_UPDATE;
ev.u.queue_update.queue = &wire_queue;
/* create, then set a resreq, then replace it */
TEST_ASSERT_EQUAL_INT(0, xce_core_push_events(&core, &ev, 1));
wire_queue.resreq = "type == any";
TEST_ASSERT_EQUAL_INT(0, xce_core_push_events(&core, &ev, 1));
wire_queue.resreq = "type == local";
TEST_ASSERT_EQUAL_INT(0, xce_core_push_events(&core, &ev, 1));
q = sched_get_queue(&core, "normal");
TEST_ASSERT_NOT_NULL(q);
TEST_ASSERT_EQUAL_STRING("type == local", q->resreq);
}
/* JOBS_PUSH with njobs > 1: on_job_push must fire once PER JOB, not once
* per event -- Req 5 AC 4's "on_job_push(picker, job) for each job in the
* batch". */
@ -1849,6 +1883,7 @@ int main(void)
UNITY_BEGIN();
RUN_TEST(test_push_events_fires_each_hook_once_per_event_with_picker_idle);
RUN_TEST(test_jobs_push_fires_on_job_push_once_per_job);
RUN_TEST(test_queue_update_replaces_existing_interned_resreq);
RUN_TEST(test_jobs_push_duplicate_jobid_does_not_call_on_job_push_or_leak);
RUN_TEST(test_jobs_push_duplicate_jobid_with_newer_state_updates_existing_job);
RUN_TEST(test_jobs_push_duplicate_jobid_run_state_transfers_allocation);