fix(scheduler): record each pend reason once per job (bug-1927)
The scheduler appended one NO_RESOURCE/MATCH_FAIL entry per host per placement plan, so a pending parallel job carried plans x hosts copies (~93k entries in the 1000-host bounds test). libxce gains add_job_preason_once() with a per-context hash index keyed on (host, resource, code) that keeps first-occurrence order; the scheduler's three builders use it. add_job_preason() is unchanged for other callers. The parjob equivalence test now expects each short host once; the bounds test runs in ~2 s instead of ~19 s. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6c3393aa86
commit
9f9b63dd24
10 changed files with 517 additions and 17 deletions
|
|
@ -162,6 +162,183 @@ int add_job_preason(xce_job_preason_t* reasons, int code, char* host, char* res)
|
|||
return rc;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* De-duplicating builder (bug-1927). Contract in preason.h. An open-
|
||||
* addressing table (linear probing, load <= 1/2) of list positions keyed by
|
||||
* the entry's hash; a hit is confirmed against the list entry itself, so
|
||||
* the table never has to follow erases of the lists.
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
#define PREASON_INDEX_MIN_SLOTS 64
|
||||
|
||||
static uint32_t preason_fnv1a(uint32_t h, const void* data, size_t n)
|
||||
{
|
||||
const unsigned char* p = (const unsigned char*)data;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
h ^= p[i];
|
||||
h *= 16777619u;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/* host is never NULL here ("" for a host-less resource reason) */
|
||||
static uint32_t preason_key_hash(int is_res, int code, const char* host,
|
||||
const char* res)
|
||||
{
|
||||
uint32_t h = 2166136261u;
|
||||
|
||||
h = preason_fnv1a(h, &is_res, sizeof(is_res));
|
||||
h = preason_fnv1a(h, &code, sizeof(code));
|
||||
h = preason_fnv1a(h, host, strlen(host) + 1);
|
||||
if (is_res) {
|
||||
h = preason_fnv1a(h, res, strlen(res) + 1);
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Does `ref` name a list entry, still present, equal to the key? */
|
||||
static int preason_ref_matches(const xce_job_preason_t* r, int ref, int is_res,
|
||||
int code, const char* host, const char* res)
|
||||
{
|
||||
int pos = (ref - 1) >> 1;
|
||||
|
||||
if (((ref - 1) & 1) != is_res) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (is_res) {
|
||||
const xce_res_reason_t* e;
|
||||
|
||||
if (pos >= r->nres_reasons) {
|
||||
return 0;
|
||||
}
|
||||
e = r->res_based_reasons[pos];
|
||||
return (e != NULL) && (e->reason == code) && str_eq(e->host, host)
|
||||
&& str_eq(e->resource, res);
|
||||
} else {
|
||||
const xce_host_reason_t* e;
|
||||
|
||||
if (pos >= r->nhost_reasons) {
|
||||
return 0;
|
||||
}
|
||||
e = r->host_based_reasons[pos];
|
||||
return (e != NULL) && (e->reason == code) && str_eq(e->host, host);
|
||||
}
|
||||
}
|
||||
|
||||
/* Doubles the table (first size PREASON_INDEX_MIN_SLOTS), rehashing every
|
||||
* slot from its stored hash. 0 or -ENOMEM with the table unchanged. */
|
||||
static int preason_index_grow(xce_preason_index_t* idx)
|
||||
{
|
||||
uint32_t* hashes;
|
||||
int* refs;
|
||||
int n;
|
||||
int i;
|
||||
|
||||
if (idx->nslots > (1 << 29)) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
n = (idx->nslots == 0) ? PREASON_INDEX_MIN_SLOTS : idx->nslots * 2;
|
||||
hashes = (uint32_t*)calloc((size_t)n, sizeof(*hashes));
|
||||
refs = (int*)calloc((size_t)n, sizeof(*refs));
|
||||
if ((hashes == NULL) || (refs == NULL)) {
|
||||
FREEUP(hashes);
|
||||
FREEUP(refs);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (i = 0; i < idx->nslots; i++) {
|
||||
int j;
|
||||
|
||||
if (idx->refs[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
for (j = (int)(idx->hashes[i] & (uint32_t)(n - 1)); refs[j] != 0;
|
||||
j = (j + 1) & (n - 1)) {
|
||||
}
|
||||
hashes[j] = idx->hashes[i];
|
||||
refs[j] = idx->refs[i];
|
||||
}
|
||||
|
||||
FREEUP(idx->hashes);
|
||||
FREEUP(idx->refs);
|
||||
idx->hashes = hashes;
|
||||
idx->refs = refs;
|
||||
idx->nslots = n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int add_job_preason_once(xce_job_preason_t* reasons, xce_preason_index_t* idx,
|
||||
int code, char* host, char* res)
|
||||
{
|
||||
const char* key_host;
|
||||
uint32_t hash;
|
||||
int is_res;
|
||||
int mask;
|
||||
int j;
|
||||
int rc;
|
||||
|
||||
if ((reasons == NULL) || (idx == NULL)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((host == NULL) && (res == NULL)) {
|
||||
rc = add_job_preason(reasons, code, NULL, NULL);
|
||||
return (rc < 0) ? rc : 0;
|
||||
}
|
||||
|
||||
if ((2 * (idx->nused + 1)) > idx->nslots) {
|
||||
rc = preason_index_grow(idx);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
is_res = (res != NULL);
|
||||
key_host = (host != NULL) ? host : "";
|
||||
hash = preason_key_hash(is_res, code, key_host, res);
|
||||
mask = idx->nslots - 1;
|
||||
|
||||
for (j = (int)(hash & (uint32_t)mask); idx->refs[j] != 0;
|
||||
j = (j + 1) & mask) {
|
||||
if ((idx->hashes[j] == hash)
|
||||
&& preason_ref_matches(reasons, idx->refs[j], is_res, code,
|
||||
key_host, res)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
rc = add_job_preason(reasons, code, host, res);
|
||||
if (rc <= 0) {
|
||||
return (rc < 0) ? rc : -EINVAL;
|
||||
}
|
||||
|
||||
/* rc is the list's new count: the entry sits at rc - 1 */
|
||||
idx->hashes[j] = hash;
|
||||
idx->refs[j] = 1 + (((rc - 1) << 1) | is_res);
|
||||
idx->nused++;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void erase_preason_index(xce_preason_index_t* idx)
|
||||
{
|
||||
if (idx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
FREEUP(idx->hashes);
|
||||
FREEUP(idx->refs);
|
||||
idx->nslots = 0;
|
||||
idx->nused = 0;
|
||||
}
|
||||
|
||||
xce_pendreason_t *make_pendreason(uint64_t jobid) {
|
||||
xce_pendreason_t *reason = NULL;
|
||||
xce_job_preason_t *preason = NULL;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -518,6 +519,179 @@ static void test_preason_eq_does_not_modify_arguments(void)
|
|||
erase_job_preason(&b);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* bug-1927: add_job_preason_once() keeps each (host, resource, code) and
|
||||
* each (host, code) once, in first-add order.
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
static void assert_res_entry(const xce_job_preason_t* r, int i, int code,
|
||||
const char* host, const char* res)
|
||||
{
|
||||
TEST_ASSERT_TRUE(i < r->nres_reasons);
|
||||
TEST_ASSERT_NOT_NULL(r->res_based_reasons[i]);
|
||||
TEST_ASSERT_EQUAL_INT(code, r->res_based_reasons[i]->reason);
|
||||
TEST_ASSERT_EQUAL_STRING(host, r->res_based_reasons[i]->host);
|
||||
TEST_ASSERT_EQUAL_STRING(res, r->res_based_reasons[i]->resource);
|
||||
}
|
||||
|
||||
static void test_once_identical_res_reason_kept_once(void)
|
||||
{
|
||||
xce_job_preason_t r;
|
||||
xce_preason_index_t idx;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
memset(&idx, 0, sizeof(idx));
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(1, add_job_preason_once(&r, &idx, 2, "h1", "slots"));
|
||||
TEST_ASSERT_EQUAL_INT(0, add_job_preason_once(&r, &idx, 2, "h1", "slots"));
|
||||
TEST_ASSERT_EQUAL_INT(0, add_job_preason_once(&r, &idx, 2, "h1", "slots"));
|
||||
TEST_ASSERT_EQUAL_INT(1, r.nres_reasons);
|
||||
TEST_ASSERT_EQUAL_INT(0, r.nhost_reasons);
|
||||
assert_res_entry(&r, 0, 2, "h1", "slots");
|
||||
|
||||
erase_preason_index(&idx);
|
||||
erase_job_preason(&r);
|
||||
}
|
||||
|
||||
static void test_once_identical_host_reason_kept_once(void)
|
||||
{
|
||||
xce_job_preason_t r;
|
||||
xce_preason_index_t idx;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
memset(&idx, 0, sizeof(idx));
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(1, add_job_preason_once(&r, &idx, 3, "h1", NULL));
|
||||
TEST_ASSERT_EQUAL_INT(0, add_job_preason_once(&r, &idx, 3, "h1", NULL));
|
||||
TEST_ASSERT_EQUAL_INT(1, r.nhost_reasons);
|
||||
TEST_ASSERT_EQUAL_INT(0, r.nres_reasons);
|
||||
TEST_ASSERT_EQUAL_INT(3, r.host_based_reasons[0]->reason);
|
||||
TEST_ASSERT_EQUAL_STRING("h1", r.host_based_reasons[0]->host);
|
||||
|
||||
erase_preason_index(&idx);
|
||||
erase_job_preason(&r);
|
||||
}
|
||||
|
||||
/* Entries differing in any one of code, host or resource are distinct; a
|
||||
* host reason and a resource reason on the same host are distinct; a
|
||||
* host-less (shared) resource reason is kept once too. First-add order. */
|
||||
static void test_once_distinct_entries_kept_in_first_add_order(void)
|
||||
{
|
||||
xce_job_preason_t r;
|
||||
xce_preason_index_t idx;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
memset(&idx, 0, sizeof(idx));
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(1, add_job_preason_once(&r, &idx, 2, "h1", "slots"));
|
||||
TEST_ASSERT_EQUAL_INT(2, add_job_preason_once(&r, &idx, 2, "h1", "mem"));
|
||||
TEST_ASSERT_EQUAL_INT(3, add_job_preason_once(&r, &idx, 2, "h2", "slots"));
|
||||
TEST_ASSERT_EQUAL_INT(4, add_job_preason_once(&r, &idx, 1, "h1", "slots"));
|
||||
TEST_ASSERT_EQUAL_INT(5, add_job_preason_once(&r, &idx, 2, NULL, "lic"));
|
||||
TEST_ASSERT_EQUAL_INT(1, add_job_preason_once(&r, &idx, 2, "h1", NULL));
|
||||
TEST_ASSERT_EQUAL_INT(2, add_job_preason_once(&r, &idx, 3, "h1", NULL));
|
||||
/* every one again, in another order: nothing appended */
|
||||
TEST_ASSERT_EQUAL_INT(0, add_job_preason_once(&r, &idx, 3, "h1", NULL));
|
||||
TEST_ASSERT_EQUAL_INT(0, add_job_preason_once(&r, &idx, 2, NULL, "lic"));
|
||||
TEST_ASSERT_EQUAL_INT(0, add_job_preason_once(&r, &idx, 2, "h2", "slots"));
|
||||
TEST_ASSERT_EQUAL_INT(0, add_job_preason_once(&r, &idx, 1, "h1", "slots"));
|
||||
TEST_ASSERT_EQUAL_INT(0, add_job_preason_once(&r, &idx, 2, "h1", "mem"));
|
||||
TEST_ASSERT_EQUAL_INT(0, add_job_preason_once(&r, &idx, 2, "h1", NULL));
|
||||
TEST_ASSERT_EQUAL_INT(0, add_job_preason_once(&r, &idx, 2, "h1", "slots"));
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(5, r.nres_reasons);
|
||||
assert_res_entry(&r, 0, 2, "h1", "slots");
|
||||
assert_res_entry(&r, 1, 2, "h1", "mem");
|
||||
assert_res_entry(&r, 2, 2, "h2", "slots");
|
||||
assert_res_entry(&r, 3, 1, "h1", "slots");
|
||||
assert_res_entry(&r, 4, 2, "", "lic"); /* host-less: stored as "" */
|
||||
TEST_ASSERT_EQUAL_INT(2, r.nhost_reasons);
|
||||
TEST_ASSERT_EQUAL_INT(2, r.host_based_reasons[0]->reason);
|
||||
TEST_ASSERT_EQUAL_INT(3, r.host_based_reasons[1]->reason);
|
||||
|
||||
erase_preason_index(&idx);
|
||||
erase_job_preason(&r);
|
||||
}
|
||||
|
||||
/* A main reason erases the lists; the index must not then claim the
|
||||
* erased entries are still present. */
|
||||
static void test_once_after_main_reason_erase_adds_again(void)
|
||||
{
|
||||
xce_job_preason_t r;
|
||||
xce_preason_index_t idx;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
memset(&idx, 0, sizeof(idx));
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(1, add_job_preason_once(&r, &idx, 2, "h1", "slots"));
|
||||
TEST_ASSERT_EQUAL_INT(1, add_job_preason_once(&r, &idx, 3, "h2", NULL));
|
||||
TEST_ASSERT_EQUAL_INT(0, add_job_preason_once(&r, &idx, 1, NULL, NULL));
|
||||
TEST_ASSERT_EQUAL_INT(1, r.main_reason);
|
||||
TEST_ASSERT_EQUAL_INT(0, r.nres_reasons);
|
||||
TEST_ASSERT_EQUAL_INT(0, r.nhost_reasons);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(1, add_job_preason_once(&r, &idx, 3, "h2", NULL));
|
||||
TEST_ASSERT_EQUAL_INT(1, add_job_preason_once(&r, &idx, 2, "h9", "mem"));
|
||||
TEST_ASSERT_EQUAL_INT(2, add_job_preason_once(&r, &idx, 2, "h1", "slots"));
|
||||
TEST_ASSERT_EQUAL_INT(0, add_job_preason_once(&r, &idx, 2, "h1", "slots"));
|
||||
TEST_ASSERT_EQUAL_INT(2, r.nres_reasons);
|
||||
assert_res_entry(&r, 0, 2, "h9", "mem");
|
||||
assert_res_entry(&r, 1, 2, "h1", "slots");
|
||||
TEST_ASSERT_EQUAL_INT(1, r.nhost_reasons);
|
||||
|
||||
erase_preason_index(&idx);
|
||||
erase_job_preason(&r);
|
||||
}
|
||||
|
||||
/* The bounds-test shape at scale: 64 passes over 2000 hosts keep 2000
|
||||
* entries (the index grows past its first size). */
|
||||
static void test_once_many_passes_keep_one_entry_per_host(void)
|
||||
{
|
||||
xce_job_preason_t r;
|
||||
xce_preason_index_t idx;
|
||||
char host[16];
|
||||
int pass;
|
||||
int i;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
memset(&idx, 0, sizeof(idx));
|
||||
|
||||
for (pass = 0; pass < 64; pass++) {
|
||||
for (i = 0; i < 2000; i++) {
|
||||
snprintf(host, sizeof(host), "bh%04d", i);
|
||||
TEST_ASSERT_EQUAL_INT((pass == 0) ? (i + 1) : 0,
|
||||
add_job_preason_once(&r, &idx, 2, host,
|
||||
"slots"));
|
||||
}
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT(2000, r.nres_reasons);
|
||||
for (i = 0; i < 2000; i++) {
|
||||
snprintf(host, sizeof(host), "bh%04d", i);
|
||||
assert_res_entry(&r, i, 2, host, "slots");
|
||||
}
|
||||
|
||||
erase_preason_index(&idx);
|
||||
erase_job_preason(&r);
|
||||
}
|
||||
|
||||
static void test_once_bad_arguments(void)
|
||||
{
|
||||
xce_job_preason_t r;
|
||||
xce_preason_index_t idx;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
memset(&idx, 0, sizeof(idx));
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, add_job_preason_once(NULL, &idx, 2, "h1",
|
||||
"slots"));
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, add_job_preason_once(&r, NULL, 2, "h1",
|
||||
"slots"));
|
||||
TEST_ASSERT_EQUAL_INT(0, r.nres_reasons);
|
||||
erase_preason_index(NULL);
|
||||
erase_preason_index(&idx); /* never used: nothing to free */
|
||||
}
|
||||
|
||||
void test_preason(void)
|
||||
{
|
||||
printf("\n>> preason test\n\n");
|
||||
|
|
@ -542,4 +716,10 @@ void test_preason(void)
|
|||
RUN_TEST(test_preason_eq_null_list_entries);
|
||||
RUN_TEST(test_preason_eq_null_string_fields);
|
||||
RUN_TEST(test_preason_eq_does_not_modify_arguments);
|
||||
RUN_TEST(test_once_identical_res_reason_kept_once);
|
||||
RUN_TEST(test_once_identical_host_reason_kept_once);
|
||||
RUN_TEST(test_once_distinct_entries_kept_in_first_add_order);
|
||||
RUN_TEST(test_once_after_main_reason_erase_adds_again);
|
||||
RUN_TEST(test_once_many_passes_keep_one_entry_per_host);
|
||||
RUN_TEST(test_once_bad_arguments);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue