admin-plane breakpoint 2: YAML resourcemap wiring + queue payload deep-copy

Breakpoint 2 of the admin-plane gaps (YAML flag/resourcemap wiring), plus
the queue payload ownership fix that the t1 controller segfault of
2026-10-23 12:41:13 traced to.

1. YAML resourcemap wiring (admin-plane-gaps 1.10):
   - schema: Resource.distributions @8 / hostBased @9 uncommented --
     resourcemap had no wire destination at all before this.
   - resobj.c: resobj_init's #if 0 stub replaced with a real deep copy
     (distributions + hbased_defined); resobj_destroy frees them. Deep
     copy is mandatory: the source is myconf->cluster_config, which
     keeps owning and later frees its tree.
   - controller: reconcile_config_resources + seed_sharedinst_from_config
     (cluster.c) seed c->sharedinst for SHARED|GLOBAL resources whose
     distribution carries a value (the license-pool shape), from both
     config_activate_committed paths, ahead of the conf4sched push.
   - xceadmin: mapping.py maps resourcemap -> distributions (only the
     location:[all]+value shape sets SHARED|GLOBAL), build.py renders
     shared=true/distributions; golden expected.bin regenerated.

2. xcesched_push_configs resval double free (t1 12:39:52 abort): the
   seeded push freed the resval entries AND the backing array via
   free_name_val_list, then xlist_fini freed the same array again.
   Detach xreslist before the FREEUP/xlist_fini block (same ownership
   class bug-1506 fixed in sched_worker_change). Latent since inception:
   every pre-1.10 push ran with nresvals==0 and never armed it.

3. queue payload deep-copy (t1 12:41:13 encode_Threshold_list segfault):
   queue_init (create) MOVED the definition's users/hosts/thresholds/
   window payloads out, and queue_refresh_def MOVED them out again --
   but reconcile_config_queues runs MORE THAN ONCE over the same
   cluster_config object (boot: pre-election activation AND phase2's
   build_queues), so the second pass refreshed live queues from the
   drained definition: thresholds became the poisoned (count>0,
   array=NULL) pair, the generated encode_Threshold_list has no NULL
   guard, and the next queue/worker push died on the NULL base.
   Both paths now deep-copy (queue_init and queue_refresh_def); the
   definition keeps its payloads, (NULL/0) genuinely means "no list in
   this config", and reconcile is idempotent. New dup_group_config
   helper in utils.c (the generated code has no GroupConfig dup).
   Mirrors resobj_init's distributions deep-copy and worker.c
   apply_join_state's dup pattern.

4. UT: test_reconcile_thresholds_survive_second_pass drives a triple
   reconcile of a threshold-carrying queue and asserts definition
   intactness plus a real encode_QueueConf -> wire -> decode round
   trip; new test_config_resource_reconcile.c covers seed/deep-copy/
   non-seed classes and idempotent re-seed.

Suites: controller_unit_test 1348/1348, sched_push 4, sched_worker_change 3,
apiserver_push 2, host_runusage 11, xcelib 759, xcealloc_oom 11,
99 scheduler binaries, all worker + harness suites, xceadmin pytest 375.
t1 E2E: boot double-reconcile clean, scheduler rejoin + full conf4sched
push applied, recovered crash-era PEND job reached DONE, 30-job saturation
flow (24 RUN + 6 PEND -> DONE), live gen-12 config activation with
re-push to the connected scheduler -- no crash, all four daemons healthy.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
lei.chen 2026-10-23 14:29:29 +00:00
parent 563f8e6890
commit fc33a5da16
22 changed files with 1510 additions and 63 deletions

View file

@ -198,19 +198,42 @@ def test_build_maps_resource_type_order_consumable_release(tmp_path, capsys):
assert "'release'" not in stderr
def test_build_warns_not_fails_on_schema_valid_unmapped_resourcemap_field(tmp_path, capsys):
"""resourcemap has no destination -- controller.capnp's distributions/
hostBased fields are commented out (needs a schema change first).
def test_build_maps_resourcemap_to_distributions(tmp_path, capsys):
"""resourcemap maps to Resource.distributions (one entry per item,
order preserved; location -> hosts, value -> Value union member by
YAML type) -- no longer an "unmapped field" warning. Only the
location==["all"] + value shape (cluster-wide static shared pool)
additionally sets XCE_RESF_SHARED|XCE_RESF_GLOBAL (admin-plane-gaps
1.10); other shapes map definition-only.
"""
_write_yaml(
tmp_path / "resources.yaml",
{
"resources": [
{
"name": "gpus",
"name": "licenses",
"type": "numeric",
"resourcemap": [{"location": ["host1"], "value": 1}],
}
"consumable": True,
"resourcemap": [{"location": ["all"], "value": 100}],
},
{
"name": "hostpool",
"type": "numeric",
"resourcemap": [
{"location": ["host1", "host2"], "value": 100},
{"location": ["default"]}, # dynamic, reported
],
},
{
"name": "flagres",
"type": "boolean",
"resourcemap": [{"location": ["host1"], "value": True}],
},
{
"name": "strres",
"type": "string",
"resourcemap": [{"location": ["host1"], "value": "v1"}],
},
]
},
)
@ -219,12 +242,34 @@ def test_build_warns_not_fails_on_schema_valid_unmapped_resourcemap_field(tmp_pa
mod = load_schema()
with mod.ClusterConfig.from_bytes(data) as reader:
assert reader.resources[0].name == "gpus"
resources = {r.name: r for r in reader.resources}
licenses = resources["licenses"]
assert len(licenses.distributions) == 1
assert [str(h) for h in licenses.distributions[0].hosts] == ["all"]
assert licenses.distributions[0].value.u.which == "fvalue"
assert licenses.distributions[0].value.u.fvalue == 100.0
assert licenses.flags == 0x80 | 0x08 | 0x04 # CONSUMABLE|SHARED|GLOBAL
hostpool = resources["hostpool"]
assert len(hostpool.distributions) == 2
assert [str(h) for h in hostpool.distributions[0].hosts] == ["host1", "host2"]
assert hostpool.distributions[0].value.u.fvalue == 100.0
# dynamic shape: hosts mapped, value left unset (capnp union default)
assert [str(h) for h in hostpool.distributions[1].hosts] == ["default"]
assert hostpool.distributions[1].value.u.which == "ivalue32"
assert hostpool.flags == 0 # no SHARED|GLOBAL without location [all]
flag = resources["flagres"]
assert flag.distributions[0].value.u.which == "bvalue"
assert bool(flag.distributions[0].value.u.bvalue) is True
sres = resources["strres"]
assert sres.distributions[0].value.u.which == "svalue"
assert str(sres.distributions[0].value.u.svalue) == "v1"
stderr = capsys.readouterr().err
assert "resources.yaml" in stderr
assert "'resourcemap'" in stderr
assert "gpus" in stderr
assert "'resourcemap'" not in stderr
def test_build_warns_on_schema_valid_unmapped_queue_fields(tmp_path, capsys):