feat(ha-heartbeat-liveness): verify ctrlhello integration and RDHUP cancel (task 3.2)

Verification task: confirmed config_repl.c handle_ctrlhello calls both
ha_on_heartbeat_received and ha_arm_heartbeat_timeout_vtask (delaymgr poll).
Confirmed ha_on_primary_ctrl_fd_rdhup leads with ha_disarm (Req 5.4).
No pthread_create in heartbeat arm path (Req 1.6). Add thread-count test.
This commit is contained in:
Rongsong Shen 2026-06-01 15:37:35 +00:00
parent d937c1c1bb
commit 12216f1165
2 changed files with 60 additions and 1 deletions

View file

@ -48,7 +48,7 @@
- _Depends: 2.1_ - _Depends: 2.1_
- _Boundary: cluster.c (opcode dispatch)_ - _Boundary: cluster.c (opcode dispatch)_
- [ ] 3.2 Verify config_repl.c ctrlhello integration and EPOLLRDHUP cancellation path - [x] 3.2 Verify config_repl.c ctrlhello integration and EPOLLRDHUP cancellation path
- Confirm `ha_arm_heartbeat_timeout_vtask` call in `handle_ctrlhello` (config_repl.c:1380) still correctly arms the delaymgr poll (not a thread) - Confirm `ha_arm_heartbeat_timeout_vtask` call in `handle_ctrlhello` (config_repl.c:1380) still correctly arms the delaymgr poll (not a thread)
- Confirm `ha_on_heartbeat_received` call at config_repl.c:1379 writes `last_activity` (new impl) - Confirm `ha_on_heartbeat_received` call at config_repl.c:1379 writes `last_activity` (new impl)
- Confirm `ha_on_primary_ctrl_fd_rdhup` now calls `ha_disarm_heartbeat_timeout_vtask` as its first step (Req 5.4) - Confirm `ha_on_primary_ctrl_fd_rdhup` now calls `ha_disarm_heartbeat_timeout_vtask` as its first step (Req 5.4)

View file

@ -576,6 +576,64 @@ static void test_cluster_dispatch_ctrlhello_resets_activity_when_standby(void)
printf("test_cluster_dispatch_ctrlhello_resets_activity_when_standby: PASS\n"); printf("test_cluster_dispatch_ctrlhello_resets_activity_when_standby: PASS\n");
} }
/* ── Test 14: ha_arm_heartbeat_timeout_vtask does not increase thread count ── */
/*
* Task 3.2, Req 1.6: ha_arm_heartbeat_timeout_vtask must NOT spawn a pthread.
*
* We read the process thread count from /proc/self/status before and after
* calling the arm function and assert the count is unchanged. This is a
* stronger check than test_ha_arm_no_crash_no_thread (Test 6), which only
* verifies no crash and last_activity written.
*
* Note: with c==NULL the delaymgr is not running; delay_task_ms will fail
* silently (no actual delaymgr task is queued), but the arm path executes and
* must not create a thread as a side-effect.
*/
static int read_proc_thread_count(void)
{
FILE *f = fopen("/proc/self/status", "r");
if (!f) return -1;
char line[128];
int count = -1;
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, "Threads:", 8) == 0) {
if (sscanf(line + 8, "%d", &count) != 1)
count = -1;
break;
}
}
fclose(f);
return count;
}
static void test_ha_arm_does_not_increase_thread_count(void)
{
ha_state_t ha;
TEST_ASSERT_EQUAL_INT(0, ha_init(&ha, &g_cfg, 1));
ha_do_demote(&ha);
int threads_before = read_proc_thread_count();
TEST_ASSERT_NOT_EQUAL_MESSAGE(-1, threads_before,
"could not read /proc/self/status Threads");
/* Call arm — must not spawn a pthread (Req 1.6) */
ha_arm_heartbeat_timeout_vtask(&ha, NULL);
int threads_after = read_proc_thread_count();
TEST_ASSERT_NOT_EQUAL_MESSAGE(-1, threads_after,
"could not read /proc/self/status Threads after arm");
TEST_ASSERT_EQUAL_INT_MESSAGE(threads_before, threads_after,
"Req 1.6: ha_arm_heartbeat_timeout_vtask must not spawn a pthread");
/* Disarm to release the ctx allocation */
ha_disarm_heartbeat_timeout_vtask(&ha);
ha_fini(&ha);
printf("test_ha_arm_does_not_increase_thread_count: PASS\n");
}
/* ── Suite entry point ──────────────────────────────────────────────────── */ /* ── Suite entry point ──────────────────────────────────────────────────── */
void test_ha_liveness(void) void test_ha_liveness(void)
@ -594,5 +652,6 @@ void test_ha_liveness(void)
RUN_TEST(test_cluster_dispatch_nop_resets_activity_when_standby); RUN_TEST(test_cluster_dispatch_nop_resets_activity_when_standby);
RUN_TEST(test_cluster_dispatch_nop_skips_when_primary); RUN_TEST(test_cluster_dispatch_nop_skips_when_primary);
RUN_TEST(test_cluster_dispatch_ctrlhello_resets_activity_when_standby); RUN_TEST(test_cluster_dispatch_ctrlhello_resets_activity_when_standby);
RUN_TEST(test_ha_arm_does_not_increase_thread_count);
UNITY_END(); UNITY_END();
} }