feat(raft-drive-async): offload requester-less config-push to slow lane (task 3.3)

push_config_transport_only spawns one thread per standby then joins them with a
CONF_PUSH_TIMEOUT_MS bounded wait — blocking the caller. On the requester-less
drive-thread paths (promotion + heartbeat convergence) that wait stalls the
io_worker. Route those through ha_config_push_offloadable, gated by
g_drive_async_configpush_offload (default OFF → inline, byte-for-byte legacy;
ON → slow lane, non-blocking).

Flag ON deep-copies the config bytes + sha into a lane ctx (caller keeps owning
and freeing its own buffer); a single cluster-wide single-flight guard
(ha_state_t.configpush_inflight scalar CAS + lane key=1) bounds it to one
config-push fan-out in flight; the in-flight flag is cleared on every terminal
path (two alloc failures, -EEXIST, hard submit error, lane completion). Send
body factored into ha_config_push_do_send.

Three requester-less drive-thread call sites converted: raft_cb_become_leader
(promotion), the HA_REPL_PUSH_CONFIG heartbeat case, and the direct-CTRL_PRIMARY
FSM promotion branch. The admin-upgrade barrier (push_config_to_standbys, has a
waiting requester) and join.c are untouched.

Lands DORMANT like tasks 3.1/3.2: the lane reads c->ha.standby_endpoints and
writes those borrowed standby endpoints, racing io_worker RDHUP teardown if run
off-thread (bug-1197 family). Flag OFF keeps push+teardown serialized on the
io_worker. Task 3.4 flips this ON together with the snapshot and peerctrl flags
after establishing endpoint-lifetime discipline.

Tests (controller suite EXIT=0): test_configpush_offload_runs_on_lane,
test_configpush_inline_when_flag_off, test_configpush_offload_dedups.
Adversarial cpp-review: no CRITICAL/HIGH/MEDIUM findings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6fDCtXkkEgupu7rUah1y2
This commit is contained in:
Rongsong Shen 2026-06-26 16:05:54 +00:00
parent 6c0523b69b
commit bd02064ebc
5 changed files with 515 additions and 13 deletions

View file

@ -285,6 +285,20 @@ typedef struct {
* snapshot for that peer. Indexed by
* ABSOLUTE candidate index. 0 at
* init (ha_init memset). */
/* ── New field added by raft-drive-async spec (Task 3.3) ──────── */
_Atomic int configpush_inflight; /* CLUSTER-WIDE single-flight guard
* for the offloaded requester-less
* config push. Config push is a
* cluster-wide fan-out (one push thread
* per standby), so at most ONE is in
* flight at a time — hence a scalar, not
* a per-peer array. Set (CAS 0->1) when
* a config push is submitted to the slow
* lane, cleared by the lane worker on
* completion (success or failure). While
* set, the heartbeat decision loop /
* promotion enqueues no duplicate config
* push. 0 at init (ha_init memset). */
} ha_state_t;
/* ── Public API ────────────────────────────────────────────── */
@ -1252,6 +1266,27 @@ int ha_reconnect_offload_enabled(void);
void ha_set_snapshot_offload_for_test(int on);
int ha_snapshot_offload_enabled(void);
/*
* ha_set_configpush_offload_for_test / ha_configpush_offload_enabled:
* TDD kill-switch for raft-drive-async task 3.3 requester-less config-push
* slow-lane offload.
* OFF (0, PRODUCTION DEFAULT): the config push runs INLINE on the caller
* (io_worker/drive) thread — byte-for-byte the legacy path (the
* requester-less push_config_transport_only fan-out + its blocking
* wait_all_with_timeout up to CONF_PUSH_TIMEOUT_MS). The offload lands
* DORMANT because the lane task borrows standby endpoints it does not own
* (bug-1197 family) and reads c->ha.standby_endpoints; moving the push to a
* lane thread while the io_worker may RDHUP-free those endpoints is a
* cross-thread UAF. Flag-ON is therefore only safe once Task 3.4
* establishes endpoint-lifetime discipline, and flipping it ON is owned by
* Task 3.4 (the single gate that flips the snapshot + peerctrl flags too).
* ON (1): the config push runs on a slow-lane worker, off the drive thread.
* NOTE: the admin-upgrade path (push_config_to_standbys, which has a waiting
* requester / barrier) is NOT affected by this flag.
*/
void ha_set_configpush_offload_for_test(int on);
int ha_configpush_offload_enabled(void);
/*
* ha_set_peerctrl_dispatch_for_test / ha_peerctrl_dispatch_enabled:
* Feature-flag accessors (raft-drive-async Task 3.1). The dispatch flag gates