diff --git a/src/controller/myapp.c b/src/controller/myapp.c index fffb8d7..838bad5 100644 --- a/src/controller/myapp.c +++ b/src/controller/myapp.c @@ -211,6 +211,37 @@ int myapp_run(myconf_t *myconf) uint64_t my_lsn = (uint64_t)atomic_load_explicit(&c.ha.applied_lsn, memory_order_acquire); + /* Startup stagger: delay election start by priority * election_priority_step_ms. + * Node 0 starts first; lower-priority nodes wait, see the winner's vote request + * arrive, and grant the vote instead of starting their own election. + * Uses the same step as the existing blocking-election probe path (Req 1.2). */ + if (c.ha.my_priority > 0) { + uint32_t step_ms = c.ha.config.election_priority_step_ms + ? c.ha.config.election_priority_step_ms + : HA_DEFAULT_ELECTION_PRIORITY_STEP_MS; + long delay_ns = (long)c.ha.my_priority * (long)step_ms * 1000000L; + struct timespec jitter = { + .tv_sec = delay_ns / 1000000000L, + .tv_nsec = delay_ns % 1000000000L, + }; + nanosleep(&jitter, NULL); + } + + /* If we already voted for someone during the startup delay (incoming vote + * request arrived from the higher-priority node that started first), skip + * starting our own election — we'll become STANDBY via the election timeout + * watchdog on the winner's side, or promote ourselves if no heartbeat arrives. */ + { + pthread_mutex_lock(&c.ha.lock); + int already_voted = (c.ha.voted_for_name[0] != '\0'); + pthread_mutex_unlock(&c.ha.lock); + if (already_voted) { + INFO("myapp_run: already voted during startup delay — " + "skipping own election, waiting for primary"); + goto skip_election; + } + } + /* Non-blocking: arms election timeout watchdog, connects outgoing vote * endpoints, returns immediately. Election resolves via iomgr callbacks; * ha_elect_evaluate_and_signal calls cluster_init_phase2 from vtask context @@ -219,6 +250,8 @@ int myapp_run(myconf_t *myconf) (n_other > 0) ? peer_ptrs : NULL, my_tag, my_lsn, myconf->name); +skip_election:; + /* ── Step 9: cluster_run ─────────────────────────────────────────────── */ /* Req 9.4: myapp_run exits into cluster_run within 10ms of startup.