From d109155a219c5f035d84932b6bf36f1c7a7ce126 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Sun, 31 May 2026 05:03:04 +0000 Subject: [PATCH] =?UTF-8?q?feat(raft-library):=20task=202.2=20=E2=80=94=20?= =?UTF-8?q?raft=5Fcallbacks=5Ft,=20raft=5Fconfig=5Ft,=20public=20API=20sig?= =?UTF-8?q?natures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .kiro/specs/raft-library/tasks.md | 2 +- src/lib/raft/raft.c | 48 ++++++++++ src/lib/raft/raft.h | 142 ++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 1 deletion(-) diff --git a/.kiro/specs/raft-library/tasks.md b/.kiro/specs/raft-library/tasks.md index 065ce47e..3565042d 100644 --- a/.kiro/specs/raft-library/tasks.md +++ b/.kiro/specs/raft-library/tasks.md @@ -15,7 +15,7 @@ - Header includes only `stdint.h`, `string.h`, `errno.h` — no `iomgr_t`, `endpoint_t`, `SSL_CTX`, `cluster_t`, or capnp types visible anywhere - _Requirements: 1.1, 1.3_ -- [ ] 2.2 Define callback interface (raft_callbacks_t, raft_config_t) and all public function signatures +- [x] 2.2 Define callback interface (raft_callbacks_t, raft_config_t) and all public function signatures - `raft_callbacks_t` with 9 function pointers: `on_send_vote_request`, `on_vote_eligible` (optional/NULL-safe), `on_become_leader`, `on_become_follower`, `on_persist`, `on_schedule_timeout`, `on_cancel_timeout`, `on_start_election_after_delay`, `on_trace` (optional/NULL-safe) - `raft_config_t` struct: `n_peers`, `self_idx`, `election_timeout_ms` (0 → 2000ms default), `my_priority`, `initial_term`, `initial_voted_for`, `cbs`, `ctx` - All public function signatures: `raft_init`, `raft_start_election`, `raft_on_vote_request`, `raft_on_vote_reply`, `raft_on_election_timeout`, `raft_on_leader_lost`, `raft_check_and_start_election`, `raft_snapshot` diff --git a/src/lib/raft/raft.c b/src/lib/raft/raft.c index fdeeef24..143ba602 100644 --- a/src/lib/raft/raft.c +++ b/src/lib/raft/raft.c @@ -9,3 +9,51 @@ void raft_snapshot(const raft_state_t *s, raft_snapshot_t *snap) snap->votes_granted = s->votes_granted; snap->election_decided = s->election_decided; } + +int raft_init(raft_state_t *s, const raft_config_t *cfg) +{ + (void)s; + (void)cfg; + return 0; +} + +int raft_start_election(raft_state_t *s) +{ + (void)s; + return 0; +} + +int raft_on_vote_request(raft_state_t *s, int from_idx, uint64_t term, + uint64_t *reply_term) +{ + (void)s; + (void)from_idx; + (void)term; + (void)reply_term; + return 0; +} + +int raft_on_vote_reply(raft_state_t *s, int from_idx, int granted, uint64_t term) +{ + (void)s; + (void)from_idx; + (void)granted; + (void)term; + return 0; +} + +void raft_on_election_timeout(raft_state_t *s) +{ + (void)s; +} + +void raft_on_leader_lost(raft_state_t *s) +{ + (void)s; +} + +int raft_check_and_start_election(raft_state_t *s) +{ + (void)s; + return 0; +} diff --git a/src/lib/raft/raft.h b/src/lib/raft/raft.h index 9255a37a..b149497b 100644 --- a/src/lib/raft/raft.h +++ b/src/lib/raft/raft.h @@ -41,6 +41,148 @@ typedef struct { int election_decided; } raft_snapshot_t; +/* raft_callbacks_t: all side effects the library fires. + * + * The library owns Raft invariants (term ordering, one-vote-per-term, quorum). + * All application-specific policy (freshness gate, role gate, catch-up state, + * heartbeat liveness) is delegated to on_vote_eligible so the library is + * fully independent of XCE domain logic. + * + * REQUIRED callbacks (must be non-NULL at raft_init): + * on_send_vote_request, on_become_leader, on_become_follower, + * on_persist, on_schedule_timeout, on_cancel_timeout, + * on_start_election_after_delay + * + * OPTIONAL callbacks (may be NULL): + * on_vote_eligible — if NULL, all Raft-eligible candidates are granted + * on_trace — if NULL, tracing is suppressed + */ +typedef struct { + /* REQUIRED: Send a RequestVote RPC to peer peer_idx for the given term. + * The caller encodes config_tag, log_lsn, and any other fields in the wire RPC. */ + int (*on_send_vote_request)(void *ctx, int peer_idx, uint64_t term); + + /* OPTIONAL: Application-specific vote eligibility gate. + * Called after Raft invariants pass (correct term, voted_for unset). + * Returns 1 to grant, 0 to deny. Receives the candidate's peer_idx and + * the incoming term so the caller can check freshness (config_tag, log_lsn), + * role gate (LEADER/COMMITTING deny), catch-up status, heartbeat liveness. + * If NULL, the library grants the vote whenever Raft invariants pass. */ + int (*on_vote_eligible)(void *ctx, int from_idx, uint64_t term); + + /* REQUIRED: Node has won the election */ + void (*on_become_leader)(void *ctx, uint64_t term); + /* REQUIRED: Node lost election or saw higher term; leader_idx = -1 if unknown */ + void (*on_become_follower)(void *ctx, uint64_t term, int leader_idx); + /* REQUIRED: Persist term + voted_for before granting vote (durability guarantee) */ + int (*on_persist)(void *ctx, uint64_t term, int voted_for); + /* REQUIRED: Schedule election timeout (fires raft_on_election_timeout after delay_ms) */ + void (*on_schedule_timeout)(void *ctx, uint32_t delay_ms); + /* REQUIRED: Cancel pending election timeout */ + void (*on_cancel_timeout)(void *ctx); + /* REQUIRED: Request delayed election start after RDHUP (priority-based jitter) */ + void (*on_start_election_after_delay)(void *ctx, uint32_t delay_ms); + /* OPTIONAL: Trace callback for observability. + * event: short string literal, e.g. "vote_granted", "became_leader" + * detail: formatted human-readable context string + * If NULL, tracing is suppressed. */ + void (*on_trace)(void *ctx, const char *event, const char *detail); +} raft_callbacks_t; + +/* + * raft_config_t: initialization context for a Raft node. + * + * Encapsulates all configuration so raft_init() has a single, extensible + * parameter rather than a flat list. Matches the existing XCE pattern + * (ha_config_t, persist_config_t). Zero-initialize and set only the + * fields you need — the library fills in safe defaults for unset fields. + * + * Persistence model: + * initial_term / initial_voted_for are restored by the caller from disk + * before raft_init(). The library never reads storage; it only writes + * via on_persist callbacks. Pass {0, -1} on first boot. + */ +typedef struct { + int n_peers; /* total cluster size (including self) */ + int self_idx; /* this node's index in [0, n_peers) */ + uint32_t election_timeout_ms; /* base election timeout; 0 → default 2000ms */ + int my_priority; /* priority index for RDHUP jitter (0 = highest) */ + uint64_t initial_term; /* term restored from disk; 0 on first boot */ + int initial_voted_for; /* voted_for restored from disk; -1 if none */ + const raft_callbacks_t *cbs; /* all callbacks; required callbacks must be non-NULL */ + void *ctx; /* opaque caller context passed to every callback */ +} raft_config_t; + +/* Return codes + * + * Note: RAFT_GRANT and RAFT_BECAME_LEADER share value 1; RAFT_DENY and RAFT_OK + * share value 0. This is intentional — different return values are used by + * different functions (raft_on_vote_request vs raft_on_vote_reply) in different + * call contexts. + */ +#define RAFT_OK 0 +#define RAFT_GRANT 1 +#define RAFT_DENY 0 +#define RAFT_BECAME_LEADER 1 +#define RAFT_BECAME_FOLLOWER 2 +#define RAFT_PENDING 3 +#define RAFT_ELECTED_IMMEDIATELY 4 +#define RAFT_SKIP_ELECTION 5 +#define RAFT_ELECTION_STARTED 6 + +/* + * raft_init: initialize raft_state_t from a raft_config_t. + * on_vote_eligible and on_trace may be NULL (optional policy / observability). + * All other callbacks in cbs must be non-NULL. + * Returns 0 on success; -EINVAL if a required callback is NULL or n_peers < 1. + */ +int raft_init(raft_state_t *s, const raft_config_t *cfg); + +/* + * raft_start_election: begin a new election. + * Increments term, records self-vote (voted_for=self_idx), transitions to + * CANDIDATE, fires on_send_vote_request for each peer, fires on_schedule_timeout. + * Returns 0 on success; -EALREADY if already CANDIDATE/LEADER; + * RAFT_ELECTED_IMMEDIATELY for single-node (n_peers==1). + */ +int raft_start_election(raft_state_t *s); + +/* + * raft_on_vote_request: process an incoming RequestVote RPC. + * Applies Raft invariants (term comparison, one-vote-per-term). + * If invariants pass, calls on_vote_eligible to allow application policy. + * Fires on_persist before granting. + * Returns RAFT_GRANT (1) or RAFT_DENY (0); sets *reply_term to current term. + */ +int raft_on_vote_request(raft_state_t *s, int from_idx, uint64_t term, + uint64_t *reply_term); + +/* + * raft_on_vote_reply: process a reply to our RequestVote. + * Returns RAFT_BECAME_LEADER, RAFT_BECAME_FOLLOWER, or RAFT_PENDING. + */ +int raft_on_vote_reply(raft_state_t *s, int from_idx, int granted, uint64_t term); + +/* + * raft_on_election_timeout: called by the timer when no quorum was reached. + * Transitions CANDIDATE → FOLLOWER; fires on_become_follower, on_cancel_timeout. + */ +void raft_on_election_timeout(raft_state_t *s); + +/* + * raft_on_leader_lost: primary control channel dropped (RDHUP). + * Clears voted_for, transitions to FOLLOWER, fires on_start_election_after_delay. + */ +void raft_on_leader_lost(raft_state_t *s); + +/* + * raft_check_and_start_election: call after the delay from raft_on_leader_lost. + * Starts election only if we have not already voted for a non-self candidate + * in the new term (i.e., another node won during the jitter window). + * Returns RAFT_SKIP_ELECTION or RAFT_ELECTION_STARTED. + */ +int raft_check_and_start_election(raft_state_t *s); + /* Fills snap with current state. Caller must hold lock. */ void raft_snapshot(const raft_state_t *s, raft_snapshot_t *snap);