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:
parent
2ed7e34e25
commit
f3babec5bc
4 changed files with 224 additions and 16 deletions
|
|
@ -5,6 +5,8 @@
|
|||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -482,6 +484,111 @@ static void test_conf_save_config_path_consistency(void)
|
|||
printf("test_conf_save_config_path_consistency: PASS\n");
|
||||
}
|
||||
|
||||
/* ── wait_all_with_timeout (XCE_TESTING shim) ───── */
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
|
||||
/* Thread args for timeout tests */
|
||||
typedef struct {
|
||||
atomic_int *stop; /* poll this; exit when set to allow pthread_join to return */
|
||||
push_ctx_t *ctx;
|
||||
} timeout_test_arg_t;
|
||||
|
||||
/* Polls stop flag at 5ms intervals; exits when stop is set WITHOUT setting acked.
|
||||
* This simulates a synchronous push that respects the done signal. */
|
||||
static void *slow_push_thread(void *arg)
|
||||
{
|
||||
timeout_test_arg_t *a = (timeout_test_arg_t *)arg;
|
||||
while (!atomic_load(a->stop))
|
||||
usleep(5000);
|
||||
/* Thread exits without setting acked — deadline fired before completion */
|
||||
a->ctx->done = 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *fast_push_thread(void *arg)
|
||||
{
|
||||
push_ctx_t *ctx = (push_ctx_t *)arg;
|
||||
ctx->acked = 1;
|
||||
ctx->done = 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_wait_all_with_timeout_slow_threads(void)
|
||||
{
|
||||
/* 2 threads that poll the done flag and exit without acking.
|
||||
* Timeout is 100ms. Expect: acked==0, done==1, returns within ~200ms. */
|
||||
const int N = 2;
|
||||
pthread_t threads[2];
|
||||
push_ctx_t ctxs[2];
|
||||
timeout_test_arg_t args[2];
|
||||
atomic_int done;
|
||||
|
||||
memset(ctxs, 0, sizeof(ctxs));
|
||||
atomic_init(&done, 0);
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
ctxs[i].started = 1;
|
||||
args[i].stop = &done;
|
||||
args[i].ctx = &ctxs[i];
|
||||
pthread_create(&threads[i], NULL, slow_push_thread, &args[i]);
|
||||
}
|
||||
|
||||
struct timespec t0, t1;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t0);
|
||||
int acked = wait_all_with_timeout_for_test(threads, ctxs, N, 100, &done);
|
||||
clock_gettime(CLOCK_MONOTONIC, &t1);
|
||||
|
||||
long elapsed_ms = (t1.tv_sec - t0.tv_sec) * 1000
|
||||
+ (t1.tv_nsec - t0.tv_nsec) / 1000000;
|
||||
|
||||
/* Threads did not ack within the 100ms timeout */
|
||||
TEST_ASSERT_EQUAL_INT(0, acked);
|
||||
/* done flag must be set by wait_all_with_timeout */
|
||||
TEST_ASSERT_EQUAL_INT(1, atomic_load(&done));
|
||||
/* Total elapsed should be close to the 100ms timeout + small join overhead */
|
||||
TEST_ASSERT_LESS_THAN(400, (int)elapsed_ms);
|
||||
|
||||
printf("test_wait_all_with_timeout_slow_threads: PASS (elapsed=%ldms acked=%d)\n",
|
||||
elapsed_ms, acked);
|
||||
}
|
||||
|
||||
static void test_wait_all_with_timeout_fast_threads(void)
|
||||
{
|
||||
/* 3 threads that complete immediately; timeout is 200ms.
|
||||
* Expect: all 3 acked, returns quickly (well under 200ms). */
|
||||
const int N = 3;
|
||||
pthread_t threads[3];
|
||||
push_ctx_t ctxs[3];
|
||||
atomic_int done;
|
||||
|
||||
memset(ctxs, 0, sizeof(ctxs));
|
||||
atomic_init(&done, 0);
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
ctxs[i].started = 1;
|
||||
pthread_create(&threads[i], NULL, fast_push_thread, &ctxs[i]);
|
||||
}
|
||||
|
||||
struct timespec t0, t1;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t0);
|
||||
int acked = wait_all_with_timeout_for_test(threads, ctxs, N, 200, &done);
|
||||
clock_gettime(CLOCK_MONOTONIC, &t1);
|
||||
|
||||
long elapsed_ms = (t1.tv_sec - t0.tv_sec) * 1000
|
||||
+ (t1.tv_nsec - t0.tv_nsec) / 1000000;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(N, acked);
|
||||
TEST_ASSERT_EQUAL_INT(1, atomic_load(&done));
|
||||
/* Should have returned long before the 200ms timeout */
|
||||
TEST_ASSERT_LESS_THAN(200, (int)elapsed_ms);
|
||||
|
||||
printf("test_wait_all_with_timeout_fast_threads: PASS (elapsed=%ldms acked=%d)\n",
|
||||
elapsed_ms, acked);
|
||||
}
|
||||
|
||||
#endif /* XCE_TESTING */
|
||||
|
||||
/* ── dispatch ───────────────────────────────────── */
|
||||
|
||||
void test_config_repl(void)
|
||||
|
|
@ -506,5 +613,7 @@ void test_config_repl(void)
|
|||
RUN_TEST(test_conf_save_config_path_consistency);
|
||||
#ifdef XCE_TESTING
|
||||
RUN_TEST(test_load_primary_config_missing_returns_error);
|
||||
RUN_TEST(test_wait_all_with_timeout_slow_threads);
|
||||
RUN_TEST(test_wait_all_with_timeout_fast_threads);
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue