merge: sync Rongsong's scheduler-framework into chatbot
Full merge of origin/scheduler-framework (scheduler-core restructure,
failover-adoption, WAL_JOB_DISPATCH, apiserver backend health/heartbeat,
build-time versioning, bug-1673/1676/1678/1679/1680).
Conflict resolution:
- scheduler: adopt Rongsong's scheduler-core architecture (driver loop,
queue project/picker fields, resource bug-1569 fix)
- worker/controller_connector.c: keep Rongsong's reconnect/rejoin
structure, PORT our bug-1509 resend_job_status onto the rejoin success
path (his reconnect didn't re-report job state)
- apiserver backend: take Rongsong's health/heartbeat (supersedes our
e505066 circuit breaker)
- group.c: keep our bug-1513 comment
- misc: take theirs for additive fixes (go_build versioning,
test_group.c, persist.c RUSAGE_UPDATE, FilterRows bug-1680)
NOTE: our scheduler fixes (bug-1494 family dispatch-fail requeue,
PEND-reason classification) are NOT in Rongsong's branch — needs
verification/porting onto scheduler-core (separate follow-up).
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
4e894d2184
395 changed files with 88517 additions and 5742 deletions
|
|
@ -6,6 +6,7 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "netio.h"
|
||||
#include "request.h"
|
||||
#include "xce/types.h"
|
||||
|
|
@ -25,7 +26,9 @@ typedef void* xcecli_ctx_t;
|
|||
*/
|
||||
typedef int (*xcecli_reconnect_cb)(xcecli_ctx_t ctx, void* arg);
|
||||
|
||||
#define N_MAX_MASTERS (3)
|
||||
/* N_MAX_MASTERS derives from config.h's MAX_HA_CANDIDATES so the two limits
|
||||
* can never silently diverge (Req 1 AC7, component-failover-adoption). */
|
||||
#define N_MAX_MASTERS MAX_HA_CANDIDATES
|
||||
|
||||
typedef struct {
|
||||
int nmasters;
|
||||
|
|
@ -134,4 +137,24 @@ extern int xcecli_jobinfo(xcecli_ctx_t, uint64_t, xce_job_t**);
|
|||
extern int xcecli_reply_fetchfile(xcecli_ctx_t, xce_request_t*, xce_fetchfile_reply_t*);
|
||||
extern int xcecli_fetchfile(xcecli_ctx_t, xce_fetchfile_t*, xce_fetchfile_reply_t**);
|
||||
|
||||
/*
|
||||
* xcecli_reply_schedadmin: send a SchedAdminReply back over `c`'s
|
||||
* connection in response to a previously-received OPCode_schedAdmin
|
||||
* request (scheduler-core spec, task 9.4, Req 5 AC 10). Mirrors
|
||||
* xcecli_reply_fetchfile() above exactly, with one difference: it takes
|
||||
* the original request's `seq` directly rather than an `xce_request_t*`,
|
||||
* since `recordStart`/`recordStop` defer this reply to the driver thread
|
||||
* (see driver.c's xce_sched_driver_translate_pending_events()), by which
|
||||
* point the original xce_request_t has already been freed by the
|
||||
* request-handling thread (xcectx.c's vtask_entry(), immediately after the
|
||||
* registered handler returns) -- seq is the only field of it the reply
|
||||
* needs (opcode is always OPCode_schedAdmin). `recordStatus` (answered
|
||||
* synchronously from the handler thread, real request still in scope)
|
||||
* uses this same function, simply passing `request->seq` directly.
|
||||
* Returns xce_reply()'s own return value; -EINVAL if `c` or `areply` is
|
||||
* NULL.
|
||||
*/
|
||||
extern int xcecli_reply_schedadmin(xcecli_ctx_t, uint64_t seq,
|
||||
sched_admin_reply_t* areply);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -120,6 +120,21 @@ typedef struct endpoint_t {
|
|||
* their lifetime directly. */
|
||||
_Atomic int refcnt;
|
||||
int heap_alloc; /* 1 iff allocated by endpoint_get (unref may free) */
|
||||
/* bug-1548: bumped once, unconditionally, by xce_ctx_reconnect() (xcectx.c)
|
||||
* on every successful transport reconnect, regardless of trigger path
|
||||
* (write-path XCECLI_FAILOVER_CALL retry or heartbeat XCECLI_FF_CALL
|
||||
* best-effort self-heal). Neither endpoint_init() nor endpoint_fini()
|
||||
* (netio.c) touch this field, so it survives reconnect cycles the same
|
||||
* way refcnt/heap_alloc already do. Exists so a consumer (e.g. apiserver's
|
||||
* heartbeat prober, client.h's xcecli_get_my_endpoint) can detect that a
|
||||
* reconnect happened even when it completed silently inside a single
|
||||
* fire-and-forget call and the call's own return code never surfaced a
|
||||
* failure — XCECLI_FF_CALL can rotate+recover before evaluating its EXPR,
|
||||
* so a failure->success transition on the caller's own return code is not
|
||||
* a reliable signal (component-failover-adoption Phase 6). Does not
|
||||
* change reconnect timing/ordering/semantics — a passive observation
|
||||
* point at the existing success path, same shape as on_fini above. */
|
||||
_Atomic uint64_t reconnect_gen;
|
||||
} endpoint_t;
|
||||
|
||||
/* bug-1298 family: msg_handler runs SYNCHRONOUSLY on the io_worker thread (not
|
||||
|
|
|
|||
|
|
@ -151,10 +151,18 @@ typedef enum {
|
|||
* the loaded snapshot's checkpoint_lsn (entries at/below it are skipped
|
||||
* before ever reaching dispatch — see replay_wal), so each one names a
|
||||
* job that must now exist in the live job table that did NOT exist
|
||||
* before this replay pass began. */
|
||||
* before this replay pass began.
|
||||
* expected_new_worker_count — bug-1427: the worker analog of
|
||||
* expected_new_job_count. The number of WAL_WORKER_CTRL/
|
||||
* HOOK_DETAIL_WORKER_JOIN entries replay dispatched in this pass that
|
||||
* genuinely created a new worker (worker_from_capnp() returns 1 for
|
||||
* that case, 0 for an UPDATE/DEAD applied to an already-existing
|
||||
* worker or -EEXIST for a duplicate JOIN — only the 1 case is tallied,
|
||||
* mirroring WAL_JOB_SUBMIT's apply_rc == 1 convention). */
|
||||
typedef struct {
|
||||
uint64_t expected_last_applied_lsn;
|
||||
uint64_t expected_new_job_count;
|
||||
uint64_t expected_new_worker_count;
|
||||
} apply_expectation_t;
|
||||
|
||||
/* ============================================================
|
||||
|
|
@ -528,6 +536,13 @@ struct persist_s {
|
|||
* must NOT advance and the failure is logged loudly. Compiled out of
|
||||
* production (xce_controller is built without -DXCE_TESTING). */
|
||||
atomic_int test_force_fsync_fail;
|
||||
/* Flusher attempt counter (bug-1563): incremented once per
|
||||
* flusher_flush_once() invocation, regardless of outcome. A sustained
|
||||
* fdatasync failure must not spin the async fsync_worker thread at full
|
||||
* CPU with zero backoff between retries -- this proves attempt rate
|
||||
* stays bounded to roughly the wal_sync_ms cadence even while durable_lsn
|
||||
* is stuck. Compiled out of production. */
|
||||
atomic_uint test_flusher_flush_attempt_count;
|
||||
/* fdatasync observability counter (ha-commit-durability task 2.3):
|
||||
* incremented at the standby-path inline sync in
|
||||
* persist_wal_write_replicated, BEFORE that function returns (and thus
|
||||
|
|
@ -915,24 +930,28 @@ bool persist_recovery_complete(persist_t* p);
|
|||
void persist_mark_recovery_pending(persist_t* p);
|
||||
|
||||
/* persist_verify_apply_correctness (raft-apply-decoupling task 5.1, Req
|
||||
* 8.1/8.2). Pure comparison — mirrors persist_recover_decision's separation
|
||||
* of decision from action: no logging, no fence side effects, no mutation of
|
||||
* `p` beyond the read of last_applied_lsn. Compares `exp` (derived by
|
||||
* scanning the committed log during replay — see apply_expectation_t) against
|
||||
* the actual materialized state: p's replay-derived apply cursor, and — when
|
||||
* p->cluster is non-NULL — the live job table's growth since
|
||||
* `jobmgr_count_before_replay` (the job count captured just before replay
|
||||
* began). Returns APPLY_CLEAN on a match. Returns APPLY_DIVERGENT when either
|
||||
* the apply cursor or the live job count disagrees with `exp`.
|
||||
* 8.1/8.2; worker half added bug-1427). Pure comparison — mirrors
|
||||
* persist_recover_decision's separation of decision from action: no
|
||||
* logging, no fence side effects, no mutation of `p` beyond the read of
|
||||
* last_applied_lsn. Compares `exp` (derived by scanning the committed log
|
||||
* during replay — see apply_expectation_t) against the actual materialized
|
||||
* state: p's replay-derived apply cursor, and — when p->cluster is
|
||||
* non-NULL — the live job table's growth since `jobmgr_count_before_replay`
|
||||
* and the live worker table's growth since `workermgr_count_before_replay`
|
||||
* (both counts captured just before replay began). Returns APPLY_CLEAN on a
|
||||
* match. Returns APPLY_DIVERGENT when the apply cursor, the live job count,
|
||||
* or the live worker count disagrees with `exp`.
|
||||
*
|
||||
* Safe to call with p->cluster == NULL (a bare unit-test persist_t, or any
|
||||
* caller with nothing to dispatch into): the job-count half of the check is
|
||||
* skipped — only the position check applies — mirroring
|
||||
* apply_committed_entry()'s own "nothing to dispatch into" convention. Safe
|
||||
* on NULL p or NULL exp (returns APPLY_CLEAN — nothing to verify). */
|
||||
* caller with nothing to dispatch into): the job-count and worker-count
|
||||
* halves of the check are both skipped — only the position check applies —
|
||||
* mirroring apply_committed_entry()'s own "nothing to dispatch into"
|
||||
* convention. Safe on NULL p or NULL exp (returns APPLY_CLEAN — nothing to
|
||||
* verify). */
|
||||
apply_verdict_t persist_verify_apply_correctness(
|
||||
persist_t* p, const apply_expectation_t* exp,
|
||||
uint64_t jobmgr_count_before_replay);
|
||||
uint64_t jobmgr_count_before_replay,
|
||||
uint64_t workermgr_count_before_replay);
|
||||
|
||||
/* Accessor for the sticky verdict computed by the last persist_recover()
|
||||
* (task 5.1). APPLY_CLEAN before recovery has run (fresh store default,
|
||||
|
|
@ -1389,6 +1408,38 @@ int persist_submit_job_fact(persist_t* p, uint32_t wal_type, uint64_t jobid,
|
|||
* case, but is still written for forward-compatibility/diagnostics). */
|
||||
int persist_submit_job_fact_ex(persist_t* p, uint32_t wal_type,
|
||||
uint64_t jobid, int state, uint32_t pad);
|
||||
|
||||
/* bug-1678: WAL_JOB_DISPATCH's own sibling of persist_submit_job_fact(),
|
||||
* additionally encoding the scheduler's resource allocation into the fact's
|
||||
* payload (header {jobid, state} + trailing Cap'n Proto JobAlloc bytes) so a
|
||||
* restart/catch-up replay can reconstruct job.allocs instead of leaving it
|
||||
* permanently NULL -- the WAL_JOB_DISPATCH-only reason persist_submit_job_fact
|
||||
* itself was never sufficient for this one fact type. `alloc` must be
|
||||
* non-NULL (the real call site, dispatch_a_job_(), always has one by
|
||||
* construction). Returns the local-append rc, same contract as
|
||||
* persist_submit_job_fact(). */
|
||||
int persist_submit_job_dispatch_fact(persist_t* p, uint64_t jobid, int state,
|
||||
const xce_job_alloc_t* alloc);
|
||||
|
||||
/* bug-1679: fire XCE_CONTROLLER_HOOK_JOB's HOOK_DETAIL_JOB_UPDATE AFTER_EVENT
|
||||
* for a job whose durable WAL fact the caller has ALREADY appended itself
|
||||
* (e.g. dispatch_a_job_()'s own persist_submit_job_dispatch_fact() call,
|
||||
* made just before this). Without this call, dispatch_a_job_()'s live,
|
||||
* synchronous state/allocs mutation is never notified: it fires no hooks at
|
||||
* all, and apply_committed_entry()'s later apply of that same WAL_JOB_DISPATCH
|
||||
* entry sees the state already matches (JOB_TRANSITION_NOOP_DUPLICATE) and
|
||||
* skips its own would-be notify -- the job is stuck at PEND from the
|
||||
* apiserver's perspective forever, restart or not.
|
||||
*
|
||||
* Internally guards with the SAME t_apply_seam_notifying flag
|
||||
* apply_committed_entry()'s own shared WAL_JOB_START/.../DISPATCH case uses
|
||||
* around its identical hook fire (mirrors that exact bracketing) -- without
|
||||
* it, persist_job_hook_fun (also subscribed to this hook) would redundantly
|
||||
* re-append this same transition as a second, duplicate WAL_JOB_UPDATE
|
||||
* entry. The flag stays file-local (static) to persist.c on purpose; this
|
||||
* function is job.c's only sanctioned way to reach it. */
|
||||
void persist_notify_job_updated_already_persisted(cluster_t* c, job_t* j);
|
||||
|
||||
int persist_worker_hook_fun(int type, int situation, int detail,
|
||||
void* cls, va_list ap);
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,15 @@ extern int xce_set_pend_reason_req(endpoint_t*, xce_preason_set_t*);
|
|||
extern int xce_clear_reason_req(endpoint_t*, xce_clear_reason_t*);
|
||||
extern int xce_fetchfile_req(endpoint_t*, xce_fetchfile_t*);
|
||||
|
||||
/*
|
||||
* xce_schedadmin_req: send OPCode_schedAdmin (SchedAdminRequest) to `ep` --
|
||||
* the async send half of task 10.1's controller->scheduler forward. Mirrors
|
||||
* xce_fetchfile_req above exactly. The caller (myreq_handler, cluster.c)
|
||||
* blocks separately for the matching SchedAdminReply; this function only
|
||||
* enqueues the request, it does not wait.
|
||||
*/
|
||||
extern int xce_schedadmin_req(endpoint_t*, sched_admin_req_t*);
|
||||
|
||||
/*
|
||||
* xce_replconfigpush_req_raw: send OP_replConfigPush carrying raw byte arrays.
|
||||
* Bypasses the encode_ReplConfigPushRequest_ptr path (which maps sha256/bytes
|
||||
|
|
|
|||
|
|
@ -197,6 +197,14 @@ typedef struct {
|
|||
uint64_t peer_last_log_lsn;
|
||||
} request_vote_reply_t;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
} probe_config_tag_req_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t config_tag;
|
||||
} probe_config_tag_reply_t;
|
||||
|
||||
/* Heartbeat reply state report — carries standby's applied_lsn and config_tag,
|
||||
* plus its raft term so the primary can discover a higher term it lost track
|
||||
* of (bug-1421 term-discovery: an AE reject response never reaches the raft
|
||||
|
|
@ -216,6 +224,31 @@ typedef struct {
|
|||
int success;
|
||||
} wal_ack_reply_t;
|
||||
|
||||
/* Scheduler admin opcode request/reply (scheduler-core task 7.4, Req 5 AC 10).
|
||||
* OPCode_schedAdmin: start/stop/query a scheduler-side `.replay` recording
|
||||
* session. No field-level $C.mapname on either capnp struct (per the exact
|
||||
* verbatim schema spec), so -- same convention as xce_ctrlstatus_t below --
|
||||
* the generated accessors and this mapped struct's field names match the
|
||||
* capnp schema's camelCase names exactly (no snake_case conversion). action
|
||||
* mirrors the existing HostRole/JoinData.role convention: an enum-typed
|
||||
* capnp field maps to a plain `int` here, not a separate named C enum type
|
||||
* (the generated `enum SchedAdminAction` in controller.capnp.h already
|
||||
* supplies the SchedAdminAction_recordStart/recordStop/recordStatus
|
||||
* constants for callers that want them). */
|
||||
typedef struct {
|
||||
int action;
|
||||
char *recordFile;
|
||||
} sched_admin_req_t;
|
||||
|
||||
typedef struct {
|
||||
int ok;
|
||||
char *error;
|
||||
int recording;
|
||||
char *file;
|
||||
uint64_t eventCount;
|
||||
uint64_t startedAt;
|
||||
} sched_admin_reply_t;
|
||||
|
||||
/* ctrlstatus reply payload (controller-status spec): one configured
|
||||
* controller (CtrlPeer) and the lean identity/health snapshot (CtrlStatus).
|
||||
* The peers list maps via $C.mapname("peers.list")/$C.maplistcount("peers.count")
|
||||
|
|
@ -265,6 +298,8 @@ typedef struct {
|
|||
wal_ack_reply_t* walackreplymsg;
|
||||
xce_ctrlstatus_t* ctrlstatus;
|
||||
xce_fetchfile_reply_t* fetchfilereply;
|
||||
sched_admin_reply_t* schedadminreplymsg;
|
||||
probe_config_tag_reply_t* probeconfigtagreplymsg;
|
||||
} data;
|
||||
/* Req 7: standby redirect fields — populated when status == XCE_ERR_STANDBY */
|
||||
char *redirect_host;
|
||||
|
|
@ -304,6 +339,8 @@ typedef struct {
|
|||
wal_catchup_entry_t* walcatchupentrymsg;
|
||||
snapshot_transfer_t* snapshottransfermsg;
|
||||
xce_fetchfile_t* fetchfilemsg;
|
||||
sched_admin_req_t* schedadminmsg;
|
||||
probe_config_tag_req_t* probeconfigtagrequestmsg;
|
||||
} body;
|
||||
} xce_request_t;
|
||||
|
||||
|
|
@ -314,14 +351,6 @@ typedef struct {
|
|||
* request_vote_req_t and request_vote_reply_t are defined above
|
||||
* xce_reply_t so they can be used in the xce_request_t body union. */
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
} probe_config_tag_req_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t config_tag;
|
||||
} probe_config_tag_reply_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t term;
|
||||
uint64_t lsn;
|
||||
|
|
@ -358,8 +387,14 @@ struct snapshot_transfer_s {
|
|||
struct wal_catchup_entry {
|
||||
uint64_t lsn;
|
||||
uint8_t entry_type;
|
||||
xce_data_t *payload; /* legacy decoded field — broken for
|
||||
* raw bytes; kept NULL, do not use */
|
||||
/* bug-1505 fix: List(UInt8) now, not Data (see controller.capnp's
|
||||
* WalCatchupEntry.payload comment for the full root cause) — a real
|
||||
* uint8_t* + wire_payload_len pair via the generic codec, same shape
|
||||
* bytes_read/content already establish elsewhere. Still legacy/unused
|
||||
* by production (wal_entry_to_wire() in request.c deliberately never
|
||||
* sets it — see that comment); this genuinely round-trips now instead
|
||||
* of being a documented-broken placeholder, but nothing reads it. */
|
||||
uint8_t *payload;
|
||||
int n_entries; /* batch: number of sub-entries */
|
||||
struct wal_catchup_entry **entries; /* batch: array of sub-entry pointers */
|
||||
/* Raw WAL entry payload bytes — same pattern as repl_config_push_req_t.
|
||||
|
|
@ -394,15 +429,23 @@ struct wal_catchup_entry {
|
|||
* send; used on decode to slice this entry's bytes out of the TOP-LEVEL
|
||||
* container's batch_payloads blob (below). */
|
||||
uint32_t wire_payload_len;
|
||||
/* bug-847: generic decoded placeholder for wire field batchPayloads @10 —
|
||||
* same broken-for-raw-bytes limitation as `payload` above; kept NULL, do
|
||||
* not use. Only meaningful on the TOP-LEVEL container (never on a nested
|
||||
* sub-entry). The real bytes are hand-extracted by xce_decode() into
|
||||
* raw_batch_payloads/raw_batch_payloads_len below, then sliced per
|
||||
/* bug-1505 fix: List(UInt8) now, not Data (controller.capnp's
|
||||
* WalCatchupEntry.batchPayloads comment has the full root cause —
|
||||
* same class as `payload` above and SnapshotData.internalState,
|
||||
* bug-1504). Generic decoded field for wire field batchPayloads @10;
|
||||
* genuinely round-trips now via the generic codec, but still unused by
|
||||
* production — the real bytes are hand-extracted by xce_decode() into
|
||||
* raw_batch_payloads/raw_batch_payloads_len below (a separate,
|
||||
* already-correct code path this fix does not touch), then sliced per
|
||||
* sub-entry using each entry's wire_payload_len into that entry's OWN
|
||||
* raw_payload/raw_payload_len (so every consumer keeps reading
|
||||
* raw_payload exactly as before — this is purely a wire-transport fix). */
|
||||
xce_data_t *batch_payloads;
|
||||
* raw_payload exactly as before). Only meaningful on the TOP-LEVEL
|
||||
* container (never on a nested sub-entry). batch_payloads_wire_len is
|
||||
* its own maplistcount companion (wire field batchPayloadsLen @11) —
|
||||
* distinct from raw_batch_payloads_len below, which belongs to the
|
||||
* separate hand-rolled extraction path. */
|
||||
uint8_t *batch_payloads;
|
||||
uint32_t batch_payloads_wire_len;
|
||||
uint8_t *raw_batch_payloads;
|
||||
size_t raw_batch_payloads_len;
|
||||
};
|
||||
|
|
@ -541,4 +584,186 @@ struct ctrl_hello_reply_s {
|
|||
uint64_t config_tag; /* primary's current config tag */
|
||||
};
|
||||
|
||||
/* ── Replay stream envelope frames (scheduler-core Group 7 task 7.1,
|
||||
* Req 5 AC 20) ──────────────────────────────────────────────────────────
|
||||
* Frame-only wire types for the `.replay` stream envelope -- same "no live
|
||||
* C struct" convention as capnp_snap_hdr_t/capnp_persist_cfg_t/
|
||||
* capnp_host_admin_state_t above (see controller.capnp's comment at
|
||||
* `struct ReplayCluster` for the full Group 7 task split). The C-side
|
||||
* reader/writer that actually populates/consumes these (recording,
|
||||
* `.replay` file I/O) is task 9+, out of this schema-only task's scope. */
|
||||
typedef struct {
|
||||
char *name;
|
||||
char **controllers;
|
||||
int n_controllers;
|
||||
char *recording_host;
|
||||
} replay_cluster_t;
|
||||
|
||||
typedef struct {
|
||||
char *recorder_version;
|
||||
/* configHash is a Data field -- the generated encode_/decode_ codec is
|
||||
* a documented no-op for Data fields in this codebase's capnpc-c setup
|
||||
* (same limitation as e.g. upgrade_config_req_s.sha256/bytes above);
|
||||
* real byte extraction is task 9+ hand-written code, same raw_sha256-
|
||||
* style pattern used throughout this file. This pointer/len pair exists
|
||||
* only so the mapname target type is well-formed; nothing here reads it
|
||||
* yet. */
|
||||
uint8_t *config_hash;
|
||||
size_t config_hash_len;
|
||||
replay_cluster_t *cluster;
|
||||
uint64_t recording_start_ts;
|
||||
char *recording_start_wall;
|
||||
char *sched_policy;
|
||||
} replay_header_t;
|
||||
|
||||
/* task 7.3 (Req 5 AC 21): SchedUser/SchedProject mirror controller.capnp's
|
||||
* SchedUser/SchedProject structs. Field lists match xce_sched_user_t/
|
||||
* xce_sched_project_t (src/scheduler/include/sched/types.h) exactly -- name
|
||||
* only, name+priority respectively -- but these are FRESH, wire-only C
|
||||
* types under different names, NOT aliases of the scheduler-internal ones.
|
||||
* See controller.capnp's comment at `struct SchedUser` for the full
|
||||
* rationale: sched/types.h is scheduler-private and already `#include`s
|
||||
* this file, so a duplicate `xce_sched_user_t`/`xce_sched_project_t`
|
||||
* typedef here would break every scheduler .c file that includes both
|
||||
* headers (confirmed: core_events.c, schedobj.c already do). */
|
||||
typedef struct {
|
||||
char *name;
|
||||
} sched_snapshot_user_t;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
int32_t priority;
|
||||
} sched_snapshot_project_t;
|
||||
|
||||
/* task 7.3 (Req 5 AC 21): the full snapshot struct, replacing the task 7.1
|
||||
* placeholder wholesale. Every domain-object pointer reuses a pre-existing
|
||||
* native type (already used natively throughout the scheduler/controller)
|
||||
* except users/projects (sched_snapshot_user_t/sched_snapshot_project_t
|
||||
* above) and the two scheduler-internal-only opaque fields. internal_version
|
||||
* is a format tag for internal_state so an incompatible future internal
|
||||
* layout is rejected rather than misinterpreted (this task's Observable).
|
||||
*
|
||||
* internal_state/internal_state_len map controller.capnp's internalState,
|
||||
* a List(UInt8) (task 12.1 review finding, fixed -- was Data, whose
|
||||
* generated codec is a silent no-op in this codebase's capnpc-c setup and
|
||||
* left a genuinely uninitialized capn_ptr on the wire, a real reproducible
|
||||
* crash; see controller.capnp's SnapshotData comment for the full story).
|
||||
* List(UInt8)'s generated encode_/decode_ actually runs (proven precedent:
|
||||
* xce_fetchfile_reply_t.content/bytes_read below), so internal_state_len
|
||||
* must be uint32_t to match internalStateLen's UInt32 wire type -- same
|
||||
* maplistcount convention as content/bytes_read. */
|
||||
typedef struct {
|
||||
xce_conf4sched_t *config;
|
||||
xce_jobs_data_t *jobs;
|
||||
xce_queue_t **queues;
|
||||
int n_queues;
|
||||
xce_workers_data_t *workers;
|
||||
xce_resource_data_t *resources;
|
||||
xce_resval_list_t *resvals;
|
||||
sched_snapshot_user_t **users;
|
||||
int n_users;
|
||||
sched_snapshot_project_t **projects;
|
||||
int n_projects;
|
||||
uint64_t prng_seed;
|
||||
uint8_t *internal_state;
|
||||
uint32_t internal_state_len;
|
||||
uint32_t internal_version;
|
||||
} sched_snapshot_data_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t ts;
|
||||
sched_snapshot_data_t *data;
|
||||
} replay_snapshot_entry_t;
|
||||
|
||||
/* task 7.2 (Req 5 AC 20): the two genuinely new event payload types -- every
|
||||
* other replay_event_t.body variant above reuses a pre-existing wire type
|
||||
* (see controller.capnp's comment at `struct JobStateChangeEvent`). */
|
||||
typedef struct {
|
||||
uint64_t jobid;
|
||||
uint32_t old_state;
|
||||
uint32_t new_state;
|
||||
int32_t exit_code;
|
||||
} job_state_change_event_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t jobid;
|
||||
/* changedFields is a UInt32 bitmask (JOB_F_PRIORITY, JOB_F_QUEUE,
|
||||
* JOB_F_RESREQ per design.md) -- no JOB_F_* constants exist anywhere in
|
||||
* the codebase yet (task 5.1's grep); concrete bit assignments are
|
||||
* task 9+ event-translation/recording code's job, not this schema-only
|
||||
* task's. */
|
||||
uint32_t changed_fields;
|
||||
xce_job_t *job; /* full updated record -- reuses xce_job_t (Job in
|
||||
* controller.capnp) */
|
||||
} job_update_event_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t ts;
|
||||
int body_which;
|
||||
union {
|
||||
xce_conf4sched_t *config_push;
|
||||
xce_jobs_data_t *jobs_push;
|
||||
xce_workers_data_t *workers_push;
|
||||
xce_resource_data_t *resources_push;
|
||||
xce_resval_list_t *resvals_full;
|
||||
xce_resval_list_t *resvals_delta;
|
||||
xce_queue_t *queue_update;
|
||||
xce_job_rusages_t *job_rusage;
|
||||
/* endSession: Void, no storage */
|
||||
job_state_change_event_t *job_state_change;
|
||||
job_update_event_t *job_update;
|
||||
} body;
|
||||
} replay_event_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t duration_ns;
|
||||
uint32_t n_decisions;
|
||||
uint32_t n_dispatched;
|
||||
uint32_t n_pended;
|
||||
} session_summary_t;
|
||||
|
||||
/* task 7.3 (Req 5 AC 5, AC 20): the three decision payload types --
|
||||
* dispatch/pend reuse existing native types (xce_job_alloc_t,
|
||||
* xce_job_preason_t); preempt_victim_t/sched_preempt_decision_t are fresh,
|
||||
* wire-only types, deliberately distinct from decision.h's core-internal
|
||||
* xce_preempt_victim_t/xce_preempt_t (task 4.4's in-process live-decision
|
||||
* representation) -- see controller.capnp's matching comment. */
|
||||
typedef struct {
|
||||
xce_job_alloc_t *alloc;
|
||||
} sched_dispatch_decision_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t jobid;
|
||||
xce_job_preason_t *reason;
|
||||
} sched_pend_decision_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t jobid;
|
||||
char *workerid;
|
||||
} preempt_victim_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t jobid;
|
||||
preempt_victim_t **victims;
|
||||
int n_victims;
|
||||
xce_job_preason_t *reason;
|
||||
} sched_preempt_decision_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t ts;
|
||||
int body_which;
|
||||
union {
|
||||
sched_dispatch_decision_t *dispatch;
|
||||
sched_pend_decision_t *pend;
|
||||
sched_preempt_decision_t *preempt;
|
||||
} body;
|
||||
} replay_decision_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t begin_round;
|
||||
replay_decision_t **decisions;
|
||||
int n_decisions;
|
||||
session_summary_t *summary;
|
||||
} replay_session_t;
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -61,6 +61,55 @@
|
|||
|
||||
#define MK_VERSION(ver, commit) ver commit
|
||||
|
||||
/* SUPERSEDED for version-string composition (build-time-versioning task
|
||||
* 3.1). MK_VERSION/STR (immediately above) cannot express an
|
||||
* empty-value -> "unknown" fallback: an unset commit_id/build_time meson
|
||||
* option makes STR(x) expand to an empty string literal ("", not a missing
|
||||
* token), and a preprocessor macro has no way to branch on "is this string
|
||||
* empty" at expansion time. xce_version_field()/xce_compose_version()
|
||||
* (extern decls below, implementation + doc comments in utils.c) are the
|
||||
* runtime replacement — they DO perform that substitution, and are what
|
||||
* compose the version string everywhere it now matters. Use those, not
|
||||
* MK_VERSION, for any new version-string composition.
|
||||
*
|
||||
* MK_VERSION/STR are kept in place here (not deleted) only because task 3.2
|
||||
* — which will migrate controller/main.c's and scheduler/src/main.c's
|
||||
* argp_program_version construction to xce_compose_version() — has not
|
||||
* landed yet. Grep finding (2026-08-20, `grep -rn 'STR(\|MK_VERSION(' src/`,
|
||||
* excluding NULLSTR/comment/definition matches): STR() has ZERO call sites
|
||||
* anywhere in this codebase outside this exact MK_VERSION-based
|
||||
* argp_program_version pattern — controller/main.c:23,
|
||||
* scheduler/src/main.c:26, AND controller/testsuite/test.c:662 (the
|
||||
* xce_test REPL tool). That third caller is NOT listed in task 3.2's
|
||||
* boundary (`controller/main.c, scheduler/main.c` only, per tasks.md) —
|
||||
* worth confirming whether it's in scope when 3.2 lands, or it will keep
|
||||
* printing a commit-id-only, build-time-less version string after the
|
||||
* other two are migrated. STR() itself is a generic single-argument
|
||||
* stringification macro (not inherently version-specific), but per this
|
||||
* grep it currently has no unrelated use, so it is not being kept around
|
||||
* for any other live caller — only in service of the not-yet-migrated
|
||||
* MK_VERSION call sites above. Once task 3.2 (and any update to
|
||||
* controller/testsuite/test.c) lands and no caller of MK_VERSION remains,
|
||||
* both MK_VERSION and this STR usage can be deleted outright — not done
|
||||
* here, out of this task's boundary.
|
||||
*
|
||||
* NOTE FOR TASK 3.2 (or whoever migrates argp_program_version):
|
||||
* xce_compose_version() is a REGULAR RUNTIME FUNCTION, not a constant
|
||||
* expression — unlike MK_VERSION's pure string-literal concatenation, it
|
||||
* CANNOT initialize a file-scope variable. `const char* argp_program_version
|
||||
* = xce_compose_version(...)` at file scope will not compile (C file-scope
|
||||
* initializers must be constant expressions). controller/main.c:22-23 and
|
||||
* scheduler/src/main.c:25-26 currently declare argp_program_version exactly
|
||||
* that way — a file-scope const initialized at compile time — so this is
|
||||
* not a one-line macro-call swap. Instead: compute the version string at
|
||||
* RUNTIME into a `static char buf[...]` (module scope or local to main()),
|
||||
* via xce_compose_version(), early in main() and BEFORE argp_parse() runs
|
||||
* (argp reads argp_program_version by pointer when --version/-V is
|
||||
* processed) — then assign argp_program_version (no longer const-qualified
|
||||
* at its declaration; assigned at runtime instead of initialized) to point
|
||||
* at that buffer. See xce_compose_version()'s doc comment in utils.c for
|
||||
* the buffer-sizing contract. */
|
||||
|
||||
#ifndef FREEUP
|
||||
#define FREEUP(p) \
|
||||
if ((p) != NULL) { \
|
||||
|
|
@ -114,6 +163,28 @@ extern int mylog_init(char*, char*, int, int);
|
|||
extern void mylog_fini(void);
|
||||
extern int mylog_setlevel(char*);
|
||||
extern char* verbose_to_loglevel(int);
|
||||
/* See the SUPERSEDED/NOTE comment above MK_VERSION for what these replace
|
||||
* and why, and the constraint they impose on any file-scope
|
||||
* argp_program_version-style global (xce_compose_version() is not a
|
||||
* constant expression). */
|
||||
extern const char* xce_version_field(const char*);
|
||||
extern int xce_compose_version(char*, size_t, const char*, const char*,
|
||||
const char*);
|
||||
/* dirty-content-fingerprint task 3.1: --version-only sibling of
|
||||
* xce_compose_version() above. Same composition (prefix + commit +
|
||||
* " built " + build_time), plus one more optional field: when
|
||||
* dirty_fingerprint is non-empty, a trailing "-dirty" on commit is
|
||||
* stripped and "-<dirty_fingerprint>" is appended in its place, producing
|
||||
* "<commit>-<dirty_fingerprint>". When dirty_fingerprint is NULL or empty,
|
||||
* this function delegates straight to xce_compose_version() and produces
|
||||
* byte-identical output -- commit_id's own computation and its existing
|
||||
* -dirty suffix are never touched by this function. The startup-log
|
||||
* composition call sites (controller/scheduler/worker main.c) must keep
|
||||
* calling xce_compose_version() directly, never this function -- this is
|
||||
* what keeps the startup log's format unchanged per Requirement 3.2 of the
|
||||
* dirty-content-fingerprint spec. */
|
||||
extern int xce_compose_version_ex(char*, size_t, const char*, const char*,
|
||||
const char*, const char*);
|
||||
extern void set_error(char*, char*, int, int);
|
||||
extern ectx_t* get_ectx(void);
|
||||
extern int xce_vprintf(const char*, va_list);
|
||||
|
|
|
|||
|
|
@ -114,6 +114,15 @@ typedef enum {
|
|||
* the whole array (base spec + index range),
|
||||
* distinct from WAL_JOB_SUBMIT's single-job
|
||||
* format, which stays unchanged. */
|
||||
WAL_JOB_DISPATCH = 12, /* scheduler-dispatch-commit: durable record of a
|
||||
* job's synchronous dispatch-time transition to
|
||||
* running, distinct from (and independent of)
|
||||
* WAL_JOB_START's later worker-confirmed-start
|
||||
* fact. Payload is wal_jobstate_t{jobid, state,
|
||||
* _pad=0} -- always the legacy absolute-value
|
||||
* form (no racing same-job fact to defer against,
|
||||
* since this is the first transition after a job
|
||||
* leaves PEND). */
|
||||
WAL_QUEUE_CTRL = 20,
|
||||
WAL_WORKER_CTRL = 30,
|
||||
WAL_NOOP = 40, /* Internal: empty payload, replay no-op; appended only at promotion */
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#define XCE_CLUSTER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "client.h" /* xce_client_config_t, N_MAX_MASTERS */
|
||||
#include "config.h" /* MAX_HA_CANDIDATES, XCE_MAXHOSTNAME_LEN */
|
||||
|
||||
/**
|
||||
|
|
@ -28,7 +29,6 @@ typedef struct {
|
|||
* (encryption without client-side peer certificate verification).
|
||||
*
|
||||
* candidates_count must be in [1, MAX_HA_CANDIDATES].
|
||||
* A WARN is logged at startup if candidates_count > 3.
|
||||
*/
|
||||
typedef struct {
|
||||
xce_candidate_t candidates[MAX_HA_CANDIDATES];
|
||||
|
|
@ -79,6 +79,24 @@ typedef struct {
|
|||
*/
|
||||
int xce_cluster_config_validate(const xce_cluster_config_t* cfg);
|
||||
|
||||
/**
|
||||
* xce_cluster_config_to_client_config() — convert a validated candidate list
|
||||
* into an xce_client_config_t's master fields (nmasters/masters/master_ports).
|
||||
*
|
||||
* Copies up to N_MAX_MASTERS candidates from cfg->candidates[] in order.
|
||||
* If cfg->candidates_count > N_MAX_MASTERS, copies the first N_MAX_MASTERS and
|
||||
* logs a WARN identifying the truncated count (mirrors xce_test's existing
|
||||
* clamp behavior).
|
||||
*
|
||||
* Does NOT set out->from / out->key — caller sets those separately.
|
||||
* Does NOT validate cfg — caller must have already called
|
||||
* xce_cluster_config_validate() successfully.
|
||||
*
|
||||
* Returns 0 on success, -EINVAL if cfg or out is NULL or cfg->candidates_count < 1.
|
||||
*/
|
||||
int xce_cluster_config_to_client_config(const xce_cluster_config_t* cfg,
|
||||
xce_client_config_t* out);
|
||||
|
||||
/**
|
||||
* xce_connect_cluster() — connect to the cluster via candidate list.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@ typedef struct {
|
|||
char* user_group;
|
||||
char* job_description;
|
||||
char* resreq;
|
||||
char* from_host;
|
||||
} job_submit_data_t;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -487,6 +488,15 @@ typedef struct {
|
|||
* serialized (wire type xce_job_t above is untouched). 0 = unknown /
|
||||
* pre-feature = always exportable (see job_submit_committed, cluster.h). */
|
||||
uint64_t submit_lsn;
|
||||
/* scheduler-dispatch-commit (1.3): consecutive retryable dispatch-failure
|
||||
* count for this job, with no intervening successful dispatch. In-memory
|
||||
* only, following submit_lsn's exact precedent above — never serialized
|
||||
* (wire type xce_job_t above is untouched), reset to 0 by a successful
|
||||
* dispatch. Resetting to 0 after a controller failover is an acceptable,
|
||||
* safe degradation (more retries allowed, never fewer, never a
|
||||
* misrouted terminal/retry decision) — see Controller Dispatch-Failure
|
||||
* Classification, design.md. */
|
||||
uint32_t dispatch_fail_count;
|
||||
} job_t;
|
||||
|
||||
typedef struct {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue