feat(controller-status): source election time in ctrlstatus_fill (task 7.4)
ctrlstatus_fill now unconditionally reads ha->last_known_elected_at_ms into the reply's elected_at_ms field -- one atomic-acquire load, no role-branch, mirroring how the handler already reports raw term unconditionally. Serves both a self-stamped primary and a heartbeat-learned standby from the same field. "Unknown" display gating is deliberately left to the client command (task 7.5), not this handler. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
30adc52ea3
commit
93f32b5d84
3 changed files with 87 additions and 1 deletions
|
|
@ -90,6 +90,20 @@ void ctrlstatus_fill(cluster_t* c, xce_ctrlstatus_t* st)
|
|||
memory_order_acquire);
|
||||
st->term = atomic_load_explicit(&ha->term, memory_order_acquire);
|
||||
|
||||
/* current primary's election time (Req 12.1, 12.2, 12.5): a single
|
||||
* unconditional read of last_known_elected_at_ms -- no role-branch. Both
|
||||
* promotion roads self-stamp this field at the instant they commit to
|
||||
* CTRL_PRIMARY (task 7.2), and it is unconditionally learned off-primary
|
||||
* via heartbeat (task 7.3), so one atomic load correctly serves both the
|
||||
* primary (self-stamped) and standby (heartbeat-learned) case -- unlike
|
||||
* committedLsn/committedConfigVersion above, there is no separate
|
||||
* primary-only marker to branch on here. "Unknown" display gating (Req
|
||||
* 12.3) is a client-side print-layer decision (task 7.5), not this
|
||||
* handler's concern -- mirrors how st->term above is also read
|
||||
* unconditionally regardless of whether a primary is currently known. */
|
||||
st->elected_at_ms = atomic_load_explicit(&ha->last_known_elected_at_ms,
|
||||
memory_order_acquire);
|
||||
|
||||
/* primary identity (Req 2.1/2.2/2.3). Only the PRIMARY can authoritatively
|
||||
* name the primary — itself. The controller does NOT track the current
|
||||
* primary's NAME on a standby: ha->primary_host is vestigial (never written)
|
||||
|
|
|
|||
|
|
@ -298,6 +298,9 @@ static void test_ctrlstatus_no_mutation(void)
|
|||
memory_order_release);
|
||||
atomic_store_explicit(&c.ha.in_catch_up, 0, memory_order_release);
|
||||
strncpy(c.ha.voted_for_name, "ctrl-1", sizeof(c.ha.voted_for_name) - 1);
|
||||
/* Req 12.5: reading the learned election-time must not mutate it either. */
|
||||
atomic_store_explicit(&c.ha.last_known_elected_at_ms,
|
||||
UINT64_C(1781939830911), memory_order_release);
|
||||
|
||||
int32_t role0 = atomic_load_explicit(&c.ha.role, memory_order_acquire);
|
||||
uint64_t term0 = atomic_load_explicit(&c.ha.term, memory_order_acquire);
|
||||
|
|
@ -312,6 +315,8 @@ static void test_ctrlstatus_no_mutation(void)
|
|||
char voted0[XCE_MAXNAME_LEN];
|
||||
strncpy(voted0, c.ha.voted_for_name, sizeof(voted0));
|
||||
uint64_t vertag0 = mc.config_version_tag;
|
||||
uint64_t elected0 = atomic_load_explicit(&c.ha.last_known_elected_at_ms,
|
||||
memory_order_acquire);
|
||||
|
||||
xce_ctrlstatus_t st;
|
||||
ctrlstatus_fill(&c, &st);
|
||||
|
|
@ -335,6 +340,9 @@ static void test_ctrlstatus_no_mutation(void)
|
|||
memory_order_acquire));
|
||||
TEST_ASSERT_EQUAL_STRING(voted0, c.ha.voted_for_name);
|
||||
TEST_ASSERT_EQUAL_UINT64(vertag0, mc.config_version_tag);
|
||||
TEST_ASSERT_EQUAL_UINT64(elected0,
|
||||
atomic_load_explicit(&c.ha.last_known_elected_at_ms,
|
||||
memory_order_acquire));
|
||||
|
||||
cs_teardown_cluster(&c);
|
||||
printf("test_ctrlstatus_no_mutation: PASS\n");
|
||||
|
|
@ -633,6 +641,68 @@ static void test_heartbeatmsg_electedatms_roundtrip(void)
|
|||
printf("test_heartbeatmsg_electedatms_roundtrip: PASS\n");
|
||||
}
|
||||
|
||||
/* (m) PRIMARY: elected_at_ms reports the responder's own learned value
|
||||
* (Req 12.1, 12.2, 12.5). Design Delta Req 12 locked decision 2: BOTH
|
||||
* promotion roads self-stamp `ha->last_known_elected_at_ms` at the instant
|
||||
* they commit to CTRL_PRIMARY -- there is no separate primary-only marker
|
||||
* the way there is for committedLsn (Req 10) -- so ctrlstatus_fill needs a
|
||||
* single UNCONDITIONAL read of last_known_elected_at_ms for every role. This
|
||||
* test seeds that field directly (same convention as the pre-existing
|
||||
* committed-position tests) to prove the primary case round-trips it. */
|
||||
static void test_ctrlstatus_primary_reports_own_elected_at_ms(void)
|
||||
{
|
||||
cluster_t c;
|
||||
myconf_t mc;
|
||||
cs_setup_cluster(&c, &mc);
|
||||
|
||||
atomic_store_explicit(&c.ha.role, (int32_t)CTRL_PRIMARY,
|
||||
memory_order_release);
|
||||
/* self-stamped by the promotion road (task 7.2); seeded directly here per
|
||||
* this task's own testing convention. */
|
||||
atomic_store_explicit(&c.ha.last_known_elected_at_ms,
|
||||
UINT64_C(1781939830911), memory_order_release);
|
||||
|
||||
xce_ctrlstatus_t st;
|
||||
ctrlstatus_fill(&c, &st);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8((uint8_t)CTRL_PRIMARY, st.responderRole);
|
||||
TEST_ASSERT_NOT_EQUAL_UINT64(0, st.elected_at_ms);
|
||||
TEST_ASSERT_EQUAL_UINT64(UINT64_C(1781939830911), st.elected_at_ms);
|
||||
|
||||
ctrlstatus_free_fields(&st);
|
||||
cs_teardown_cluster(&c);
|
||||
printf("test_ctrlstatus_primary_reports_own_elected_at_ms: PASS\n");
|
||||
}
|
||||
|
||||
/* (n) STANDBY seeded with a learned election-time (simulating an already-
|
||||
* processed heartbeat, task 7.3) reports that LEARNED value, not zero (Req
|
||||
* 12.1, 12.2). Mirrors test_ctrlstatus_standby_reports_learned_committed's
|
||||
* pattern for the committed pair -- except here there is no role-branch: the
|
||||
* handler reads the SAME field unconditionally regardless of responder role. */
|
||||
static void test_ctrlstatus_standby_reports_learned_elected_at_ms(void)
|
||||
{
|
||||
cluster_t c;
|
||||
myconf_t mc;
|
||||
cs_setup_cluster(&c, &mc);
|
||||
|
||||
atomic_store_explicit(&c.ha.role, (int32_t)CTRL_STANDBY,
|
||||
memory_order_release);
|
||||
/* learned from the primary's heartbeat (task 7.3); seeded directly here. */
|
||||
atomic_store_explicit(&c.ha.last_known_elected_at_ms,
|
||||
UINT64_C(1781939830911), memory_order_release);
|
||||
|
||||
xce_ctrlstatus_t st;
|
||||
ctrlstatus_fill(&c, &st);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8((uint8_t)CTRL_STANDBY, st.responderRole);
|
||||
TEST_ASSERT_NOT_EQUAL_UINT64(0, st.elected_at_ms);
|
||||
TEST_ASSERT_EQUAL_UINT64(UINT64_C(1781939830911), st.elected_at_ms);
|
||||
|
||||
ctrlstatus_free_fields(&st);
|
||||
cs_teardown_cluster(&c);
|
||||
printf("test_ctrlstatus_standby_reports_learned_elected_at_ms: PASS\n");
|
||||
}
|
||||
|
||||
/* ── suite entry point ─────────────────────────────────────── */
|
||||
|
||||
void test_ctrlstatus(void)
|
||||
|
|
@ -652,4 +722,6 @@ void test_ctrlstatus(void)
|
|||
RUN_TEST(test_ctrlstatus_responder_time_ms);
|
||||
RUN_TEST(test_ctrlstatus_electedatms_roundtrip);
|
||||
RUN_TEST(test_heartbeatmsg_electedatms_roundtrip);
|
||||
RUN_TEST(test_ctrlstatus_primary_reports_own_elected_at_ms);
|
||||
RUN_TEST(test_ctrlstatus_standby_reports_learned_elected_at_ms);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue