From 5b8c8fb5bbfc9c43602d29172f37ed17fc5f6802 Mon Sep 17 00:00:00 2001 From: Lee Yunjin Date: Wed, 14 Oct 2026 11:08:40 +0900 Subject: [PATCH] perf(reactor): remove the late-round heuristic (CWIST_REACTOR_LATE_US) The late-round mode from bc358b45 (issue #319 experiment) switched the rest of an io_uring dispatch round into a no-yield, flush-per-callback mode once the round had run past 3 ms. It never showed a tail improvement beyond noise in the same-runner A/B, and the follow-up age-based variant (#375) was neutral too. Drop it: - reactor.c: reactor_late_ns(), cwist_reactor_dispatch_late(), the per-callback clock read and late flush, and the late_rounds / late_callbacks probe counters; - reactor_rx.h: the cwist_reactor_dispatch_late() declaration and stub; - app.c: the HTTP handler yields every yield_batch requests again, with no late bypass; the now unused reactor_rx.h include goes too; - tests/test_reactor_late.c and its Makefile target; - the CWIST_REACTOR_LATE_US rows in docs/runtime-config.md (en, ko); - the "CWIST C1M (late mode off)" benchmark leg. The round-budget flush (CWIST_REACTOR_ROUND_BUDGET_US) and the post-drain chunking (CWIST_REACTOR_DRAIN_CHUNK) are unchanged. Past cwist_c1m_lateoff rows in benchmarks/webserver.json stay as history. --- .github/workflows/bsd-kqueue-benchmarks.yml | 12 +- Makefile | 4 - docs/ko/runtime-config.md | 1 - docs/runtime-config.md | 1 - scripts/ci/benchmark.py | 6 +- src/sys/app/app.c | 13 +- src/sys/io/reactor.c | 68 +-------- src/sys/io/reactor_rx.h | 10 -- tests/test_reactor_late.c | 149 -------------------- 9 files changed, 8 insertions(+), 256 deletions(-) delete mode 100644 tests/test_reactor_late.c diff --git a/.github/workflows/bsd-kqueue-benchmarks.yml b/.github/workflows/bsd-kqueue-benchmarks.yml index 659d4707c..ce8c5bf33 100644 --- a/.github/workflows/bsd-kqueue-benchmarks.yml +++ b/.github/workflows/bsd-kqueue-benchmarks.yml @@ -370,13 +370,6 @@ jobs: # the mechanism this workload can't reach). run_bench "CWIST C1M (drain_chunk=8)" "env CWIST_C1M_MODE=1 CWIST_REACTOR_DRAIN_CHUNK=8 /tmp/cwist_server" 9091 "/tmp/cwist_c1m_drainchunk.txt" "/tmp/cwist_c1m_drainchunk_stat.txt" 10 - # Late-round mode off (issue #319 experiment): "CWIST C1M" above - # runs with the default CWIST_REACTOR_LATE_US=3000, this leg with - # 0, the pre-experiment behavior. Same binary, one env var - a - # same-runner A/B of the tail (p99.99/p99.999), which cross-run - # comparisons on shared runners are too noisy to show. - run_bench "CWIST C1M (late mode off)" "env CWIST_C1M_MODE=1 CWIST_LATENCY_PROBE=1 CWIST_REACTOR_LATE_US=0 /tmp/cwist_server" 9091 "/tmp/cwist_c1m_lateoff.txt" "/tmp/cwist_c1m_lateoff_stat.txt" 10 - # Tuned low-latency profile (CWIST classic only): lower connection # pressure (c100, 4 wrk threads) so the per-request latency floor is # visible alongside the c400 throughput numbers. @@ -573,8 +566,6 @@ jobs: cwist_c1m_drainchunk_wrk = parse_wrk("/tmp/cwist_c1m_drainchunk.txt") cwist_c1m_drainchunk_rss, cwist_c1m_drainchunk_csw = parse_stat("/tmp/cwist_c1m_drainchunk_stat.txt") - cwist_c1m_lateoff_wrk = parse_wrk("/tmp/cwist_c1m_lateoff.txt") - cwist_c1m_lateoff_rss, cwist_c1m_lateoff_csw = parse_stat("/tmp/cwist_c1m_lateoff_stat.txt") cwist_tuned_wrk = parse_wrk("/tmp/cwist_tuned.txt") @@ -607,7 +598,6 @@ jobs: # cwist_async_defer, so parity with cwist_c1m_* here is the # expected, desired result, not a null finding. **server_fields("cwist_c1m_drainchunk", cwist_c1m_drainchunk_wrk, cwist_c1m_drainchunk_rss, cwist_c1m_drainchunk_csw), - **server_fields("cwist_c1m_lateoff", cwist_c1m_lateoff_wrk, cwist_c1m_lateoff_rss, cwist_c1m_lateoff_csw), **server_fields("cwist_tuned", cwist_tuned_wrk), **server_fields("axum", axum_wrk, axum_rss, axum_csw), **server_fields("actix", actix_wrk, actix_rss, actix_csw), @@ -644,7 +634,7 @@ jobs: - name: Copy raw benchmark output run: | mkdir -p raw_logs - cp /tmp/cwist.txt /tmp/cwist_stat.txt /tmp/cwist_c1m.txt /tmp/cwist_c1m_stat.txt /tmp/cwist_c1m_arena1.txt /tmp/cwist_c1m_arena1_stat.txt /tmp/cwist_c1m_drainchunk.txt /tmp/cwist_c1m_drainchunk_stat.txt /tmp/cwist_c1m_lateoff.txt /tmp/cwist_c1m_lateoff_stat.txt /tmp/cwist_tuned.txt /tmp/axum.txt /tmp/axum_tuned.txt /tmp/axum_stat.txt /tmp/actix.txt /tmp/actix_stat.txt /tmp/gin.txt /tmp/gin_stat.txt /tmp/spring.txt /tmp/spring_stat.txt /tmp/spring_tuned.txt /tmp/spring_gc.log raw_logs/ 2>/dev/null || true + cp /tmp/cwist.txt /tmp/cwist_stat.txt /tmp/cwist_c1m.txt /tmp/cwist_c1m_stat.txt /tmp/cwist_c1m_arena1.txt /tmp/cwist_c1m_arena1_stat.txt /tmp/cwist_c1m_drainchunk.txt /tmp/cwist_c1m_drainchunk_stat.txt /tmp/cwist_tuned.txt /tmp/axum.txt /tmp/axum_tuned.txt /tmp/axum_stat.txt /tmp/actix.txt /tmp/actix_stat.txt /tmp/gin.txt /tmp/gin_stat.txt /tmp/spring.txt /tmp/spring_stat.txt /tmp/spring_tuned.txt /tmp/spring_gc.log raw_logs/ 2>/dev/null || true - uses: actions/upload-artifact@v4 with: name: webserver-result diff --git a/Makefile b/Makefile index 7e5a36b17..a7521ce89 100644 --- a/Makefile +++ b/Makefile @@ -896,7 +896,6 @@ TEST_TARGETS = test_worker_affinity \ test_reactor_timer \ test_rx_epoll \ test_reactor_drain_chunk \ - test_reactor_late \ test_latency_probe \ test_sstring \ test_abi_layout \ @@ -1120,9 +1119,6 @@ test_rx_epoll: tests/test_rx_epoll.c src/sys/io/reactor.c src/sys/io/reactor_rx. # full before/after latency measurement. test_reactor_drain_chunk: tests/test_reactor_drain_chunk.c src/sys/io/reactor.c $(CC) $(CFLAGS) -o $@ tests/test_reactor_drain_chunk.c -pthread - -test_reactor_late: tests/test_reactor_late.c src/sys/io/reactor.c - $(CC) $(CFLAGS) -o $@ tests/test_reactor_late.c -pthread ./$@ # Latency probe test (issue #166): run twice per build, with the probe diff --git a/docs/ko/runtime-config.md b/docs/ko/runtime-config.md index 479d42f63..cdbbe43ea 100644 --- a/docs/ko/runtime-config.md +++ b/docs/ko/runtime-config.md @@ -90,7 +90,6 @@ | `CWIST_REACTOR_DRAIN_CHUNK` | tuning | 0 이상 정수 | `8` (`CWIST_PROFILE`이 설정, 없으면 `64`) | 게시된 작업을 비우는 사이에 처리하는 completion 수. [협력적 큐잉](cooperative-queuing.md) 참고. | | `CWIST_REACTOR_ROUND_BUDGET_US` | tuning | 마이크로초, 0 이상 | `2000` | reactor 라운드가 이 시간을 넘기면, 대기 중인 re-arm을 라운드 끝이 아니라 콜백 64개마다 커널에 넘깁니다. 라운드는 끝까지 실행됩니다. `0`이면 한도를 끕니다. | | `CWIST_REACTOR_CQ_GRACE_NS` | tuning | 나노초, 0 이상 | `20000` | 커널에서 잠들기 전에 completion ring을 확인하며 도는 시간. `0`이면 돌지 않습니다. | -| `CWIST_REACTOR_LATE_US` | tuning | 마이크로초, 0 이상 | `3000` | 실험 기능(#319). reactor 라운드가 completion을 수확한 뒤 이 시간을 넘기면, 남은 콜백을 지연 모드로 처리합니다: 콜백마다 re-arm을 바로 커널에 넘기고, 게시된 작업을 콜백마다 비우며, 파이프라인 HTTP/1.1 연결은 양보하지 않고 쌓인 요청을 처리합니다. io_uring 전용. `0`이면 끕니다. | ## 진단 diff --git a/docs/runtime-config.md b/docs/runtime-config.md index 16e4990dc..84b9a704e 100644 --- a/docs/runtime-config.md +++ b/docs/runtime-config.md @@ -90,7 +90,6 @@ Each variable has one status. The status says what v4 promises about it. | `CWIST_REACTOR_DRAIN_CHUNK` | tuning | integer ≥ 0 | `8` (set by `CWIST_PROFILE`; `64` without it) | Completions handled between drains of posted work. See [cooperative queuing](cooperative-queuing.md). | | `CWIST_REACTOR_ROUND_BUDGET_US` | tuning | microseconds ≥ 0 | `2000` | Once a reactor round runs longer than this, parked re-arms are flushed to the kernel every 64 callbacks instead of at the end of the round. The round still runs to completion. `0` disables the bound. | | `CWIST_REACTOR_CQ_GRACE_NS` | tuning | nanoseconds ≥ 0 | `20000` | How long the reactor spins on the completion ring before it sleeps in the kernel. `0` disables the spin. | -| `CWIST_REACTOR_LATE_US` | tuning | microseconds ≥ 0 | `3000` | Experiment (#319). Once a reactor round has run this long since its completions were reaped, the rest of the round runs in late mode: each callback's re-arm is submitted right after it, posted work is drained after each callback, and a pipelined HTTP/1.1 connection serves its buffered requests without yielding. io_uring only. `0` disables. | ## Diagnostics diff --git a/scripts/ci/benchmark.py b/scripts/ci/benchmark.py index 2af89879f..65dc47664 100644 --- a/scripts/ci/benchmark.py +++ b/scripts/ci/benchmark.py @@ -59,9 +59,9 @@ def replace(path: Path, begin: str, end: str, content: str) -> None: # Servers compared in the KDE-style latency distribution chart: only the ones # run under the identical wrk -t12 -c400 profile every CI cycle (the # *_tuned entries use a different, lower-concurrency profile and aren't -# comparable here; cwist_c1m_arena1/cwist_c1m_drainchunk/cwist_c1m_lateoff/ -# cwist_sharded are targeted A/B legs against the plain cwist_c1m row, not -# part of the standing cross-framework comparison set). +# comparable here; cwist_c1m_arena1/cwist_c1m_drainchunk/cwist_sharded are +# targeted A/B legs against the plain cwist_c1m row, not part of the +# standing cross-framework comparison set). _LATENCY_KDE_SERVERS = [ ("CWIST Classic", "cwist", "#22c55e"), ("CWIST", "cwist_c1m", "#10b981"), diff --git a/src/sys/app/app.c b/src/sys/app/app.c index cc359e5d2..ec637ec22 100644 --- a/src/sys/app/app.c +++ b/src/sys/app/app.c @@ -33,7 +33,6 @@ #include "../../net/http/simd_parser.h" #include "../../net/websocket/ws_async_internal.h" /* I/O headers (internal) */ -#include "../io/reactor_rx.h" #include "../io/async_debug.h" /* Core headers */ #include @@ -3313,15 +3312,9 @@ cwist_async_action_t cwist_app_http_handler_async(int client_fd, cwist_http_asyn /* Jeungseung Gaebang Scaling for priority weighting. */ uint32_t priority_weight = ((mixed_seed * 16777619U) >> 8) % 100; - /* One pipeline must not monopolize this reactor's other connections, - * unless this callback runs in a late round (issue #319 experiment): the - * connection has already waited past CWIST_REACTOR_LATE_US, so it serves - * its buffered pipeline now instead of yielding behind the others. */ - const bool late = cwist_reactor_dispatch_late(); + /* One pipeline must not monopolize this reactor's other connections. */ const unsigned int yield_batch = app_http_yield_batch(); - const unsigned int turn_max = - late ? CWIST_HTTP_REQUESTS_PER_TURN_MAX : app_http_requests_per_turn(); - for (unsigned int handled = 0; handled < turn_max; ++handled) { + for (unsigned int handled = 0; handled < app_http_requests_per_turn(); ++handled) { cwist_http_request *req = NULL; cwist_http_parse_error_t perr = CWIST_HTTP_PARSE_OK; cwist_recv_status_t st = cwist_http_receive_request_nb(conn, &req, &perr); @@ -3370,7 +3363,7 @@ cwist_async_action_t cwist_app_http_handler_async(int client_fd, cwist_http_asyn * event, but serving the whole turn inline serializes every other * connection on this reactor. Exit to the continuation path below, * which reposts this connection so it resumes after the ready CQEs. */ - if (!late && (handled + 1) % yield_batch == 0 && conn->len > 0) break; + if ((handled + 1) % yield_batch == 0 && conn->len > 0) break; } if (conn->len > 0) { /* Buffered work cannot wait for another read event. The continuation diff --git a/src/sys/io/reactor.c b/src/sys/io/reactor.c index 3b4308480..fbafc868c 100644 --- a/src/sys/io/reactor.c +++ b/src/sys/io/reactor.c @@ -263,12 +263,6 @@ struct cwist_reactor { /* Owned by the run thread: read only after reactor_on_run_thread(), so * other threads never touch it. */ bool dispatching; - /* True while the run thread dispatches a callback of a round that has - * run past CWIST_REACTOR_LATE_US (issue #319 experiment). Run thread - * only; read through cwist_reactor_dispatch_late(). */ - bool dispatch_late; - uint64_t late_rounds; /* Rounds that crossed the threshold. */ - uint64_t late_callbacks; /* Callbacks dispatched in late mode. */ /* RX-uring completion handler for tagged (low-bit set) RECV SQEs; * registered by the HTTP layer once per worker reactor. */ cwist_rx_cb_t rx_cb; @@ -375,10 +369,6 @@ static uint64_t latency_probe_percentile(const uint64_t *buckets, uint64_t count static void latency_probe_dump(const cwist_reactor_t *reactor) { if (!latency_probe_enabled()) return; - if (reactor->late_rounds) - fprintf(stderr, "[latency-probe] pid=%d late_rounds=%llu late_callbacks=%llu\n", - (int)getpid(), (unsigned long long)reactor->late_rounds, - (unsigned long long)reactor->late_callbacks); for (int phase = 0; phase < 2; phase++) { const uint64_t *b = reactor->probe[phase].buckets; uint64_t count = reactor->probe[phase].count; @@ -1423,39 +1413,6 @@ static uint64_t reactor_round_budget_us(void) { #endif #endif -#ifdef __linux__ -/* Late-round threshold (issue #319 experiment). Once a dispatch round has - * run this long since its completions were reaped, every remaining callback - * of the round is dispatched in late mode: its re-arm (and any parked so - * far) is submitted right after it instead of at round end, foreign-thread - * posts are drained after each callback, and the HTTP layer serves the - * connection's buffered pipeline without yielding. The connections behind a - * slow round have already waited the threshold; late mode stops them from - * waiting for the rest of it too. CWIST_REACTOR_LATE_US overrides the - * 3000 us default; 0 disables. */ -static uint64_t reactor_late_ns(void) { - static _Atomic long cached = -1; - long v = atomic_load_explicit(&cached, memory_order_relaxed); - if (v < 0) { - const char *s = getenv("CWIST_REACTOR_LATE_US"); - if (s && *s) { - char *end = NULL; - long parsed = strtol(s, &end, 10); - v = (end != s && *end == '\0' && parsed >= 0 && parsed < 3600000000L) ? parsed : 3000; - } else { - v = 3000; - } - atomic_store_explicit(&cached, v, memory_order_relaxed); - } - return (uint64_t)v * 1000ull; -} - -bool cwist_reactor_dispatch_late(void) { - const cwist_reactor_t *r = t_run_reactor; - return r && r->dispatching && r->dispatch_late; -} -#endif - void cwist_reactor_run(cwist_reactor_t *reactor) { if (!reactor) return; __atomic_store_n(&reactor->running, true, __ATOMIC_RELEASE); @@ -1464,7 +1421,6 @@ void cwist_reactor_run(cwist_reactor_t *reactor) { #ifdef __linux__ if (!reactor->impl.use_epoll) { const uint64_t cq_grace_ns = reactor_cq_grace_ns(); - const uint64_t late_ns = reactor_late_ns(); while (reactor_running(reactor)) { reactor_drain_posts(reactor); reactor_run_timers(reactor); @@ -1556,7 +1512,7 @@ void cwist_reactor_run(cwist_reactor_t *reactor) { reactor->dispatching = true; const uint64_t round_budget_us = reactor_round_budget_us(); uint64_t round_start_ns = 0; - if (round_budget_us || late_ns) { + if (round_budget_us) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); round_start_ns = (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; @@ -1576,19 +1532,7 @@ void cwist_reactor_run(cwist_reactor_t *reactor) { uint32_t drain_chunk = reactor_drain_chunk(); uint32_t since_drain = 0; uint32_t since_budget_check = 0; - bool round_late = false; while (head != tail) { - if (late_ns && !round_late) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - uint64_t now_ns = - (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; - if (now_ns - round_start_ns >= late_ns) { - round_late = true; - reactor->dispatch_late = true; - reactor->late_rounds++; - } - } struct io_uring_cqe *cqe = &reactor->impl.cqes[head & *reactor->impl.cq_ring_mask]; uint64_t user_data = cqe->user_data; if (user_data & 1u) { @@ -1638,15 +1582,6 @@ void cwist_reactor_run(cwist_reactor_t *reactor) { } } head++; - if (round_late) { - /* Late mode: nothing this connection queued waits for - * the rest of the round. */ - reactor->late_callbacks++; - __atomic_store_n(reactor->impl.cq_head, head, __ATOMIC_RELEASE); - flush_deferred(reactor); - reactor_drain_posts(reactor); - continue; - } if (drain_chunk && ++since_drain >= drain_chunk && head != tail) { since_drain = 0; __atomic_store_n(reactor->impl.cq_head, head, __ATOMIC_RELEASE); @@ -1671,7 +1606,6 @@ void cwist_reactor_run(cwist_reactor_t *reactor) { } } reactor->dispatching = false; - reactor->dispatch_late = false; __atomic_store_n(reactor->impl.cq_head, head, __ATOMIC_RELEASE); queue_deferred(reactor); } diff --git a/src/sys/io/reactor_rx.h b/src/sys/io/reactor_rx.h index 52edf5920..21ac04571 100644 --- a/src/sys/io/reactor_rx.h +++ b/src/sys/io/reactor_rx.h @@ -89,18 +89,8 @@ bool cwist_reactor_recv_arm(cwist_reactor_t *reactor, int fd, void *buf, unsigne * wrapper in cwist_reactor_run. */ void cwist_reactor_probe_record(cwist_reactor_t *reactor, bool queue, uint64_t sample_us); -/* True while the calling thread dispatches a reactor callback in late mode: - * the round it belongs to has run past CWIST_REACTOR_LATE_US (issue #319 - * experiment), so the connection should finish its buffered work now - * instead of yielding. False off the run thread and outside dispatch. */ -bool cwist_reactor_dispatch_late(void); - #else /* !__linux__ */ -static inline bool cwist_reactor_dispatch_late(void) { - return false; -} - static inline bool cwist_rx_uring_env_enabled(void) { return false; } diff --git a/tests/test_reactor_late.c b/tests/test_reactor_late.c deleted file mode 100644 index 5fbb8b5d4..000000000 --- a/tests/test_reactor_late.c +++ /dev/null @@ -1,149 +0,0 @@ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -/* Late-round mode (CWIST_REACTOR_LATE_US, issue #319 experiment): once a - * dispatch round has run past the threshold, the rest of its callbacks see - * cwist_reactor_dispatch_late() == true; below the threshold, with the knob - * at 0, and outside dispatch it is false. - * - * Same technique as test_reactor_drain_chunk.c: include the reactor - * implementation directly, and run each configuration in its own process - * because the knob is cached on first read. - */ -#include -#include -#include -#include -#include -#include -#include -#include "../src/sys/io/reactor.c" - -void *cwist_alloc(size_t size) { - return calloc(1, size); -} -void cwist_free(void *ptr) { - free(ptr); -} -atomic_int g_cwist_running = 1; - -enum { BATCH = 8 }; - -static cwist_reactor_t *loop; -static int pipes[BATCH][2]; -static int handled; -static int late_seen; -static int spin_us_per_cb; - -static void spin_us(int us) { - struct timespec start, now; - clock_gettime(CLOCK_MONOTONIC, &start); - do { - clock_gettime(CLOCK_MONOTONIC, &now); - } while ((now.tv_sec - start.tv_sec) * 1000000L + (now.tv_nsec - start.tv_nsec) / 1000L < us); -} - -static void conn_cb(int fd, void *ctx) { - (void)ctx; - char buf[8]; - ssize_t rc = read(fd, buf, sizeof(buf)); - (void)rc; - if (cwist_reactor_dispatch_late()) late_seen++; - spin_us(spin_us_per_cb); - if (++handled == BATCH) cwist_reactor_stop(loop); -} - -typedef struct { - int late_seen; - unsigned long long late_callbacks; -} result_t; - -/* Returns -1 in late_seen when io_uring is unavailable. */ -static result_t run_batch(int spin_us_each) { - result_t res = {0, 0}; - handled = 0; - late_seen = 0; - spin_us_per_cb = spin_us_each; - assert(!cwist_reactor_dispatch_late()); /* not dispatching */ - loop = cwist_reactor_create(); - assert(loop); - if (loop->impl.use_epoll) { - cwist_reactor_destroy(loop); - res.late_seen = -1; - return res; - } - for (int i = 0; i < BATCH; i++) { - assert(pipe(pipes[i]) == 0); - char b = 'x'; - assert(write(pipes[i][1], &b, 1) == 1); - } - /* Already readable: every POLL_ADD completes before the first wait, so - * all callbacks land in one round. */ - for (int i = 0; i < BATCH; i++) assert(cwist_reactor_add(loop, pipes[i][0], conn_cb, NULL, 0)); - cwist_reactor_run(loop); - assert(handled == BATCH); - assert(!cwist_reactor_dispatch_late()); - res.late_seen = late_seen; - res.late_callbacks = (unsigned long long)loop->late_callbacks; - for (int i = 0; i < BATCH; i++) { - close(pipes[i][0]); - close(pipes[i][1]); - } - cwist_reactor_destroy(loop); - return res; -} - -static result_t run_in_child(const char *late_env, int spin_us_each) { - int fds[2]; - assert(pipe(fds) == 0); - pid_t pid = fork(); - assert(pid >= 0); - if (pid == 0) { - close(fds[0]); - if (late_env) - setenv("CWIST_REACTOR_LATE_US", late_env, 1); - else - unsetenv("CWIST_REACTOR_LATE_US"); - result_t r = run_batch(spin_us_each); - ssize_t w = write(fds[1], &r, sizeof(r)); - (void)w; - close(fds[1]); - _exit(0); - } - close(fds[1]); - result_t r = {-2, 0}; - ssize_t n = read(fds[0], &r, sizeof(r)); - close(fds[0]); - int status; - assert(waitpid(pid, &status, 0) == pid); - assert(WIFEXITED(status) && WEXITSTATUS(status) == 0); - assert(n == (ssize_t)sizeof(r)); - return r; -} - -int main(void) { - /* Default threshold (3 ms): 5 ms callbacks make every callback after the - * first one late. */ - result_t def = run_in_child(NULL, 5000); - if (def.late_seen == -1) { - printf("SKIP: io_uring unavailable on this host\n"); - return 0; - } - printf("default: late callbacks %d/%d (counter %llu)\n", def.late_seen, BATCH, - def.late_callbacks); - assert(def.late_seen == BATCH - 1); - assert(def.late_callbacks == (unsigned long long)(BATCH - 1)); - - /* Fast callbacks never cross the threshold. */ - result_t fast = run_in_child(NULL, 10); - printf("fast: late callbacks %d/%d\n", fast.late_seen, BATCH); - assert(fast.late_seen == 0 && fast.late_callbacks == 0); - - /* 0 disables late mode however long the round runs. */ - result_t off = run_in_child("0", 5000); - printf("off: late callbacks %d/%d\n", off.late_seen, BATCH); - assert(off.late_seen == 0 && off.late_callbacks == 0); - - printf("late-round mode (CWIST_REACTOR_LATE_US) test passed\n"); - return 0; -}