feat(config-repl): implement wait_all_with_timeout() for push fan-out

Replaces blocking pthread_join() per thread with a CLOCK_MONOTONIC
deadline + 5ms poll loop. CONF_PUSH_TIMEOUT_MS (500ms) now enforced.
Matches design §6.3 aggregate timeout guarantee.

- Add done field to push_ctx_t; push_thread sets it on exit
- Add static wait_all_with_timeout() before push_config_to_standbys()
- Wire push_config_to_standbys() to use wait_all_with_timeout()
- Add #include <stdatomic.h> to config_repl.c
- Guard push_ctx_t local typedef with #ifndef XCE_TESTING
- Expose push_ctx_t and wait_all_with_timeout_for_test() in header
  under XCE_TESTING for unit test access
- Add test_wait_all_with_timeout_slow_threads (timeout fires, acked=0)
- Add test_wait_all_with_timeout_fast_threads (all complete, acked=3)
- Update design doc §6.3 with actual implementation signature and
  5ms poll interval note
This commit is contained in:
Rongsong Shen 2026-05-21 08:30:43 +00:00
parent 2ed7e34e25
commit f3babec5bc
4 changed files with 224 additions and 16 deletions

View file

@ -850,19 +850,22 @@ that the config may not have reached any standby.
for (int i = 0; i < n_standbys; i++)
pthread_create(&push_threads[i], NULL, push_config_thread, &push_ctx[i]);
/* Single aggregate wait — 500ms total, not 500ms per standby.
* Uses CONF_PUSH_TIMEOUT_MS (default 500ms), distinct from probe_connect_timeout_ms
* used in election probing (§9.2). */
wait_all_with_timeout(push_threads, n_standbys, CONF_PUSH_TIMEOUT_MS);
/* Cancel still-running threads via atomic stop flag (same pattern as probe_thread) */
atomic_store(&push_done, 1);
for (int i = 0; i < n_standbys; i++)
pthread_join(push_threads[i], NULL);
/* Single aggregate wait — 500ms total, not 500ms per standby. */
atomic_int push_done;
atomic_init(&push_done, 0);
int acked = wait_all_with_timeout(threads, ctxs, n,
CONF_PUSH_TIMEOUT_MS, &push_done);
LOG("Config pushed to %d/%d standbys; restarting", acked, n_standbys);
```
Implemented signature (in `config_repl.c`):
```c
static int wait_all_with_timeout(pthread_t *threads, push_ctx_t *ctxs,
int n, uint32_t timeout_ms, atomic_int *done);
```
Uses a 5ms poll loop against `ctx->done` flags until the CLOCK_MONOTONIC deadline, then sets `*done = 1` and joins all started threads. The `done` flag allows thread implementations that check it to exit early; `push_thread` sets `ctx->done = 1` on natural completion, so joins return immediately for threads that finished within the timeout.
Offline standbys recover on their next reconnect via the normal pull mechanism
(§5.2) — their `conf/current` will have the old tag, the new primary will detect
the mismatch and push via `OP_replConfig`.