fix(scheduler): route sim reconciliation's on_dispatch() through the allocator thread (bug-1837)

sim_reconcile_unaccounted_jobs() (sim_loop.c) was calling
picker->picker_iface->on_dispatch() directly from the sim driver thread,
violating plugin.h's documented "allocator thread only" contract for
Post-allocation hooks. Dormant in practice only because builtin_fcfs.c
happens to lock internally around every hook -- a defensive measure the
contract text doesn't actually require of picker plugins.

Since scheduler-sim-live-allocation, sim mode spawns the same real
sched_job_picker()/sched_allocator() thread pair normal mode uses, so a
real allocator thread now exists to route through. Added a
reconcile_req/reconcile_ack cbuf pair on xce_sched_core_t and a reserved
XCE_SCHED_RECONCILE_WAKE sentinel pushed through jbuf to wake the
allocator thread without touching jbuf's pinned capacity-1/item-type
invariant (Req 5 AC 7). sched_allocator() services the request and calls
on_dispatch() itself; sim_reconcile_invoke_on_dispatch() blocks on the ack.
Falls back to a direct call when no allocator thread is live for a given
core (standalone unit tests), which is safe by construction.

TDD: new test spawns a stand-in allocator-relay thread and asserts
on_dispatch() fires there, not on the caller's thread -- confirmed RED
against the old direct-call code, GREEN after. Four other test files with
hand-rolled sched_allocator() stubs needed the same relay branch (found by
an actual hang at meson's timeout). Full scheduler suite: 85/85, zero
regressions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-21 12:58:57 +00:00
parent 94115b0024
commit 4f94638a1d
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
15 changed files with 541 additions and 67 deletions

View file

@ -165,6 +165,16 @@ typedef void (*xce_unsatisfiable_populate_cb_t)(xce_sched_core_t* core,
xce_sched_obj_t* obj,
int reason_category);
/* Reserved jbuf sentinel for the Recorded-Decision Reconciliation
* allocator-thread relay (bug-1837) -- see xce_sched_core_s's reconcile_req/
* reconcile_ack fields' own doc comment below for the full protocol. Never a
* real jobid (job ids are assigned by the controller's monotonic id
* generator, starting near 1, nowhere near UINT64_MAX) and distinct from
* jbuf's own pre-existing 0 "end of session" sentinel -- same reserved-value
* idiom this codebase already uses elsewhere for an analogous "not a real
* value" marker (see SCHED_SIM_START_TS_LATEST, sim.h). */
#define XCE_SCHED_RECONCILE_WAKE ((uint64_t)-1)
struct xce_sched_core_s {
mylock_t lock;
int stop;
@ -198,6 +208,26 @@ struct xce_sched_core_s {
pthread_t allocator;
cbuf_t wbuf; /* action to trigger scheduling */
cbuf_t jbuf; /* job picked for allocation */
/* Recorded-Decision Reconciliation allocator-thread relay (bug-1837):
* plugin.h documents on_dispatch()/on_pend() as allocator-thread-only
* hooks, but sim_loop.c's sim_reconcile_unaccounted_jobs() (the driver
* thread) needs to invoke on_dispatch() for a job it reconstructs from
* replay history -- there is no fresh allocation to run through the
* normal jbuf/next_job() pipeline for it (the job is already RUN per
* history, not a live candidate), so it cannot flow through jbuf's
* existing real-jobid protocol. These two dedicated cbufs let the driver
* hand the call off to the real sched_allocator() thread (sched_
* election.c) instead of calling it directly: reconcile_req carries the
* jobid to dispatch, reconcile_ack echoes on_dispatch()'s return code
* once applied. Deliberately SEPARATE from jbuf, not a widened jbuf item
* type -- jbuf's item size/capacity are a pinned determinism invariant
* (test_core_jbuf_capacity_pin.c, Req 5 AC 7) that must never change.
* See XCE_SCHED_RECONCILE_WAKE below and sched_allocator()'s matching
* branch (sched_election.c) for the full handoff protocol. Normal mode
* never populates reconcile_req (only sim_loop.c's reconciliation does),
* so this is a true no-op for normal mode's allocator thread. */
cbuf_t reconcile_req; /* jobid for on_dispatch, allocator-thread relay */
cbuf_t reconcile_ack; /* on_dispatch()'s rc, echoed back once applied */
cbuf_t decisions; /* scheduler decisions */
/* Req 5 AC 6 (scheduler-core spec, task 4.4): driver-registered
* decision callback + opaque driver context, set via

View file

@ -93,6 +93,25 @@ typedef struct {
bool interactive;
uint64_t snapshot_ts;
xce_sched_repl_state_t* repl_state;
/* bug-1837 fix: set true by sched_sim_driver_run() (sim_loop.c),
* immediately after it successfully spawns the real sched_allocator()
* thread (sched_election.c) -- both the write here and every read in
* sim_reconcile_unaccounted_jobs() happen on the SAME thread (the one
* running sched_sim_driver_run()'s own round loop), in program order, so
* this is plain single-threaded state, not a cross-thread flag; no
* atomics needed. Tells sim_reconcile_invoke_on_dispatch() (sim_loop.c)
* whether a real allocator thread exists to relay on_dispatch() through
* (core->reconcile_req/reconcile_ack, core.h) or whether this xce_sched_
* sim_driver_t was only ever driven directly (no thread pair spawned at
* all -- e.g. test_sim_reconcile_unaccounted.c's own "call sim_
* reconcile_unaccounted_jobs() standalone" Testing Strategy), in which
* case calling on_dispatch() directly is safe by construction: there is
* only ever one thread touching picker state in that case, so plugin.h's
* allocator-thread-only contract has nothing to violate. Stays false
* forever for a bare xce_sched_sim_driver_t built via fixture_init()-
* style direct core init, matching sched_sim_driver_start()'s own
* memset(sim, 0, sizeof(*sim)) default. */
bool allocator_thread_live;
} xce_sched_sim_driver_t;
/*

View file

@ -162,6 +162,17 @@ int xce_sched_core_init(xce_sched_core_t* core)
* indirection for this value without first re-deriving the
* determinism argument in AC 7. */
cbuf_init(&(core->jbuf), 1, sizeof(uint64_t));
/* bug-1837 fix: Recorded-Decision Reconciliation's allocator-thread
* relay -- see core.h's own doc comment on these two fields for the full
* protocol. Capacity 1 each, matching jbuf's own one-request-in-flight
* discipline (this is not the jbuf determinism invariant itself, so not
* subject to test_core_jbuf_capacity_pin.c's pin -- just a sized-for-
* purpose choice, since only one reconciliation request is ever
* outstanding at a time by construction). */
cbuf_init(&(core->reconcile_req), 1, sizeof(uint64_t));
cbuf_init(&(core->reconcile_ack), 1, sizeof(int));
cbuf_init(&(core->decisions), ndecisions, sizeof(xce_decision_t*));
core->resmgr = makeInstance(&xkvmgr_type, &resmgr_params);
@ -196,6 +207,8 @@ void xce_sched_core_fini(xce_sched_core_t* core)
}
cbuf_fini(&(core->decisions));
cbuf_fini(&(core->reconcile_ack));
cbuf_fini(&(core->reconcile_req));
cbuf_fini(&(core->jbuf));
cbuf_fini(&(core->wbuf));

View file

@ -202,6 +202,27 @@ void* sched_allocator(void* arg)
continue;
}
if (jobid == XCE_SCHED_RECONCILE_WAKE) {
/* bug-1837: Recorded-Decision Reconciliation relay
* (sim_reconcile_unaccounted_jobs(), sim_loop.c) -- see core.h's
* reconcile_req/reconcile_ack doc comment for the full
* handshake. Not a real job: no plugin_job_election()/
* sched_make_allocation() call, no decision built, nothing
* pushed to core->decisions -- purely a threading-contract
* relay so on_dispatch() genuinely runs here, on the allocator
* thread, exactly as plugin.h's Post-allocation hooks contract
* requires, instead of being called directly by the driver
* thread. */
uint64_t reconcile_jobid = 0;
int reconcile_rc;
cbuf_get(&(core->reconcile_req), &reconcile_jobid);
reconcile_rc = job_picker->picker_iface->on_dispatch(
job_picker, reconcile_jobid);
cbuf_put(&(core->reconcile_ack), &reconcile_rc);
continue;
}
job = sched_get_job(core, jobid);
if (job == NULL) {
fmt_jobid(jobidString, sizeof(jobidString), jobid);

View file

@ -764,6 +764,56 @@ static void sim_reconcile_toggle_prior_charges(xce_sched_core_t* core,
iter_destroy(&it);
}
/*
* sim_reconcile_invoke_on_dispatch - bug-1837 fix: invoke picker->
* picker_iface->on_dispatch() on behalf of Recorded-Decision Reconciliation
* (sim_reconcile_unaccounted_jobs(), below), routed through the real
* allocator thread when one is live, per plugin.h's documented Post-
* allocation hooks contract ("allocator thread, after each allocation
* attempt"). Direct-call fallback when `sim->allocator_thread_live` is
* false: no thread pair was ever spawned against this xce_sched_sim_driver_t
* (e.g. a standalone unit test that calls sim_reconcile_unaccounted_jobs()
* directly, never sched_sim_driver_run() -- test_sim_reconcile_unaccounted.c's
* own documented Testing Strategy), so there is provably only one thread in
* the whole process touching picker state and nothing to race.
*
* Protocol when a real allocator thread IS live (core.h's reconcile_req/
* reconcile_ack doc comment has the full rationale): push `jobid` onto
* core->reconcile_req, THEN push the reserved XCE_SCHED_RECONCILE_WAKE
* sentinel onto core->jbuf (never a real jobid, never jbuf's own 0
* end-of-session marker) to wake sched_allocator() (sched_election.c) out of
* its cbuf_get(&core->jbuf) block. Ordering matters: cbuf_put()'s own
* mutex-protected write to reconcile_req happens-before the jbuf push that
* follows it (same thread, program order), and cbuf_get()'s own
* mutex-protected read on the allocator thread happens-after the matching
* cbuf_put() (POSIX mutex unlock->lock ordering) -- so by the time the
* allocator thread's cbuf_get(&core->jbuf) returns the wake sentinel,
* reconcile_req is guaranteed to already hold this call's jobid. Blocks on
* core->reconcile_ack until sched_allocator() echoes on_dispatch()'s return
* code back -- exactly one request in flight at a time, matching jbuf's own
* capacity-1 rendezvous discipline (never widened; a completely separate
* pair of cbufs carries this protocol instead).
*/
static int sim_reconcile_invoke_on_dispatch(xce_sched_sim_driver_t* sim,
xce_sched_plugin_t* picker,
uint64_t jobid)
{
if (!sim->allocator_thread_live) {
return picker->picker_iface->on_dispatch(picker, jobid);
}
cbuf_put(&(sim->core.reconcile_req), &jobid);
{
uint64_t wake = XCE_SCHED_RECONCILE_WAKE;
int rc = 0;
cbuf_put(&(sim->core.jbuf), &wake);
cbuf_get(&(sim->core.reconcile_ack), &rc);
return rc;
}
}
/*
* sim_reconcile_unaccounted_jobs - Recorded-Decision Reconciliation
* (scheduler-sim-decision-parity spec, design.md's "Recorded-Decision
@ -1163,7 +1213,7 @@ static void sim_reconcile_unaccounted_jobs(xce_sched_sim_driver_t* sim,
* confirmed dispatched, so a later round's sim_picker_thread_fn()
* never re-offers (and re-PENDs) it. */
if (picker != NULL) {
picker->picker_iface->on_dispatch(picker, job->jobid);
sim_reconcile_invoke_on_dispatch(sim, picker, job->jobid);
}
found = 1;
@ -1349,6 +1399,13 @@ int sched_sim_driver_run(xce_sched_sim_driver_t* sim,
return -rc;
}
/* bug-1837 fix: recorded exactly once, right here, before the round loop
* below can possibly run -- see xce_sched_sim_driver_t's own doc comment
* (sim.h) on this field for why this is plain single-threaded state
* (same thread sets it and later reads it, in program order) rather than
* a cross-thread flag needing atomics. */
sim->allocator_thread_live = true;
/* Req 5 AC 16 (task 11.5, "What-if branching"): once --sim-alt-events
* is set, the ONGOING event/session stream comes from
* sim->alt_events_file instead of sim->replay_file -- the initial

View file

@ -531,6 +531,30 @@ void* sched_allocator(void* arg)
continue;
}
if (jobid == XCE_SCHED_RECONCILE_WAKE) {
/* bug-1837: this file's stand-in sched_allocator() must service
* the same Recorded-Decision Reconciliation relay protocol the
* real sched_election.c implementation does (see that file's
* matching branch for the full rationale) -- this file's own
* sched_sim_driver_run() calls set sim->allocator_thread_live,
* so sim_reconcile_unaccounted_jobs() routes on_dispatch()
* through jbuf/reconcile_req/reconcile_ack exactly as
* production does; without this branch, a reconciliation
* success here would hang forever waiting on an ack this stub
* never sends. */
uint64_t reconcile_jobid = 0;
int reconcile_rc = 0;
cbuf_get(&(core->reconcile_req), &reconcile_jobid);
(void)sched_resolve_job_picker(core, &picker);
if (picker != NULL) {
reconcile_rc = picker->picker_iface->on_dispatch(
picker, reconcile_jobid);
}
cbuf_put(&(core->reconcile_ack), &reconcile_rc);
continue;
}
job = sched_get_job(core, jobid);
if (job == NULL) {
continue;

View file

@ -483,6 +483,30 @@ void* sched_allocator(void* arg)
continue;
}
if (jobid == XCE_SCHED_RECONCILE_WAKE) {
/* bug-1837: this file's stand-in sched_allocator() must service
* the same Recorded-Decision Reconciliation relay protocol the
* real sched_election.c implementation does (see that file's
* matching branch for the full rationale) -- this file's own
* sched_sim_driver_run() calls set sim->allocator_thread_live,
* so sim_reconcile_unaccounted_jobs() routes on_dispatch()
* through jbuf/reconcile_req/reconcile_ack exactly as
* production does; without this branch, a reconciliation
* success here would hang forever waiting on an ack this stub
* never sends. */
uint64_t reconcile_jobid = 0;
int reconcile_rc = 0;
cbuf_get(&(core->reconcile_req), &reconcile_jobid);
(void)sched_resolve_job_picker(core, &picker);
if (picker != NULL) {
reconcile_rc = picker->picker_iface->on_dispatch(
picker, reconcile_jobid);
}
cbuf_put(&(core->reconcile_ack), &reconcile_rc);
continue;
}
job = sched_get_job(core, jobid);
if (job == NULL) {
continue;

View file

@ -90,6 +90,7 @@
*/
#include <errno.h>
#include <limits.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@ -2524,6 +2525,192 @@ test_reconcile_removes_reconciled_job_from_picker_candidates(void)
"PASS\n");
}
/* ---------------------------------------------------------------------- */
/* Test (bug-1837, TDD): plugin.h documents on_dispatch()/on_pend() as */
/* "Post-allocation hooks (MUST) -- allocator thread, after each allocation */
/* attempt". Recorded-Decision Reconciliation used to call on_dispatch() */
/* directly from ITS OWN caller thread (sim_reconcile_unaccounted_jobs()'s */
/* thread -- normally sched_sim_driver_run()'s driver thread in production), */
/* violating that contract by construction. Dormant in production only */
/* because builtin_fcfs.c happens to guard every hook with its own internal */
/* lock -- a defensive measure plugin.h's contract text does not actually */
/* require of picker plugins in general ("the picker thread holds no lock */
/* the allocator thread needs"). */
/* */
/* Fix: once a real allocator thread is live for a core (xce_sched_sim_ */
/* driver_t.allocator_thread_live, sim.h), sim_reconcile_invoke_on_dispatch()*/
/* (sim_loop.c) routes the call through it via core->reconcile_req/ */
/* core->jbuf's XCE_SCHED_RECONCILE_WAKE sentinel/core->reconcile_ack */
/* (core.h), and sched_election.c's sched_allocator() is what actually */
/* invokes on_dispatch() on that thread. */
/* */
/* This test proves it directly, independent of builtin_fcfs's own internal */
/* locking: a minimal test-owned picker plugin records pthread_self() from */
/* inside its on_dispatch() implementation, and a one-shot thread standing */
/* in for sched_allocator() (relay_one_reconcile_request(), below) services */
/* the relay protocol. RED before the bug-1837 fix (on_dispatch() ran on */
/* this test's OWN thread -- g_dispatch_calling_tid would equal */
/* pthread_self(), and g_seen_jbuf_wake_value would never be observed since */
/* nothing was ever pushed onto jbuf at all), GREEN after. */
/* ---------------------------------------------------------------------- */
static pthread_t g_dispatch_calling_tid;
static int g_dispatch_call_count;
static uint64_t g_seen_jbuf_wake_value;
static int thread_recording_on_dispatch(void* picker, uint64_t jobid)
{
(void)picker;
(void)jobid;
g_dispatch_calling_tid = pthread_self();
g_dispatch_call_count++;
return 0;
}
static int noop_picker_session_hook(void* picker)
{
(void)picker;
return 0;
}
static xce_sched_job_t* noop_picker_next_job_hook(void* picker)
{
(void)picker;
return NULL;
}
static int noop_picker_jobid_hook(void* picker, uint64_t jobid)
{
(void)picker;
(void)jobid;
return 0;
}
static xce_job_picker_iface_t g_thread_recording_picker_iface = {
.init = NULL,
.fini = NULL,
.begin_session = noop_picker_session_hook,
.next_job = noop_picker_next_job_hook,
.end_session = noop_picker_session_hook,
.on_dispatch = thread_recording_on_dispatch,
.on_pend = noop_picker_jobid_hook,
.on_queue_update = NULL,
.on_job_push = NULL,
.on_job_state_change = NULL,
.on_job_update = NULL,
.on_config = NULL,
.on_job_rusage = NULL,
};
static xce_sched_plugin_t g_thread_recording_plugin
= { .name = (char*)"test_reconcile_thread_picker",
.picker_iface = &g_thread_recording_picker_iface };
/* One-shot stand-in for sched_election.c's real sched_allocator() --
* services exactly one Recorded-Decision Reconciliation relay request
* (core->jbuf's XCE_SCHED_RECONCILE_WAKE sentinel, then core->reconcile_req,
* then acks via core->reconcile_ack) and returns, mirroring that function's
* own SIM_RECONCILE_JBUF_WAKE branch exactly (see its own comment,
* sched_election.c, for the full protocol this mirrors). Deliberately not a
* forever loop: this test only ever sends one request. No TEST_ASSERT_*
* calls in here -- Unity's assertion macros longjmp against the MAIN test
* thread's context, unsafe to invoke from a spawned thread; this function
* only records observations into statics, asserted on after pthread_join()
* back on the main test thread. */
static void* relay_one_reconcile_request(void* arg)
{
xce_sched_core_t* core = (xce_sched_core_t*)arg;
uint64_t wake_val = 0;
uint64_t reconcile_jobid = 0;
xce_sched_plugin_t* job_picker = NULL;
int reconcile_rc = 0;
cbuf_get(&(core->jbuf), &wake_val);
g_seen_jbuf_wake_value = wake_val;
cbuf_get(&(core->reconcile_req), &reconcile_jobid);
(void)sched_resolve_job_picker(core, &job_picker);
if (job_picker != NULL) {
reconcile_rc = job_picker->picker_iface->on_dispatch(job_picker,
reconcile_jobid);
}
cbuf_put(&(core->reconcile_ack), &reconcile_rc);
return NULL;
}
static void
test_reconcile_invokes_on_dispatch_on_allocator_thread_not_caller(void)
{
xce_sched_sim_driver_t sim;
xce_sched_job_t* job;
xce_job_alloc_t* jalloc;
replay_session_t* decoded;
int residx;
pthread_t relay_tid;
int prc;
fixture_init(&sim);
g_dispatch_calling_tid = pthread_self(); /* sentinel: only meaningful if
* overwritten below */
g_dispatch_call_count = 0;
g_seen_jbuf_wake_value = 0;
TEST_ASSERT_EQUAL_INT(
0, plugin_register_core(&(sim.core), &g_thread_recording_plugin));
sim.core.job_pick_plugin = g_thread_recording_plugin.name;
residx = register_slots_resource(&(sim.core));
add_worker(&(sim.core), "worker1", residx, 1000.0f);
job = add_job(&(sim.core), 7200ULL, JOB_STAT_RUN, NULL);
TEST_ASSERT_NULL(job->accounts);
jalloc = make_test_job_alloc_with_host(7200ULL, "worker1", "slots", 2.0f);
decoded = make_decoded_session(1000ULL, make_dispatch_entry_from_alloc(jalloc));
/* bug-1837 fix: mark a real allocator thread as live for this core, and
* actually spawn its stand-in BEFORE triggering reconciliation -- cbuf's
* own mutex+condvar makes the exact spawn/relay-vs-post ordering
* irrelevant (see relay_one_reconcile_request()'s own comment). */
sim.allocator_thread_live = true;
prc = pthread_create(&relay_tid, NULL, relay_one_reconcile_request,
&(sim.core));
TEST_ASSERT_EQUAL_INT(0, prc);
sim_reconcile_unaccounted_jobs(&sim, decoded, NULL);
pthread_join(relay_tid, NULL);
TEST_ASSERT_NOT_NULL_MESSAGE(
job->accounts, "fixture sanity: reconciliation must still have found "
"and captured the matching recorded decision");
TEST_ASSERT_EQUAL_INT_MESSAGE(1, g_dispatch_call_count,
"on_dispatch() must be invoked exactly "
"once");
TEST_ASSERT_EQUAL_UINT64_MESSAGE(
XCE_SCHED_RECONCILE_WAKE, g_seen_jbuf_wake_value,
"bug-1837: reconciliation must wake the allocator thread via the "
"reserved jbuf sentinel, never by pushing the real jobid onto jbuf "
"directly (that would incorrectly route it through the live "
"allocation chain)");
TEST_ASSERT_FALSE_MESSAGE(
pthread_equal(g_dispatch_calling_tid, pthread_self()),
"bug-1837: on_dispatch() must run on the allocator thread, never on "
"sim_reconcile_unaccounted_jobs()'s own caller thread, once a real "
"allocator thread is live for this core -- plugin.h's documented "
"Post-allocation hooks contract (\"allocator thread, after each "
"allocation attempt\")");
TEST_ASSERT_TRUE_MESSAGE(
pthread_equal(g_dispatch_calling_tid, relay_tid),
"on_dispatch() must run specifically on the spawned allocator-relay "
"thread, not some other thread");
printf("test_reconcile_invokes_on_dispatch_on_allocator_thread_not_"
"caller: PASS\n");
}
/* ---------------------------------------------------------------------- */
/* Task 4.2 fixtures (scheduler-sim-live-allocation spec, "Sim-Mode */
/* Reconciliation Narrowing" component, Requirement 3.4/3.5): register a */
@ -2816,6 +3003,8 @@ int main(void)
RUN_TEST(
test_reconcile_info_worker_and_summary_hide_completed_job_after_stray_dispatch);
RUN_TEST(test_reconcile_removes_reconciled_job_from_picker_candidates);
RUN_TEST(
test_reconcile_invokes_on_dispatch_on_allocator_thread_not_caller);
RUN_TEST(test_reconcile_registers_global_limit_usage_for_reconciled_job);
RUN_TEST(
test_reconcile_does_not_reregister_limit_for_already_decided_job);

View file

@ -375,6 +375,30 @@ void* sched_allocator(void* arg)
continue;
}
if (jobid == XCE_SCHED_RECONCILE_WAKE) {
/* bug-1837: this file's stand-in sched_allocator() must service
* the same Recorded-Decision Reconciliation relay protocol the
* real sched_election.c implementation does (see that file's
* matching branch for the full rationale) -- this file's own
* sched_sim_driver_run() calls set sim->allocator_thread_live,
* so sim_reconcile_unaccounted_jobs() routes on_dispatch()
* through jbuf/reconcile_req/reconcile_ack exactly as
* production does; without this branch, a reconciliation
* success here would hang forever waiting on an ack this stub
* never sends. */
uint64_t reconcile_jobid = 0;
int reconcile_rc = 0;
cbuf_get(&(core->reconcile_req), &reconcile_jobid);
(void)sched_resolve_job_picker(core, &picker);
if (picker != NULL) {
reconcile_rc = picker->picker_iface->on_dispatch(
picker, reconcile_jobid);
}
cbuf_put(&(core->reconcile_ack), &reconcile_rc);
continue;
}
job = sched_get_job(core, jobid);
if (job == NULL) {
continue;

View file

@ -391,6 +391,30 @@ void* sched_allocator(void* arg)
continue;
}
if (jobid == XCE_SCHED_RECONCILE_WAKE) {
/* bug-1837: this file's stand-in sched_allocator() must service
* the same Recorded-Decision Reconciliation relay protocol the
* real sched_election.c implementation does (see that file's
* matching branch for the full rationale) -- this file's own
* sched_sim_driver_run() calls set sim->allocator_thread_live,
* so sim_reconcile_unaccounted_jobs() routes on_dispatch()
* through jbuf/reconcile_req/reconcile_ack exactly as
* production does; without this branch, a reconciliation
* success here would hang forever waiting on an ack this stub
* never sends. */
uint64_t reconcile_jobid = 0;
int reconcile_rc = 0;
cbuf_get(&(core->reconcile_req), &reconcile_jobid);
(void)sched_resolve_job_picker(core, &picker);
if (picker != NULL) {
reconcile_rc = picker->picker_iface->on_dispatch(
picker, reconcile_jobid);
}
cbuf_put(&(core->reconcile_ack), &reconcile_rc);
continue;
}
job = sched_get_job(core, jobid);
if (job == NULL) {
continue;