merge: sync Rongsong's scheduler-framework (88 commits, f01ae353..c5b172c7)

Two-sided divergence from f01ae353 (2026-08-30): theirs +88 (worker
rejoin-resync reconciliation, terminal-state bug-1737/1738/1739/1764,
scheduler-sim verbosity, hermetic offline build, go mod vendor of Go deps
in apiserver -- 3375 pure-adds), ours +89 (job-finish robustness bug-1509
B1/B3, PEND-reason, worker reconnect resend, apiserver job control).

Conflict resolutions (kept both sides' fixes):
- controller_connector.c checking_connection(): combine theirs
  xcecd_resync_is_pending() rejoin deferral gate with ours
  resend_job_status() actual-state re-report on rejoin success.
- job.c: keep both unknown-finish registry (ours, bug-1509) and
  record_job_finish_fact_at (theirs, bug-1747); keep both
  mark_jobs_unknown_core family (ours) and record_job_reconcile_*
  (theirs). No double-notify: on_job_finish unchanged, theirs is
  resync-path-only.
- scheduler handler.c/test_handlers.c/meson: theirs schedExitJob
  exit_code branch (bug-1737) + ours jobDispatchFail success-ack no-op
  (bug-1512) + configpush enq_pend_update (bug-1513) both present; both
  test suites and REPL/sim targets wired.
- apiserver jobs.go: keep ours normalizeJobInt64/jobArrayIdx superset +
  JobIDHuman; drop theirs strict IntOrInt64 early-reject (superseded --
  ours tolerates float64/json.Number, no panic path).
- jobs_test.go: keep both regression suites (ours bug-1490, theirs
  bug-1742).
- .kiro/specs scheduler-sim-*/worker-rejoin-resync + .wolf/*: keep our
  deletions (established convention -- drop Rongsong bookkeeping on merge).

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
lei.chen 2026-09-03 14:11:43 +00:00
commit 5e2985542f
3469 changed files with 1466599 additions and 382 deletions

View file

@ -25,6 +25,12 @@ typedef struct {
int nlimits;
xce_limit_config_t** limits;
uint64_t config_generation; /* monotonic audit counter from capnp schema */
/* worker-rejoin-resync (Req 3.7, 5.3): shared, cluster-wide via
* operational-uniformity override (design.md Configuration Model) --
* alongside heartbeat_interval/inactive_heartbeat_count above, same
* domain. */
int worker_resync_window_ms;
int worker_resync_timeout_ms;
} cluster_config_t;
#endif

View file

@ -60,6 +60,31 @@ extern int xce_ctrlstatus_req(endpoint_t*);
extern int xce_get_req(endpoint_t*, xce_get_t*);
extern int xce_reply_get(endpoint_t*, xce_peerinfo_t*, xce_reply_t*,
xce_check_fn_t, void*, int);
/*
* xce_job_state_query_req / xce_job_state_report_req: send the worker-rejoin-
* resync job-state reconciliation exchange's paginated request/reply pair
* (worker-rejoin-resync task 1.4, Req 8.1/8.2/8.3) --
* OPCode_jobStateQuery (controller -> worker, query->jobids) and
* OPCode_jobStateReport (worker -> controller, report->entries) -- using the
* same {pos,count,ntotal} pagination idiom as xce_push_data_req()/
* xce_reply_get() above: `pktlimit <= 0` sends the whole list in one page;
* a zero-length list still sends exactly one page with count == 0 (an empty
* page is a valid, complete exchange, not something to skip). The receiving
* side decodes each page with the generic xce_decode() -- these opcodes are
* their own dedicated Request body union arms (jobstatequerymsg/
* jobstatereportmsg, added by task 1.1's schema), not multiplexed through
* xce_data_t, so encode/decode of each page's payload is already handled by
* the generated encode_JobStateQuery()/encode_JobStateReport() (controller.capnp.c)
* that xce_encode()/xce_decode() call into.
*/
extern int xce_job_state_query_req(endpoint_t*, xce_peerinfo_t*,
xce_job_state_query_t*,
xce_check_fn_t, void*, int);
extern int xce_job_state_report_req(endpoint_t*, xce_peerinfo_t*,
xce_job_state_report_t*,
xce_check_fn_t, void*, int);
extern int xce_notify_req(endpoint_t*, xce_notify_data_t*);
extern int extract_data(dataset_t*, xce_data_t*);
extern int xce_update_req(endpoint_t*, xce_update_data_t*);

View file

@ -72,6 +72,11 @@ typedef struct {
char* clustername;
int n_confvars;
xce_config_t** confvars;
/* worker-rejoin-resync task 6.1 (Req 3.7): mirrors clustername above --
* pulled directly from c->myconf->cluster_config->worker_resync_window_ms
* (worker.c's worker_push_config()), not part of the generic confvars
* key/value list. */
int worker_resync_window_ms;
} xce_conf4worker_t;
typedef struct {
@ -153,7 +158,9 @@ typedef struct {
job_failinfo_t* dispatchfail;
xce_jobfinish_t* finishinfo;
uint64_t sched_start_job;
uint64_t sched_exit_job;
/* bug-1737: carries exit_code alongside jobid now -- see
* controller.capnp's schedExitJob field comment. */
xce_jobfinish_t* sched_exit_job;
uint64_t sched_fail_job;
xce_job_t* sched_update_job;
xce_resval_list_t* sched_update_resvals;
@ -279,6 +286,42 @@ typedef struct {
uint64_t responder_time_ms; /* @13: responder wall-clock at sample time (epoch ms) */
} xce_ctrlstatus_t;
/* ── Worker rejoin/resync job-state reconciliation (worker-rejoin-resync
* task 1.1, Req 2.1/2.2/3.5/3.7/5.3/8.1-8.3) ────────────────────────────
* Frame-only wire types for the controller<->worker job-state
* reconciliation exchange (OPCode_jobStateQuery/OPCode_jobStateReport) --
* same "no live C struct" convention as capnp_host_admin_state_t/
* replay_cluster_t above (see controller.capnp's comment at `struct
* JobStateEntry`). An enum-typed capnp field (status) maps to a plain
* `int` here, not a separate named C enum type -- mirrors the existing
* HostRole/JoinData.role and SchedAdminAction/sched_admin_req_t.action
* convention (the generated `enum JobStateStatus` in controller.capnp.h
* already supplies the JobStateStatus_running/finishedOk/finishedFail/
* unrecognized constants for callers that want them). This is schema-only:
* the C-side send/receive/encode/decode logic is later tasks (1.4 wire
* codec helpers; 6.1/6.2 controller wiring; 4.x worker wiring). */
typedef struct {
uint64_t jobid;
int status; /* JobStateStatus_running/finishedOk/finishedFail/unrecognized */
int32_t exit_code;
uint64_t finish_time_ms;
int queried;
} xce_job_state_entry_t;
typedef struct {
int n_total;
int pos;
int count;
uint64_t* jobids;
} xce_job_state_query_t;
typedef struct {
int n_total;
int pos;
int count;
xce_job_state_entry_t** entries;
} xce_job_state_report_t;
typedef struct {
uint64_t seq;
uint32_t opcode;
@ -341,6 +384,8 @@ typedef struct {
xce_fetchfile_t* fetchfilemsg;
sched_admin_req_t* schedadminmsg;
probe_config_tag_req_t* probeconfigtagrequestmsg;
xce_job_state_query_t* jobstatequerymsg;
xce_job_state_report_t* jobstatereportmsg;
} body;
} xce_request_t;

View file

@ -174,6 +174,7 @@ typedef struct {
#define XCE_WORKER_CLOSED (0x00100000)
#define XCE_WORKER_HIDDEN (0x00200000) /* Admin explicitly removed — invisible but not yet cleaned up */
#define XCE_WORKER_CLOSED_BY_ADMIN (0x00400000) /* Admin closed — host closed explicitly by admin */
#define XCE_WORKER_RECONCILING (0x00800000) /* worker-rejoin-resync: job-state reconciliation outstanding for this host (Req 5.1) */
#define IS_WORKER_UNAVAIL(status) (((status) & XCE_WORKER_UNAVAIL) != 0)
#define IS_WORKER_LOCKEDU(status) (((status) & XCE_WORKER_LOCKEDU) != 0)
@ -182,6 +183,7 @@ typedef struct {
#define IS_WORKER_LOCKED(status) \
(IS_WORKER_LOCKEDU(status) || IS_WORKER_LOCKEDW(status))
#define IS_WORKER_BUSY(status) (((status) & XCE_WORKER_BUSY) != 0)
#define IS_WORKER_RECONCILING(status) (((status) & XCE_WORKER_RECONCILING) != 0)
typedef struct {
char* hosttype;
@ -281,12 +283,43 @@ typedef struct {
double allocated;
} xce_res_account_t;
/* worker-rejoin-resync: per-worker job-state reconciliation round. One
* resync_state_t per worker_t (worker_t.resync below), absent (NULL) when
* no reconciliation is currently outstanding for that worker. Purely
* in-memory bookkeeping, not itself persisted -- see design.md's Data
* Models / State Management for the full rationale. */
typedef struct {
xce_worker_t host;
char hostkey[XCE_MAXKEY_LEN];
endpoint_t* ep;
int idx;
zarray_t usages;
uint64_t round_token; /* discards stale/superseded reports (Req 6.3) */
int n_queried_jobids;
uint64_t* queried_jobids; /* set semantics (membership-checked, not
* positionally iterated): job IDs queried this
* round; array+count, mirroring
* xce_job_state_query_t.jobids (typemap.h) */
uint64_t deadline; /* CLOCK_MONOTONIC ms deadline (Req 5.3); same
* representation as delaymgr_t.nextrun_ms
* (delay.h) */
} resync_state_t;
typedef struct {
xce_worker_t host;
char hostkey[XCE_MAXKEY_LEN];
endpoint_t* ep;
int idx;
zarray_t usages;
resync_state_t* resync; /* worker-rejoin-resync: NULL unless a job-state
* reconciliation round is currently outstanding
* for this worker */
/* worker-rejoin-resync task 6.2 (Req 8.1, 8.3): opaque (void*) rather
* than a typed pointer here because its real type, resync_report_
* accum_t (resync.h), embeds xce_job_state_entry_t -- defined in
* typemap.h, which itself includes THIS header (xce/types.h), so
* typemap.h cannot be included from here without a cycle. NULL unless
* an OPCode_jobStateReport is currently being reassembled across
* multiple pages for this worker; owned and cast back to resync_
* report_accum_t* exclusively by resync.c, under this worker's own
* RTTI lock, mirroring how `resync` above is the only other opaque-
* from-here, resync.c-owned worker_t field. */
void* report_accum;
} worker_t;
typedef struct {