diff --git a/.kiro/specs/scheduler-dispatch-commit/tasks.md b/.kiro/specs/scheduler-dispatch-commit/tasks.md index 63e81d02..30d5f244 100644 --- a/.kiro/specs/scheduler-dispatch-commit/tasks.md +++ b/.kiro/specs/scheduler-dispatch-commit/tasks.md @@ -15,7 +15,7 @@ - _Requirements: 2.4_ - _Boundary: WAL Dispatch Fact_ -- [ ] 1.3 (P) Track how many consecutive times a job's dispatch has failed for a retryable reason +- [x] 1.3 (P) Track how many consecutive times a job's dispatch has failed for a retryable reason - Add an in-memory counter on the controller's job record that increments on each retryable dispatch failure and resets to zero on a successful dispatch - This counter does not need to survive a controller failover — resetting to zero after a failover is an acceptable, safe degradation - Observable completion: the counter can be incremented, read, and reset independently of any actual dispatch attempt diff --git a/src/controller/meson.build b/src/controller/meson.build index 931fbc64..0464c5dc 100644 --- a/src/controller/meson.build +++ b/src/controller/meson.build @@ -170,6 +170,7 @@ controller_unit_test_src = [ 'testsuite/test_apply_committed_entry.c', 'testsuite/test_replay_apply_seam.c', 'testsuite/test_wal_job_dispatch_fact.c', + 'testsuite/test_job_dispatch_fail_count.c', 'testsuite/test_apply_reapply_idempotency.c', 'testsuite/test_apply_correctness_verification.c', 'testsuite/test_apply_pump.c', diff --git a/src/controller/testsuite/controller_test.c b/src/controller/testsuite/controller_test.c index 791531ab..cf07de74 100644 --- a/src/controller/testsuite/controller_test.c +++ b/src/controller/testsuite/controller_test.c @@ -84,6 +84,7 @@ extern void test_apply_seam(void); extern void test_apply_committed_entry(void); extern void test_replay_apply_seam(void); extern void test_wal_job_dispatch_fact(void); +extern void test_job_dispatch_fail_count(void); extern void test_apply_reapply_idempotency(void); extern void test_apply_correctness_verification(void); extern void test_apply_pump(void); @@ -249,6 +250,7 @@ int main(int argc, char* argv[]) TEST(test_apply_committed_entry); TEST(test_replay_apply_seam); TEST(test_wal_job_dispatch_fact); + TEST(test_job_dispatch_fail_count); TEST(test_apply_reapply_idempotency); TEST(test_apply_correctness_verification); TEST(test_apply_pump); diff --git a/src/controller/testsuite/test_job_dispatch_fail_count.c b/src/controller/testsuite/test_job_dispatch_fail_count.c new file mode 100644 index 00000000..461ba12b --- /dev/null +++ b/src/controller/testsuite/test_job_dispatch_fail_count.c @@ -0,0 +1,98 @@ +/* test_job_dispatch_fail_count.c — scheduler-dispatch-commit task 1.3 + * + * Proves job_t.dispatch_fail_count (xce/types.h) -- a new in-memory-only + * uint32_t counter on the job_t wrapper ({xce_job_t job; uint64_t + * submit_lsn;}), NOT on xce_job_t itself -- exists and behaves as a plain, + * directly settable/readable/resettable scalar (Requirement 4.3, design.md + * "Logical Data Model": "in-memory only... following submit_lsn's exact + * precedent... recomputed... never serialized"). + * + * This task is the data-model addition ONLY. It does NOT wire any + * increment/reset call site into dispatch_a_job_() (task 3.1/3.3's job) and + * does NOT implement the bounded-retry threshold check (also task 3.3). + * Every test here manipulates the field directly on a bare stack-local + * job_t -- no jobmgr/RTTI/makeJob machinery -- mirroring + * test_persist_types.c's test_slot_key_t_fields precedent for a plain + * struct-field addition, since this is not a behavioral/dispatch-path test. + */ +#include +#include +#include + +#include + +#include "xce/types.h" + +static void test_dispatch_fail_count_default_zero(void) +{ + job_t j; + memset(&j, 0, sizeof(j)); + + TEST_ASSERT_EQUAL_UINT32(0, j.dispatch_fail_count); + printf("test_dispatch_fail_count_default_zero: PASS\n"); +} + +static void test_dispatch_fail_count_increments_independently(void) +{ + job_t j; + memset(&j, 0, sizeof(j)); + + /* Simulates 3 consecutive retryable dispatch failures with no + * intervening successful dispatch (Requirement 4.3) -- incremented + * directly, with no actual dispatch attempt involved. */ + j.dispatch_fail_count++; + TEST_ASSERT_EQUAL_UINT32(1, j.dispatch_fail_count); + + j.dispatch_fail_count++; + j.dispatch_fail_count++; + TEST_ASSERT_EQUAL_UINT32_MESSAGE(3, j.dispatch_fail_count, + "counter must be incrementable directly, independent of any actual " + "dispatch attempt (Observable completion)"); + printf("test_dispatch_fail_count_increments_independently: PASS\n"); +} + +static void test_dispatch_fail_count_resets_to_zero(void) +{ + job_t j; + memset(&j, 0, sizeof(j)); + + j.dispatch_fail_count = 7; + TEST_ASSERT_EQUAL_UINT32(7, j.dispatch_fail_count); + + /* A successful dispatch resets the counter to zero -- task 3.1/3.3 wire + * the real call site; this proves the field itself supports the + * reset-to-zero operation the design requires, independent of any + * actual dispatch attempt. */ + j.dispatch_fail_count = 0; + TEST_ASSERT_EQUAL_UINT32_MESSAGE(0, j.dispatch_fail_count, + "counter must be independently resettable to zero, matching " + "submit_lsn's in-memory-only precedent (no durability obligation, " + "safe to lose across a controller failover)"); + printf("test_dispatch_fail_count_resets_to_zero: PASS\n"); +} + +static void test_dispatch_fail_count_does_not_alias_submit_lsn(void) +{ + job_t j; + memset(&j, 0, sizeof(j)); + + j.submit_lsn = 12345; + j.dispatch_fail_count = 6; + + TEST_ASSERT_EQUAL_UINT64(12345, j.submit_lsn); + TEST_ASSERT_EQUAL_UINT32_MESSAGE(6, j.dispatch_fail_count, + "dispatch_fail_count must be its own field on job_t, distinct from " + "submit_lsn (both in-memory-only siblings on the wrapper struct)"); + printf("test_dispatch_fail_count_does_not_alias_submit_lsn: PASS\n"); +} + +void test_job_dispatch_fail_count(void) +{ + UnitySetTestFile(__FILE__); + printf("\n>> job_t.dispatch_fail_count test\n\n"); + + RUN_TEST(test_dispatch_fail_count_default_zero); + RUN_TEST(test_dispatch_fail_count_increments_independently); + RUN_TEST(test_dispatch_fail_count_resets_to_zero); + RUN_TEST(test_dispatch_fail_count_does_not_alias_submit_lsn); +} diff --git a/src/include/xce/types.h b/src/include/xce/types.h index 4c0ee34a..8e81bb34 100644 --- a/src/include/xce/types.h +++ b/src/include/xce/types.h @@ -487,6 +487,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 {