feat(scheduler-dispatch-commit): add durable WAL_JOB_DISPATCH fact type (task 1.2)

New wal_entry_type WAL_JOB_DISPATCH=12, reusing wal_jobstate_t verbatim
(_pad=0, absolute-value semantics), wired into both apply_wal_entry()'s
and apply_committed_entry()'s existing job-state fall-through groups per
Requirement 2.4. Restart-replay test writes only WAL_JOB_SUBMIT+
WAL_JOB_DISPATCH to a real on-disk WAL and confirms job.state==RUN after
a fresh persist_recover(), independent of WAL_JOB_START.

Review found apply_wal_entry() has zero production call sites today
(persist_recover() and standby catch-up both route through
apply_committed_entry() post the 2026-07-10 ha_apply_single_entry
refactor) -- design.md's stale characterization corrected; the dual
case-label registration itself is unaffected and correct.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-08-23 07:12:50 +00:00
parent 5724d1e90a
commit 8eea74909c
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
7 changed files with 367 additions and 5 deletions

View file

@ -3458,6 +3458,7 @@ int apply_wal_entry(persist_t* p, uint32_t type, uint64_t lsn,
case WAL_JOB_CKPT:
case WAL_JOB_PREASON:
case WAL_JOB_RUSAGE:
case WAL_JOB_DISPATCH:
/* Payload: wal_jobstate_t {jobid, state} */
if (payload_len >= sizeof(wal_jobstate_t)) {
const wal_jobstate_t* js = (const wal_jobstate_t*)payload;
@ -3703,7 +3704,8 @@ int apply_committed_entry(persist_t* p, uint32_t wal_type, uint64_t lsn,
case WAL_JOB_RESUME:
case WAL_JOB_RUSAGE:
case WAL_JOB_CKPT:
case WAL_JOB_PREASON: {
case WAL_JOB_PREASON:
case WAL_JOB_DISPATCH: {
/* Every job-state fact type in this group shares one wire shape —
* wal_jobstate_t {jobid, state} — and therefore one guarded core.
* Their payloads are deterministic/state-independent (a START is
@ -3716,7 +3718,13 @@ int apply_committed_entry(persist_t* p, uint32_t wal_type, uint64_t lsn,
* finish}_transition() cores (their parameter structs are not
* present on this wire format) — start_time/finish_time/run_time
* are instead stamped directly below, keyed on wal_type, since
* apply_job_update_transition() only ever touches `state`. */
* apply_job_update_transition() only ever touches `state`.
* WAL_JOB_DISPATCH joins this group for the same reason: it is
* synchronously computed at dispatch-commit time under the job's
* own write lock, with no other in-flight fact for the same job
* (it is the first transition after PEND) — so it never needs
* WAL_JOB_FAIL/WAL_JOB_UPDATE's deferred, apply-time-recomputed
* handling. */
if (len < sizeof(wal_jobstate_t))
return 0; /* undersized payload — skip, mirrors apply_wal_entry */