Skip to content

perf: small tail-latency cleanups (log fflush, date lock, epoll timer rounding) - #382

Merged
gg582 merged 3 commits into
devfrom
perf/small-tail-cleanups
Oct 16, 2026
Merged

gg582 merged 3 commits into
devfrom
perf/small-tail-cleanups

Conversation

@gg582

@gg582 gg582 commented Oct 16, 2026

Copy link
Copy Markdown
Member

Summary

Three independent micro-fixes from the tail-latency audit, one commit each (revertible individually):

  1. perf(log) — the sync log fallback (background writer not started) no longer fflushes per line: one stdio-lock + write syscall per logged request drops off the request path. Write granularity stays one line per fwrite; stdio batches delivery (block-buffered on pipes/files, TTY stays line-buffered). cwist_log_async_start() is the documented immediate-delivery path. Access-log middleware unchanged (it never flushed per line).
  2. perf(http) — the cached Date header drops its global mutex: per-thread (second, string) cache, so a second rollover costs each worker one local recompute instead of a cross-worker lock acquisition. g_date_lock had no other user.
  3. perf(reactor) — the epoll branch rounds the timer wait to the nearest ms (1 ms floor) instead of always up, cutting worst-case timer lateness from ~1 ms to ~0.6 ms. Shipped only after verifying early dispatch is impossible: reactor_run_timers() re-checks every deadline against the clock, and the only armed timers (WebRTC conn/SCTP ticks) re-check their own expiry or are harmless when ticked early.

Test plan

  • test_log, test_log_async, test_logger_config, test_access_log, test_http_stringify (Date wire format), test_http, test_http_fairness, test_reactor_wake, test_reactor_timer — default and CWIST_REACTOR_BACKEND=epoll forced, all pass
  • Date header live-verified over curl
  • wrk sanity, 3 interleaved rounds vs a HEAD-built baseline on both simple-server and cwist_bench: all within noise, no regression

gg582 added 3 commits October 16, 2026 22:16
When the opt-in background writer is not started, every log line in the
sync fallback forced a stdio-lock + write syscall with fflush. Keep the
one-line-per-fwrite granularity but let stdio batch lines (block-buffered
on pipes/files, line-buffered on a TTY); the documented interleaving
contract is about write granularity, not delivery latency. Document the
batching in the logger docblock and the middleware guide (en + ko) and
name cwist_log_async_start() as the immediate-delivery path. Access-log
middleware behavior is unchanged (it never flushed per line).
The cached IMF-fixdate string took a global mutex at every 1-second
boundary, so each second rollover contended every worker on one lock
once. Cache the (second, string) pair per thread like the logger's stamp
pattern: on a boundary miss each worker recomputes its own copy once;
concurrent one-time recomputes yield the same value. g_date_lock has no
other user.
The epoll branch always rounded the timer wait UP, letting a timer land
up to ~1 ms late. Round to nearest with a 1 ms minimum (absorbing the
sub-ms remainder instead of every wait) — an early epoll_wait return
cannot dispatch a timer early because reactor_run_timers() re-checks
every deadline against the clock, and the only armed timers (webrtc
conn/SCTP ticks) re-check their own expiry or are harmless when ticked
slightly early. An already-due timer now waits at most 1 ms.
@gg582

gg582 commented Oct 16, 2026

Copy link
Copy Markdown
Member Author

although the comment looks slop it's valid.

@gg582
gg582 merged commit df8a3e3 into dev Oct 16, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant