feat(raft-apply-decoupling): ordered live apply pump activates apply_committed_entry (task 6.1)
Build persist_apply_pump_run()/persist_apply_pump_kick() -- the ordered walk from last_applied_lsn+1 through committed_lsn, applying every entry via apply_committed_entry() regardless of commit mode. This is the first live (non-replay) consumer of that dispatch function. Kick is wired into commit_resolve_pending() (the single choke point all 6 internal committed_lsn-advancing paths funnel through, discovered in task 1.2), dispatched via vtask_enq_classified(VTASK_LIVE_BLOCK) with a CAS-coalesced fire-and-forget trigger. Fixes the two hazards flagged since task 4.1 as required before going live: 1. Double-append: a thread-local call-provenance guard (t_apply_seam_notifying) prevents persist_job_hook_fun/ persist_queue_hook_fun/persist_worker_hook_fun from re-appending entries the apply pump itself just applied, while preserving job_ctrl's legitimate live command-path appends (a naive detail-based filter would have broken SIGCONT/SIGSTOP). Also closes an undocumented instance of the same hazard in the queue/worker hook functions. 2. Merge-staleness: record_job_fail_fact/postdone's fact recording previously baked a receipt-time-computed final job state into the WAL entry, which goes stale under deferred apply -- a FINISH followed shortly by an ordinary post-exec FAIL could silently clobber a correct DONE completion with a stale RUN|EXIT|PERR, no guard catching it. Fixed by repurposing wal_jobstate_t's unused _pad field to carry just enough data (FAIL's stage) to recompute against the job's actual current state at apply time via the real apply_job_fail_transition()/new apply_job_postdone_transition() guard cores, instead of overwriting with a stale absolute value. Backward compatible: _pad==0 (all prior data) routes to the same legacy overwrite behavior. Also fixes bug-1409 (unbounded busy-loop in apply_pump_drain when committed_lsn advances without a corresponding on-disk entry) and a genuine test-flakiness hazard (a second, independent apply-pump kicker from the fsync_worker thread that existing tests didn't account for). Two pre-existing tests in test_commit_barrier_reply.c now fail as expected -- their own assertions state "no job may be constructed until task 6's apply pump exists," now correctly invalidated by this task landing; updating them is task 7.3's explicit job.
This commit is contained in:
parent
d3da3ca8bd
commit
2fa955b92e
11 changed files with 1687 additions and 135 deletions
|
|
@ -1860,9 +1860,14 @@ static void test_update_op_rusage_receipt_reply_facts_retained(void)
|
|||
* entry (payload "p", 1 byte) and any non-jobstate entries are skipped by
|
||||
* the payload-size check. Single segment is guaranteed here: rotation
|
||||
* needs 64 MB (WAL_SEGMENT_SIZE_BYTES). Returns the match count;
|
||||
* *state_out (optional) receives the LAST matching entry's state. */
|
||||
static int cbrp_count_jobstate_entries(persist_t *p, uint32_t wal_type,
|
||||
uint64_t jobid, uint32_t *state_out)
|
||||
* state_out/pad_out (both optional pointers) receive the LAST matching
|
||||
* entry's state/_pad. raft-apply-decoupling task 6.1 round 2: WAL_JOB_FAIL and a
|
||||
* postdone WAL_JOB_UPDATE now carry their real information in `_pad` (see
|
||||
* wal_types.h's doc comment on wal_jobstate_t._pad) rather than a
|
||||
* precomputed `state` — pad_out lets a test observe that. */
|
||||
static int cbrp_count_jobstate_entries_ex(persist_t *p, uint32_t wal_type,
|
||||
uint64_t jobid, uint32_t *state_out,
|
||||
uint32_t *pad_out)
|
||||
{
|
||||
char seg_path[sizeof(p->seg_path)];
|
||||
uint64_t seg_end, off = 0;
|
||||
|
|
@ -1895,6 +1900,8 @@ static int cbrp_count_jobstate_entries(persist_t *p, uint32_t wal_type,
|
|||
count++;
|
||||
if (state_out != NULL)
|
||||
*state_out = js.state;
|
||||
if (pad_out != NULL)
|
||||
*pad_out = js._pad;
|
||||
}
|
||||
}
|
||||
off += sizeof(hdr) + hdr.payload_len;
|
||||
|
|
@ -1903,6 +1910,14 @@ static int cbrp_count_jobstate_entries(persist_t *p, uint32_t wal_type,
|
|||
return count;
|
||||
}
|
||||
|
||||
/* Thin wrapper preserving every pre-existing caller's signature/contract
|
||||
* unchanged (pad = 0, "legacy absolute state" — see wal_types.h). */
|
||||
static int cbrp_count_jobstate_entries(persist_t *p, uint32_t wal_type,
|
||||
uint64_t jobid, uint32_t *state_out)
|
||||
{
|
||||
return cbrp_count_jobstate_entries_ex(p, wal_type, jobid, state_out, NULL);
|
||||
}
|
||||
|
||||
/* Drive one fact call: it must succeed (rc 0 — in particular NEVER
|
||||
* -ETIMEDOUT) and return latency-bounded. sync_timeout_ms is 5000 in this
|
||||
* test, so a fact path that wrongly ran a WAIT barrier with ACKs
|
||||
|
|
@ -2372,24 +2387,32 @@ static void test_guard_replay_finish_equals_single_application(void)
|
|||
|
||||
/* 5.2 boundary pin — a FORWARD update on a terminal job must NOT be
|
||||
* over-rejected by the guard. on_job_postdone is the reachable
|
||||
* record_job_update_fact caller: on a DONE job it OR's JOB_STAT_PDONE
|
||||
* record_job_postdone_fact caller: on a DONE job it OR's JOB_STAT_PDONE
|
||||
* (terminal preserved, strictly forward). This must still durably append
|
||||
* (an entry appends, carrying the merged state) — the guard rejects
|
||||
* backward, never a forward merge. Preservation pin: green before and after
|
||||
* task 6.2.
|
||||
* (an entry appends, marked for the correct apply-time merge) — the guard
|
||||
* rejects backward, never a forward merge. Preservation pin: green before
|
||||
* and after task 6.2.
|
||||
*
|
||||
* raft-apply-decoupling task 4.1: on_job_postdone is now durable-append-only
|
||||
* — the oracle for "did the forward merge compute correctly" moves from live
|
||||
* state to the WAL-recorded fact (cbrp_count_jobstate_entries), mirroring
|
||||
* test_fact_retention_across_quorum_loss. The live-state-unchanged
|
||||
* assertion is this task's own new pin (Req 5.1): live state must still show
|
||||
* only DONE (from cbrp_drive_to_done's direct apply), never PDONE, since
|
||||
* nothing applies this fact live yet. */
|
||||
* raft-apply-decoupling task 4.1: on_job_postdone is now durable-append-only.
|
||||
* task 6.1 round 2 (merge-staleness fix, Req 11.3): the WAL-recorded fact no
|
||||
* longer carries a precomputed `state` at all — postdone's merge depends on
|
||||
* whatever state holds at APPLY time (which may differ from receipt time
|
||||
* once apply is deferred), so it is recomputed then by
|
||||
* apply_job_postdone_transition(), never baked into the WAL entry. The
|
||||
* oracle for "was this fact durably recorded correctly" is therefore the
|
||||
* WAL entry's `_pad` marker (cbrp_count_jobstate_entries_ex); the oracle for
|
||||
* "does the forward merge itself compute correctly" is a direct call to
|
||||
* apply_job_postdone_transition() below, mirroring how
|
||||
* test_guard_fail_after_finish_preserves_perr_merge pins apply_job_fail_
|
||||
* transition(). The live-state-unchanged assertion is task 4.1's own pin
|
||||
* (Req 5.1): live state must still show only DONE (from cbrp_drive_to_done's
|
||||
* direct apply), never PDONE, since nothing applies this fact live yet. */
|
||||
static void test_guard_forward_update_on_terminal_preserved(void)
|
||||
{
|
||||
uint64_t lsn_after_done, lsn_after_pdone;
|
||||
uint32_t st_disk = 0;
|
||||
uint32_t pad_disk = 0;
|
||||
int st_live;
|
||||
job_t scratch;
|
||||
|
||||
cbrp_setup(3000);
|
||||
cbrp_make_job(73, &g_job_submit);
|
||||
|
|
@ -2404,18 +2427,33 @@ static void test_guard_forward_update_on_terminal_preserved(void)
|
|||
lsn_after_pdone = g_hc.persist.current_lsn;
|
||||
pthread_mutex_unlock(&g_hc.persist.lock);
|
||||
|
||||
/* Assert: the update appended, carrying the correctly forward-merged
|
||||
* state (terminal DONE preserved, PDONE merged in — not rejected). */
|
||||
/* Assert: the update appended, marked for a correct apply-time forward
|
||||
* merge (terminal DONE preserved, PDONE merged in — not rejected). */
|
||||
TEST_ASSERT_TRUE_MESSAGE(lsn_after_pdone > lsn_after_done,
|
||||
"a forward update on a terminal job must still append its WAL entry "
|
||||
"— the guard rejects backward transitions, never forward merges");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(1,
|
||||
cbrp_count_jobstate_entries(&g_hc.persist, WAL_JOB_UPDATE, 73,
|
||||
&st_disk),
|
||||
cbrp_count_jobstate_entries_ex(&g_hc.persist, WAL_JOB_UPDATE, 73,
|
||||
NULL, &pad_disk),
|
||||
"exactly one UPDATE fact must be durably recorded (Req 5.1)");
|
||||
TEST_ASSERT_TRUE_MESSAGE((st_disk & JOB_STAT_DONE) != 0,
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(WAL_JOB_UPDATE_POSTDONE_MARK, pad_disk,
|
||||
"the recorded UPDATE fact must be marked for apply-time postdone "
|
||||
"recomputation (task 6.1 round 2) — not carry a state precomputed "
|
||||
"at receipt time, which would go stale under deferred apply");
|
||||
|
||||
/* Assert: the actual forward-merge guarantee this test exists to pin —
|
||||
* DONE preserved, PDONE merged in, not rejected — verified directly
|
||||
* against the guard core that performs it at apply time, on an
|
||||
* independent scratch job seeded at the same terminal state job 73 was
|
||||
* driven to above. */
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
scratch.job.jobid = 73;
|
||||
scratch.job.state = JOB_STAT_DONE;
|
||||
TEST_ASSERT_EQUAL_INT(JOB_TRANSITION_APPLIED,
|
||||
apply_job_postdone_transition(&scratch));
|
||||
TEST_ASSERT_TRUE_MESSAGE((scratch.job.state & JOB_STAT_DONE) != 0,
|
||||
"the recorded UPDATE fact must preserve the terminal bit");
|
||||
TEST_ASSERT_TRUE_MESSAGE((st_disk & JOB_STAT_PDONE) != 0,
|
||||
TEST_ASSERT_TRUE_MESSAGE((scratch.job.state & JOB_STAT_PDONE) != 0,
|
||||
"the recorded UPDATE fact must merge JOB_STAT_PDONE (not be "
|
||||
"rejected)");
|
||||
|
||||
|
|
@ -2439,18 +2477,27 @@ static void test_guard_forward_update_on_terminal_preserved(void)
|
|||
* Preservation pin: green before and after task 6.2.
|
||||
*
|
||||
* raft-apply-decoupling task 4.1: on_job_fail (via the new
|
||||
* record_job_fail_fact) is now durable-append-only — it computes the
|
||||
* PERR-merged target state on a throwaway copy of the job and durably
|
||||
* appends it, without touching the real job's live state. The oracle for
|
||||
* "did the merge compute correctly" moves from live state to the
|
||||
* WAL-recorded fact (cbrp_count_jobstate_entries); the live-state-unchanged
|
||||
* assertion is this task's own new pin (Req 5.1). */
|
||||
* record_job_fail_fact) is now durable-append-only. task 6.1 round 2
|
||||
* (merge-staleness fix, Req 11.3): the WAL-recorded fact no longer carries a
|
||||
* precomputed target state at all — the PERR merge genuinely depends on
|
||||
* whatever state holds at APPLY time (a receipt-time snapshot goes stale
|
||||
* the moment another fact for the same job, e.g. a FINISH, is still pending
|
||||
* apply — the exact bug this round fixed), so it is recomputed then by the
|
||||
* REAL apply_job_fail_transition(), never baked into the WAL entry at
|
||||
* receipt time. The oracle for "was this fact durably recorded correctly"
|
||||
* is therefore the WAL entry's `_pad`-carried stage
|
||||
* (cbrp_count_jobstate_entries_ex); the oracle for "does the PERR merge
|
||||
* itself compute correctly" is a direct call to apply_job_fail_transition()
|
||||
* below (its own logic is unchanged by this round — only WHEN it runs
|
||||
* changed). The live-state-unchanged assertion is task 4.1's own pin
|
||||
* (Req 5.1). */
|
||||
static void test_guard_fail_after_finish_preserves_perr_merge(void)
|
||||
{
|
||||
job_failinfo_t failinfo;
|
||||
uint64_t lsn_after_done, lsn_after_fail;
|
||||
uint32_t st_disk = 0;
|
||||
uint32_t pad_disk = 0;
|
||||
int st_live;
|
||||
job_t scratch;
|
||||
|
||||
cbrp_setup(3000);
|
||||
cbrp_make_job(74, &g_job_submit);
|
||||
|
|
@ -2470,19 +2517,36 @@ static void test_guard_fail_after_finish_preserves_perr_merge(void)
|
|||
lsn_after_fail = g_hc.persist.current_lsn;
|
||||
pthread_mutex_unlock(&g_hc.persist.lock);
|
||||
|
||||
/* Assert: the FAIL fact appended, carrying the correctly PERR-merged
|
||||
* state — DONE preserved, PERR merged in, not reset to bare EXIT. */
|
||||
/* Assert: the FAIL fact appended, marked for a correct apply-time
|
||||
* PERR-merge — DONE preserved, PERR merged in, not reset to bare EXIT. */
|
||||
TEST_ASSERT_TRUE_MESSAGE(lsn_after_fail > lsn_after_done,
|
||||
"the PERR-merge fail must still append its WAL entry (forward "
|
||||
"terminal→terminal merge — not a guarded-out no-op)");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(1,
|
||||
cbrp_count_jobstate_entries(&g_hc.persist, WAL_JOB_FAIL, 74,
|
||||
&st_disk),
|
||||
cbrp_count_jobstate_entries_ex(&g_hc.persist, WAL_JOB_FAIL, 74,
|
||||
NULL, &pad_disk),
|
||||
"exactly one FAIL fact must be durably recorded (Req 5.1)");
|
||||
TEST_ASSERT_TRUE_MESSAGE((st_disk & JOB_STAT_DONE) != 0,
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(
|
||||
WAL_JOB_FAIL_STAGE_FLAG | (uint32_t)JOB_EXEC_STAGE_POST_EXEC,
|
||||
pad_disk,
|
||||
"the recorded FAIL fact must carry its stage marked for apply-time "
|
||||
"recomputation (task 6.1 round 2) — not a state precomputed at "
|
||||
"receipt time, which would go stale under deferred apply");
|
||||
|
||||
/* Assert: the actual PERR-merge guarantee this test exists to pin —
|
||||
* DONE preserved, PERR merged in, not reset to bare EXIT — verified
|
||||
* directly against the guard core that performs it at apply time, on an
|
||||
* independent scratch job seeded at the same terminal state job 74 was
|
||||
* driven to above. */
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
scratch.job.jobid = 74;
|
||||
scratch.job.state = JOB_STAT_DONE;
|
||||
TEST_ASSERT_EQUAL_INT(JOB_TRANSITION_APPLIED,
|
||||
apply_job_fail_transition(&scratch, &failinfo));
|
||||
TEST_ASSERT_TRUE_MESSAGE((scratch.job.state & JOB_STAT_DONE) != 0,
|
||||
"the recorded FAIL fact must keep the DONE terminal bit "
|
||||
"(PERR-merge behavior)");
|
||||
TEST_ASSERT_TRUE_MESSAGE((st_disk & JOB_STAT_PERR) != 0,
|
||||
TEST_ASSERT_TRUE_MESSAGE((scratch.job.state & JOB_STAT_PERR) != 0,
|
||||
"the recorded FAIL fact must OR JOB_STAT_PERR onto the existing "
|
||||
"terminal state (PERR-merge behavior)");
|
||||
|
||||
|
|
@ -2684,7 +2748,7 @@ static void test_fact_state_unchanged_throughout_quorum_loss_outage(void)
|
|||
uint64_t lsn_before, lsn_after;
|
||||
uint64_t committed_at_poll;
|
||||
int initial_state, poll_state, final_state;
|
||||
uint32_t st_start = 0, st_fin = 0, st_fail = 0, st_upd = 0;
|
||||
uint32_t st_start = 0, st_fin = 0, pad_fail = 0, pad_upd = 0;
|
||||
int cnt_start, cnt_fin, cnt_fail, cnt_upd;
|
||||
int i;
|
||||
enum { OUTAGE_POLL_COUNT = 5, OUTAGE_POLL_INTERVAL_MS = 20 };
|
||||
|
|
@ -2790,10 +2854,10 @@ static void test_fact_state_unchanged_throughout_quorum_loss_outage(void)
|
|||
&st_start);
|
||||
cnt_fin = cbrp_count_jobstate_entries(&g_hc.persist, WAL_JOB_FINISH, 85,
|
||||
&st_fin);
|
||||
cnt_fail = cbrp_count_jobstate_entries(&g_hc.persist, WAL_JOB_FAIL, 85,
|
||||
&st_fail);
|
||||
cnt_upd = cbrp_count_jobstate_entries(&g_hc.persist, WAL_JOB_UPDATE, 85,
|
||||
&st_upd);
|
||||
cnt_fail = cbrp_count_jobstate_entries_ex(&g_hc.persist, WAL_JOB_FAIL, 85,
|
||||
NULL, &pad_fail);
|
||||
cnt_upd = cbrp_count_jobstate_entries_ex(&g_hc.persist, WAL_JOB_UPDATE,
|
||||
85, NULL, &pad_upd);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(1, cnt_start,
|
||||
"job 85 START fact must remain durably retained exactly once "
|
||||
|
|
@ -2809,14 +2873,20 @@ static void test_fact_state_unchanged_throughout_quorum_loss_outage(void)
|
|||
"throughout the whole outage window (Req 6.1)");
|
||||
TEST_ASSERT_EQUAL_UINT32(JOB_STAT_RUN, st_start);
|
||||
TEST_ASSERT_EQUAL_UINT32(JOB_STAT_DONE, st_fin);
|
||||
TEST_ASSERT_TRUE_MESSAGE((st_upd & JOB_STAT_PDONE) != 0,
|
||||
"the recorded UPDATE fact must carry JOB_STAT_PDONE");
|
||||
TEST_ASSERT_TRUE_MESSAGE((st_upd & JOB_STAT_EXIT) != 0,
|
||||
"the recorded UPDATE fact must carry the backstop JOB_STAT_EXIT "
|
||||
"bit postdone computes when no prior exit status was set");
|
||||
(void)st_fail; /* FAIL's merge result depends on current state at the
|
||||
* (never-reached) apply step; only its durable
|
||||
* retention count is this test's concern. */
|
||||
/* task 6.1 round 2 (Req 11.3): FAIL's and postdone's UPDATE's merge
|
||||
* results depend on current state at the (never-reached, in this
|
||||
* outage-only test) apply step — they are no longer precomputed at
|
||||
* receipt time at all (the merge-staleness fix), so only their durable
|
||||
* retention count and their apply-time-recomputation marker are this
|
||||
* test's concern. */
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(
|
||||
WAL_JOB_FAIL_STAGE_FLAG | (uint32_t)JOB_EXEC_STAGE_POST_EXEC,
|
||||
pad_fail,
|
||||
"the recorded FAIL fact must carry its stage marked for apply-time "
|
||||
"recomputation");
|
||||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(WAL_JOB_UPDATE_POSTDONE_MARK, pad_upd,
|
||||
"the recorded UPDATE fact must be marked for apply-time postdone "
|
||||
"recomputation");
|
||||
|
||||
/* Final check: after the ENTIRE outage window, job state is STILL
|
||||
* unchanged and no reaction has fired — sealing the "for the duration
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue