Both job-fields update paths, the core JOB_UPDATE event (core_events.c, used by the live and sim drivers) and sched_update_existing_job() (schedobj.c), set job->priority to the new queue's priority when a job changed queue, while ingest gives every job 0. builtin_fcfs orders a queue's jobs by job priority first, so a moved job overtook every job already pending in the target queue (and snapshot restore, which sets 0, disagreed with live for moved jobs). Moving now only re-points job->queue; queue priority still orders the queues themselves. test_job_move_priority.c moves a job into a priority-10 queue and checks it keeps priority 0 and does not take the only slot from the queue's first job; test_job_update.c's in-place update case asserted the old behaviour and now expects the job's priority to be kept. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
227 lines
7.1 KiB
C
227 lines
7.1 KiB
C
/*
|
|
* test_job_move_priority.c - bug-1944: moving a pending job to another
|
|
* queue must not change the job's own priority, so the moved job does not
|
|
* overtake jobs submitted earlier to the target queue.
|
|
*
|
|
* Fixture: queue qa (priority 0) and queue qb (priority 10); jobs B1 and B2
|
|
* pending in qb (queue_time 1000, 1001), job M pending in qa (1002). M is
|
|
* moved to qb through the core's JOB_UPDATE event (core_events.c), the path
|
|
* both drivers use for a job-fields update. One worker slot, so only the
|
|
* first job the picker offers runs. FCFS orders a queue's jobs by job
|
|
* priority, then queue time: B1 must run and M must stay pending with its
|
|
* submit-time priority (0). Built like test_sim_contention_limit_driver_
|
|
* parity.c's normal-mode fixture (real scheduler_srcs, real picker and
|
|
* allocator threads, `static` fixture, exactly one RUN_TEST()).
|
|
*/
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <unity/unity.h>
|
|
|
|
#include "controller.capnp.h"
|
|
#include "decision.h"
|
|
#include "plugin.h"
|
|
#include "preason.h"
|
|
#include "resobj.h"
|
|
#include "sched/types.h"
|
|
#include "scheduler.h"
|
|
#include "xstring.h"
|
|
|
|
#define WORKER_NAME "jmove_worker"
|
|
#define QUEUE_A "jmove_qa"
|
|
#define QUEUE_B "jmove_qb"
|
|
#define TEST_RESREQ ((char*)"select[type==any] rusage[mem=1]")
|
|
|
|
#define JOB_B1_ID ((uint64_t)194401ULL)
|
|
#define JOB_B2_ID ((uint64_t)194402ULL)
|
|
#define JOB_M_ID ((uint64_t)194403ULL)
|
|
|
|
void setUp(void) { return; }
|
|
void tearDown(void) { return; }
|
|
|
|
static void register_slots_resource(var resmgr)
|
|
{
|
|
xce_resource_t res;
|
|
|
|
memset(&res, 0, sizeof(res));
|
|
res.name = strdup("slots");
|
|
res.valtype = XCE_VAL_NUMERIC;
|
|
res.ordtype = XCE_ORD_DECR;
|
|
res.flags = XCE_RESF_LIMIT | XCE_RESF_CONSUMABLE | XCE_RESF_RELEASE
|
|
| XCE_RESF_DEFAULT_HOST;
|
|
|
|
TEST_ASSERT_NOT_NULL(makeResource(resmgr, &res));
|
|
}
|
|
|
|
static void add_worker(xce_sched_core_t* core, double slots)
|
|
{
|
|
xce_sched_worker_t* w = (xce_sched_worker_t*)calloc(1, sizeof(*w));
|
|
xce_resource_t* r = NULL;
|
|
xce_val_t* val = NULL;
|
|
int rc;
|
|
|
|
TEST_ASSERT_NOT_NULL(w);
|
|
w->name = strdup(WORKER_NAME);
|
|
w->idx = -1;
|
|
zarray_init(&(w->resvals), 128, (zarray_item_killer_t)kill_sched_resval);
|
|
zarray_init(&(w->resInUse), 128, (zarray_item_killer_t)kill_sched_resval);
|
|
|
|
rc = key_find(core->resmgr, (void*)"slots", (void**)&r);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
val = (xce_val_t*)calloc(1, sizeof(*val));
|
|
TEST_ASSERT_NOT_NULL(val);
|
|
val->which = Value_u_fvalue;
|
|
val->u.fvalue = slots;
|
|
zarray_put(&(w->resvals), r->idx, val);
|
|
|
|
rc = sched_add_worker(core, w);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
}
|
|
|
|
static void add_queue(xce_sched_core_t* core, xce_sched_plugin_t* picker,
|
|
const char* name, int priority)
|
|
{
|
|
xce_sched_queue_t* q = (xce_sched_queue_t*)calloc(1, sizeof(*q));
|
|
int rc;
|
|
|
|
TEST_ASSERT_NOT_NULL(q);
|
|
q->name = makeString((char*)name);
|
|
q->priority = priority;
|
|
rc = sched_add_queue(core, q);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
picker->picker_iface->on_queue_update(picker, q);
|
|
}
|
|
|
|
static void push_job(xce_scheduler_t* sched, xce_sched_plugin_t* picker,
|
|
uint64_t jobid, const char* queue, uint64_t queue_time)
|
|
{
|
|
job_submit_data_t submit;
|
|
xce_job_t j;
|
|
xce_sched_job_t* job;
|
|
int rc;
|
|
|
|
memset(&submit, 0, sizeof(submit));
|
|
submit.queue = (char*)queue;
|
|
submit.user = (char*)"jmove_user";
|
|
submit.project_name = (char*)"default";
|
|
submit.resreq = TEST_RESREQ;
|
|
submit.mintasks = 1;
|
|
submit.maxtasks = 1;
|
|
|
|
memset(&j, 0, sizeof(j));
|
|
j.jobid = jobid;
|
|
j.state = JOB_STAT_PEND;
|
|
j.sub_time = queue_time;
|
|
j.queue_time = queue_time;
|
|
j.submit = &submit;
|
|
|
|
job = make_sched_job(sched, &j);
|
|
TEST_ASSERT_NOT_NULL(job);
|
|
job->schedobj->u.primary->resreq->minTasks = 1;
|
|
job->schedobj->u.primary->resreq->maxTasks = 1;
|
|
rc = sched_add_job(&(sched->core), job);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
picker->picker_iface->on_job_push(picker, job);
|
|
}
|
|
|
|
static void run_round(xce_sched_core_t* core)
|
|
{
|
|
pthread_t picker_tid;
|
|
pthread_t allocator_tid;
|
|
xce_decision_t* d;
|
|
int rc;
|
|
|
|
rc = pthread_create(&picker_tid, NULL, sched_job_picker, core);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = pthread_create(&allocator_tid, NULL, sched_allocator, core);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
rc = xce_core_go(core);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
do {
|
|
d = NULL;
|
|
cbuf_get(&(core->decisions), &d);
|
|
} while (d != NULL);
|
|
|
|
rc = xce_core_end_session(core);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
}
|
|
|
|
static xce_scheduler_t g_sched;
|
|
|
|
static void test_moved_job_keeps_its_priority_and_place(void)
|
|
{
|
|
xce_sched_plugin_t* fcfs = NULL;
|
|
job_submit_data_t move_submit;
|
|
xce_job_t move;
|
|
xce_core_event_t ev;
|
|
xce_sched_job_t* m;
|
|
int rc;
|
|
|
|
memset(&g_sched, 0, sizeof(g_sched));
|
|
rc = xce_sched_core_init(&(g_sched.core));
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
register_slots_resource(g_sched.core.resmgr);
|
|
|
|
fcfs = builtin_fcfs_new_plugin();
|
|
TEST_ASSERT_NOT_NULL(fcfs);
|
|
rc = plugin_register(&g_sched, fcfs);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
g_sched.core.job_pick_plugin = (char*)"builtin_fcfs";
|
|
rc = register_seqjob_allocator(&g_sched);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
rc = register_parjob_allocator(&g_sched);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
add_queue(&(g_sched.core), fcfs, QUEUE_A, 0);
|
|
add_queue(&(g_sched.core), fcfs, QUEUE_B, 10);
|
|
add_worker(&(g_sched.core), 1.0);
|
|
|
|
push_job(&g_sched, fcfs, JOB_B1_ID, QUEUE_B, 1000ULL);
|
|
push_job(&g_sched, fcfs, JOB_B2_ID, QUEUE_B, 1001ULL);
|
|
push_job(&g_sched, fcfs, JOB_M_ID, QUEUE_A, 1002ULL);
|
|
|
|
m = sched_get_job(&(g_sched.core), JOB_M_ID);
|
|
TEST_ASSERT_NOT_NULL(m);
|
|
TEST_ASSERT_EQUAL_INT(0, m->priority);
|
|
|
|
/* move M from qa to qb */
|
|
memset(&move_submit, 0, sizeof(move_submit));
|
|
move_submit.queue = (char*)QUEUE_B;
|
|
memset(&move, 0, sizeof(move));
|
|
move.jobid = JOB_M_ID;
|
|
move.state = JOB_STAT_PEND;
|
|
move.submit = &move_submit;
|
|
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.type = XCE_CORE_EVENT_JOB_UPDATE;
|
|
ev.u.job_update.job = &move;
|
|
rc = xce_core_push_events(&(g_sched.core), &ev, 1);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
TEST_ASSERT_EQUAL_STRING_MESSAGE(QUEUE_B, m->queue->name,
|
|
"the move must reach the job");
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(
|
|
0, m->priority,
|
|
"moving a job must not change its own priority (bug-1944)");
|
|
|
|
run_round(&(g_sched.core));
|
|
|
|
TEST_ASSERT_EQUAL_UINT_MESSAGE(
|
|
JOB_STAT_RUN, sched_get_job(&(g_sched.core), JOB_B1_ID)->state,
|
|
"B1, first in qb, must take the only slot");
|
|
TEST_ASSERT_EQUAL_UINT_MESSAGE(
|
|
JOB_STAT_PEND, m->state,
|
|
"the moved job must not overtake jobs already pending in qb");
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
UNITY_BEGIN();
|
|
RUN_TEST(test_moved_job_keeps_its_priority_and_place);
|
|
return UNITY_END();
|
|
}
|