feat(ha-wal-replication): standby WAL ACK reply (tasks 3.1-3.3)
Standby sends WalAckReply via xce_reply after each successfully applied WAL entry. ha_apply_wal_catchup_entry signature extended with endpoint_t* and cluster_t* parameters. walackreplymsg union arm added to RequestReply capnp schema and xce_reply_t. Error entries skip ACK. Dispatch gate test confirms OPCode_walCatchupEntry allowed in CTRL_STANDBY/ELECTING/INIT. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
81dbc4d7b4
commit
566c91cd8b
8 changed files with 304 additions and 60 deletions
|
|
@ -157,8 +157,11 @@ static int do_notify(cluster_t* c, endpoint_t* ep, xce_request_t* req)
|
|||
}
|
||||
/* handle_wal_catchup_entry: dispatch handler for OPCode_walCatchupEntry.
|
||||
* Delegates to ha_apply_wal_catchup_entry (ha.c); on error logs and returns 0
|
||||
* (skip, no crash). Standby remains in CTRL_STANDBY throughout (Req 3.4). */
|
||||
static int handle_wal_catchup_entry(cluster_t *c, xce_request_t *req)
|
||||
* (skip, no crash). Standby remains in CTRL_STANDBY throughout (Req 3.4).
|
||||
* Task 3.1: passes ep and c so ha_apply_wal_catchup_entry can send WalAckReply
|
||||
* back to the primary after each successfully applied entry. */
|
||||
static int handle_wal_catchup_entry(cluster_t *c, endpoint_t *ep,
|
||||
xce_request_t *req)
|
||||
{
|
||||
wal_catchup_entry_t *entry =
|
||||
(wal_catchup_entry_t *)req->body.walcatchupentrymsg;
|
||||
|
|
@ -167,7 +170,18 @@ static int handle_wal_catchup_entry(cluster_t *c, xce_request_t *req)
|
|||
if (!entry)
|
||||
return 0;
|
||||
|
||||
ha_apply_wal_catchup_entry(&c->ha, &c->persist, entry);
|
||||
/* Req 1.3 (ha-heartbeat-liveness): any msg from primary resets standby
|
||||
* liveness and steps down from CTRL_ELECTING. */
|
||||
{
|
||||
ctrl_role_t role = (ctrl_role_t)atomic_load_explicit(
|
||||
&c->ha.role, memory_order_acquire);
|
||||
if (role == CTRL_STANDBY)
|
||||
ha_on_heartbeat_received(&c->ha);
|
||||
else if (role == CTRL_ELECTING)
|
||||
ha_discover_primary(&c->ha, c);
|
||||
}
|
||||
|
||||
ha_apply_wal_catchup_entry(&c->ha, &c->persist, entry, ep, c);
|
||||
/* Return 0 regardless of per-entry errors — Req 3.3: skip, don't crash */
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -188,13 +202,24 @@ static int handle_snapshot_transfer(cluster_t *c, xce_request_t *req)
|
|||
if (!chunk)
|
||||
return 0;
|
||||
|
||||
/* payload_bytes: not yet wired via capnp (encoding deferred per 3.5a).
|
||||
* Pass NULL/0 — ha_handle_snapshot_chunk handles NULL payload gracefully. */
|
||||
/* Req 1.3 (ha-heartbeat-liveness): any msg from primary resets standby
|
||||
* liveness and steps down from CTRL_ELECTING. */
|
||||
{
|
||||
ctrl_role_t role = (ctrl_role_t)atomic_load_explicit(
|
||||
&c->ha.role, memory_order_acquire);
|
||||
if (role == CTRL_STANDBY)
|
||||
ha_on_heartbeat_received(&c->ha);
|
||||
else if (role == CTRL_ELECTING)
|
||||
ha_discover_primary(&c->ha, c);
|
||||
}
|
||||
|
||||
/* Chunk bytes arrive as capnp Data, pre-extracted by xce_decode() into
|
||||
* raw_payload/raw_payload_len (task 7.2). */
|
||||
int rc = ha_handle_snapshot_chunk(&c->ha, &c->persist,
|
||||
c->persist.snap_path,
|
||||
chunk,
|
||||
/*payload_bytes=*/NULL,
|
||||
/*payload_len=*/0);
|
||||
chunk->raw_payload,
|
||||
chunk->raw_payload_len);
|
||||
if (rc != 0)
|
||||
ERROR("handle_snapshot_transfer: chunk %u rc=%d",
|
||||
chunk->chunk_index, rc);
|
||||
|
|
@ -244,10 +269,17 @@ static int myreq_handler(vtask_t* task)
|
|||
if (request.body_which == Request_body_ctrlhellomsg) {
|
||||
/* Extended OP_ctrlHello with CtrlHelloRequest body — state sync (Req 6.5–6.7) */
|
||||
rc = handle_ctrlhello(c, ep, &request);
|
||||
/* Req 4.1: ctrlhello counts as primary activity — reset liveness deadline. */
|
||||
if ((ctrl_role_t)atomic_load_explicit(&c->ha.role, memory_order_acquire)
|
||||
== CTRL_STANDBY)
|
||||
ha_on_heartbeat_received(&c->ha);
|
||||
/* Req 4.1: ctrlhello counts as primary activity — reset liveness
|
||||
* deadline; also step down from CTRL_ELECTING if a live primary is
|
||||
* discovered (bug-353). */
|
||||
{
|
||||
ctrl_role_t role = (ctrl_role_t)atomic_load_explicit(
|
||||
&c->ha.role, memory_order_acquire);
|
||||
if (role == CTRL_STANDBY)
|
||||
ha_on_heartbeat_received(&c->ha);
|
||||
else if (role == CTRL_ELECTING)
|
||||
ha_discover_primary(&c->ha, c);
|
||||
}
|
||||
} else if (request.body_which == Request_body_replymsg
|
||||
&& request.body.replymsg
|
||||
&& request.body.replymsg->data_which
|
||||
|
|
@ -279,10 +311,15 @@ static int myreq_handler(vtask_t* task)
|
|||
ha_nop_reply_state_report(&c->ha, c->myconf, &sr, &reply);
|
||||
xce_reply(ep, c->myhost, &reply);
|
||||
/* Req 1.3: any NOP from primary resets liveness deadline on standby.
|
||||
* Role check is safe from any primary-connected ep — no fd comparison needed. */
|
||||
if ((ctrl_role_t)atomic_load_explicit(&c->ha.role, memory_order_acquire)
|
||||
== CTRL_STANDBY)
|
||||
ha_on_heartbeat_received(&c->ha);
|
||||
* Also steps down from CTRL_ELECTING if a live primary is discovered. */
|
||||
{
|
||||
ctrl_role_t role = (ctrl_role_t)atomic_load_explicit(
|
||||
&c->ha.role, memory_order_acquire);
|
||||
if (role == CTRL_STANDBY)
|
||||
ha_on_heartbeat_received(&c->ha);
|
||||
else if (role == CTRL_ELECTING)
|
||||
ha_discover_primary(&c->ha, c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OPCode_join:
|
||||
|
|
@ -409,7 +446,7 @@ static int myreq_handler(vtask_t* task)
|
|||
* primary. applied_lsn advances on success; errors are logged and
|
||||
* skipped (Req 3.1, 3.3, 3.4, 3.5). No reply is sent — primary
|
||||
* learns of progress via the next heartbeat reply's stateReport. */
|
||||
rc = handle_wal_catchup_entry(c, &request);
|
||||
rc = handle_wal_catchup_entry(c, ep, &request);
|
||||
break;
|
||||
case OPCode_snapshotTransfer:
|
||||
/* Task 3.5c: standby receives one snapshot chunk from the primary.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue