Skip to content

Releases: c4punks/CWIST

CWIST v3.9.1

Choose a tag to compare

@gg582 gg582 released this 10 Oct 19:16

v3.9.1 is a bug-fix release on top of v3.9. It fixes use-after-free crashes and stalled connections in the HTTPS pool, removes the HTTPS task queue's fixed ~96 MiB memory cost per process, and fixes the data races and static-analysis findings that turned up while adding three new CI gates: ThreadSanitizer, an RSS gate, and Cppcheck. The public API is unchanged from v3.9.

Thanks to Dan Kegel, who pointed out that the HTTPS pool queue was sized for over two million pending connections. That is far beyond any realistic file-descriptor limit, and the memory for it was paid up front in every process. Fix 1 below came out of that report.

Fixes

  1. The HTTPS task queue grows on demand instead of reserving ~96 MiB per process (e756831)

    • The pool queue was a static ring of 2,097,152 tasks (48 bytes each, ~96 MiB). https_pool_init() zeroes the whole pool, so every process that starts the pool kept all of it resident before its first connection. With CWIST_C1M_MODE=1 that is every worker plus the master.
    • The queue now starts empty, takes 1,024 slots on first use, and doubles when a burst fills it, up to the old limit. At the limit, or if growing fails, submitters wait for a free slot as before.
    • Measured on a Ryzen 5600X: https_pool_init() alone adds +176 KiB of RSS instead of +98,484 KiB. Idle RSS of a 4-worker HTTPS server drops from ~422 MB to ~29 MB. Keep-alive, connection-churn and 3,000-connection burst results are unchanged, and a 6,000-connection burst on one worker grows the queue to 4,096 slots and completes with no failures. Details are in docs/performance/https-pool-dynamic-queue.md; the regression test is test_https_pool_queue (01e1cb3).
  2. Use-after-free on deferred HTTPS connections (d861c7c, #344)

    • After a handler deferred a response with cwist_async_defer(), the pool worker still read conn->deferred once the handler returned. By then the completion owned the connection. It might already have closed it (use-after-free), or reset the flag and requeued it for keep-alive, in which case the worker closed a queued connection and the next worker used freed memory.
    • If the completion arrives before the dispatch ack, for example a response sent right after deferring, the bug triggered every time, not just in a rare race. In production it crashed forked workers in SSL_shutdown, cwist_http_header_get and https_connection_teardown, and clients saw the dropped connections as timeouts.
  3. Idle TLS connections no longer starve the pool when full GC is on (0c2b76d, #344)

    • With cwist_full_gc(true), idle connections were never parked. Each idle keep-alive client held a pool thread for the whole idle budget: 30 s for HTTP/1.1, 300 s for HTTP/2. On small hosts a few idle browsers were enough to make new connections time out.
    • Idle HTTP/1.1 connections and idle HTTP/2 sessions are now parked under full GC as well. Before parking, an h2 session releases its allocations from the parking thread's GC tracking, so it can resume on another pool thread.
    • Reproduced with one pool thread and 8 idle clients: new connections went from timing out after 15 s to answering in 2-7 ms. Covered by test_https_park_full_gc.
  4. HTTP/2 connections with a deferred stream were never closed (2c8a4f7)

    • When a session ended in the same pool-thread run that had deferred a stream, the connection's fd, TLS session and state leaked. Covered by test_http2_conn_closed_after_deferred_stream.
  5. Full-GC connection registry: closing on another thread left a stale entry (bb52192, #344)

    • cwist_conn_registry_untrack() searched only the calling thread's list, but HTTPS connections are usually closed on a different thread than the one that accepted them. The freed handle stayed registered, and a later sweep closed it a second time. Untrack now searches every thread's list.
  6. Async completion before the dispatch ack: deadlock, then a race and a lost completion (fd8bbe5, dde0dfe)

    • A completion called from another thread before the dispatching thread acknowledged the deferral used to busy-wait for that ack inside the claim, which could deadlock. fd8bbe5 removed the wait.
    • Without it, the early completion rewrote the response while the ack was still reading it. On top of that, the "who sends" handoff (finish_on_ack) was unsynchronized, so a response could be lost and the request would hang.
    • Each exchange now has a short mutex. The producer's mutation and the ack's bookkeeping are serialized, and whichever side ends up owning the send does it after unlocking. Neither side waits for the other while holding the lock.
  7. HTTPS park restarted during pool shutdown (3a5cc7f)

    • A pool worker that parked a connection after https_park_stop() started a second park thread, so the join in https_pool_destroy() never returned. Parking now stays closed until the next https_pool_init(). The park thread also reads epoll_fd under its lock.
  8. Other data races found by ThreadSanitizer (39188fe, a9e7bc4)

    • The reactor's run-thread check no longer reads a pthread_t field that the run thread writes. The io_uring SQ-to-CQ handoff is annotated for TSan; this generates no code outside TSan builds.
    • cwist_scheduler_t.running and NukeDB's sync flag (previously volatile) are now atomic.
    • The listen and UDP fd globals, which cwist_shutdown_request() claims from signal handlers, are now always accessed atomically.
  9. Static-analysis fixes (834aa2b, 5dc5c76)

    • make_error() returned a cwist_error_t whose payload union was uninitialized; it is now zeroed (Abhijeet Sharma, d3db20f on dev).
    • cwist_orm_query(), cwist_orm_insert_returning_json() and cwist_orm_select_one_json() dereferenced a NULL out-pointer before checking it, so the documented CWIST_ERROR_INVALID_PARAM path crashed instead of returning.
    • mux_chain_next() dereferenced a NULL middleware state.
    • The protobuf ZigZag encoder relied on an implementation-defined signed shift.
    • A format string in the latency probe used the wrong type.
  10. Build fixes

  • Emscripten/WASI: mmap and mprotect in gc.c are guarded (b121fd1), and gc.c is part of the WASI 0.2 sources so cwist_full_gc_enabled links (38a115c).
  • Zig bindings: link the system libstdc++ and libgcc_s on Linux (750ed1e, 43d2418), and restore the system <stdatomic.h> prelude in cwist.h (865eb24).

New CI gates (f5dbe54)

  • TSan (tsan.yml): make SANITIZE=thread tsan-test runs every test target except TSAN_EXCLUDE, and each exclusion records its reason in the Makefile. The excluded tests' open reports are tracked in #351.
  • RSS gate (rss-gate.yml, scripts/ci/rss_gate.sh): an HTTPS server with 4 workers on cwist_app_listen().
    • Every idle process must stay under 32 MiB.
    • Over a second, identical round of keep-alive, churn, burst and large-response load, RSS may grow by at most max(8 MiB, 10%).
    • Checked locally: it fails on v3.9 (an idle process at 112 MiB) and passes on v3.9.1 (14.5 MiB, 464 KiB of growth).
  • Cppcheck (cppcheck.yml): warning, performance and portability checks over src/ with cppcheck 2.13. Any finding fails the gate. The tree starts at zero findings.

Also on main since v3.9

These are not library changes:

  • Zig 0.17.0 bindings (bindings/zig, example/zig-hello), with middleware and async responses (866afe6, a6bb445).
  • libttak bumped to v3.4.0 (50eb774).
  • Actix-web added to the CI web server benchmark (942780a).
  • Documentation: cookie API page, OpenAPI/Swagger UI guide, example READMEs, the v4.0 soak test and public API baseline, and the AI assistance policy in CONTRIBUTING.

Files that a history replay had brought back onto main (committed test binaries, a machine-specific .build-flags, and unbuilt modules) were removed before this release (04840a6, 9887bb9). None of them are in the tarball. A duplicated test_gc_job_handoff target left by that cleanup is wired once (4e58368).

CWIST v3.9

Choose a tag to compare

@gg582 gg582 released this 05 Oct 13:42

CWIST v3.9: TLS observability, HTTPS performance gates, and WebRTC DataChannel

v3.9 adds TLS observability and CI performance gates so HTTPS regressions show up before they ship, and adds WebRTC DataChannel support. It is also the first release cut on main since v3.7.2, so main now includes v3.8's changes too: Rust FFI Phase 2, supported opt-in full-GC, and the features promoted to supported (see the v3.8 notes). Long-running experimental dev work, such as the native WebTransport client and its lsquic pin, is still not included.

Major changes compared to v3.8:

  1. HTTPS handshake latency fix (#306, #307)

    • The ~640 handshakes/s plateau was a Nagle/delayed-ACK stall, not a crypto or shard limit. The kernel drops TCP_QUICKACK during the TLS handshake. A client without TCP_NODELAY then holds its first request until the server's delayed ACK, about 40 ms later.
    • CWIST now re-arms TCP_QUICKACK around the handshake (9eea519). Measured on a Ryzen 5600X: request RTT for such clients 43 ms → 0.08 ms; HTTPS connection churn ~680/s → ~1,650/s.
  2. TLS observability in /metrics (5fa7b13)

    • New counters: cwist_tls_handshakes_total, cwist_tls_handshakes_resumed_total, cwist_tls_connections_active, cwist_tls_handshakes_tls12_total, cwist_tls_handshakes_tls13_total, and cwist_tls_ciphers_{aes128_gcm,aes256_gcm,chacha20,other}_total. Covered by test_https_metrics.
    • The counters are per process. With prefork workers, each worker's /metrics reports only its own connections.
  3. HTTPS performance gates in CI (dbac164, 2a190f4, 73ffa4f, 0c9ccac)

    • Perf — HTTPS gates measures TLS RTT, connection churn, keep-alive throughput and 1 MiB transfer over HTTPS, with plaintext controls.
    • Gates use absolute backstops, HTTPS/HTTP ratios measured in the same run, and a comparison against earlier runs on the same runner CPU. History is kept in benchmarks/https_gates.json.
    • Checked against the real regression: with the #307 fix disabled, the RTT gate fails (0.08 → 43 ms) on any CPU.
  4. WebRTC DataChannel (0026a48, 714acdf)

    • New cwist/net/webrtc.h: SDP offer/answer, ICE-lite, DTLS 1.2 (vendored BoringSSL), SCTP DataChannels via the new lib/usrsctp submodule, and DCEP. DataChannel only, no media.
    • Runs on the cwist reactor:
      • cwist_webrtc_ctx_new() creates its own reactor and thread; cwist_webrtc_ctx_new_on() attaches to a reactor you already run.
      • Every callback runs on the reactor thread. send, close, handle_offer and ctx_free can be called from any thread.
      • Connections are reference counted (cwist_webrtc_conn_retain/release), and a close handler reports when one goes away.
      • Text and binary messages are told apart (cwist_webrtc_data_type), and empty messages are supported.
      • Messages are capped at 256 KiB, and at most 4 MiB can be queued per connection (cwist_webrtc_conn_buffered_amount).
    • Interop: verified against headless Chromium with make test_webrtc_browser. Text, a 200,000-byte binary message and an empty string all round-trip. The channel opens in about 8 ms.
    • Loopback numbers (make bench_webrtc): about 970k msg/s at 64 B, about 184 MB/s at 64 KiB, about 11 wakeups/s when idle.
    • example/webrtc/: an echo server with HTTP signaling, one ctx per HTTP worker process.
    • Compiled in by default. Build with CWIST_WEBRTC=0 to leave it out.
  5. Reactor one-shot timers (b08df73)

    • New public API: cwist_reactor_timer_init/arm/cancel/armed. Timers are kept in a min-heap per reactor and fire on its run thread.
    • The io_uring, epoll and kqueue waits are shortened to the next deadline, so a reactor with no armed timer wakes no more often than before.
    • cwist_reactor_stop() now writes the run flag atomically, since it is called from other threads.
  6. Fixes

    • Rust: App::use_builtin_middleware accepts only CWIST's own middleware (8f4b1cb); rust-hello is built and served in CI (de8bc92).
    • cwist_app_listen() restores the caller's SIGTERM/SIGINT handlers when it returns (fd94139).
    • make install: cwist.pc now lists libusrsctp when WebRTC is built, so programs using the WebRTC API link through pkg-config (41f7e9a).
    • Allocator use in the WebRTC module goes through cwist_alloc/cwist_free (5db07b2, bf545c6).
  7. Dependencies and docs

    • libttak pinned to v3.3.1 (4f2eae8).
    • Doxygen comments for every function in src/ that lacked one (bb4389c, 42062b2, 27b70b1). API reference page for sse.h (b2e269a, #271).
    • #294 closed: cwist_app_listen() does use the sharded handshake shepherds (measured), and v3.8's default of at least 4 shards (cap 16) needs no change.

Known limitations / deferred:

  • WebRTC:
    • ICE-lite only: no STUN/TURN servers, no trickle ICE, IPv4 host candidates only.
    • The peer certificate fingerprint is not yet checked against the SDP.
    • Ordered, reliable channels only, with no per-channel close.
    • Firefox has not been tested.
  • TLS counters are not yet summed across prefork workers.
  • WebTransport stays experimental until v4.1. The lsquic re-pin and the HTTP/3 connection-close fixes are still waiting on upstream lsquic.

CWIST v3.7.2

Choose a tag to compare

@gg582 gg582 released this 30 Sep 01:14

Cut from main at 92a0866d, with every CI workflow green on that commit and the source archive built and tested clean without Git metadata.

This release is not bug-fix-only. Besides the fixes below it carries the experimental Rust bindings step from #287. Treat the Rust API as alpha: it may change before v4.0.

Included experimental features (alpha)

  • Rust App::listen and cwist::shutdown (issue #36, PR #287, community contribution): serving a Rust app on a port with graceful shutdown. Adds three small, additive C lifecycle hooks: cwist_app_listen_ex() (explicit worker/server-mode overrides), cwist_http_pool_init_mode(), and the public cwist_shutdown_request(). Existing cwist_app_listen() behaviour is unchanged.

Fixes and improvements

1. TLS handshake shepherd was O(n) per event: 35% of handshakes failed at 20k connects/s

perf(https): keep the handshake shepherd's pending list O(1) per event (PR #289)

The shepherd kept pending handshakes in a singly linked list: every epoll event scanned the list to unlink an entry, and every wait round walked it all for expiry. Under a connect burst a shard held tens of thousands of entries and queued handshakes ran out their 45 s budget. The list is now doubly linked (O(1) unlink) and kept in deadline order (expiry sweep stops at the first live entry). Measured at 1,000,000 connections opened at 20,000/s: served 538,051 -> 1,000,000, handshake failures 351,821 -> 0.

2. Every worker's first request was ~10 ms late

perf(app): prime libttak TSC calibration before forking workers (PR #290)

libttak's TSC calibration (nanosleep(10ms)) ran lazily inside the first request path of every forked worker. The supervisor now primes it once before the fork loop; children inherit the calibrated value copy-on-write. Measured with 12 workers: slow first requests 11 of 24 -> 0 of 24.

3. Churned TLS connections closed with RST instead of FIN

fix(https): drain the receive queue before closing TLS connections (PR #291)

https_connection_teardown() closed right after SSL_shutdown(), but the peer's close_notify (or a pipelined request) routinely sat unread in the receive queue, so the kernel answered with RST — flushing response bytes the client had not read. The teardown now drains the queue (bounded, non-blocking) before close(). Measured over ~35k churned connections: TCPAbortOnData 24,256 -> 800 (the remainder are client-side aborts a server cannot prevent).

4. Same RST teardown on the async plain-HTTP path

fix(http): drain the receive queue in cwist_http_async_close (PR #292)

Mirrors the TLS drain for the C1M plain-HTTP close helper.

5. Deferred responses: flush failure handed a dangling connection to the completion (use-after-free)

fix(app): hand deferred flush-failure teardown to the async completion

When a handler deferred via cwist_async_defer and earlier responses in the same turn left bytes in the coalesce stash, a flush failure made the batch loop close the connection — but req/res ownership had already moved to the cwist_async, which later wrote through the freed conn shell and possibly-reused fd (use-after-free, double release, response bytes landing on an unrelated connection). The stash now drains before the defer is acked, and on failure the teardown goes through cwist_async_abort() so the completion path owns the close exactly once.

6. Idle keep-alive connections held 32 KB each

perf(http): shrink idle keep-alive stashes to a 4 KiB floor

A served keep-alive connection kept its 16 KiB receive stash plus 16 KiB coalesce buffer for its whole idle lifetime (~24 KB RSS per connection; 1M idle connections measured at 22.9 GiB). At rearm both stashes now shrink to a 4 KiB floor; grow-on-demand restores capacity on the next busy turn, so no per-request alloc/free churn is reintroduced.

7. Shutdown always sat out a fixed 5-second drain, dead time

fix(shutdown): exit the connection drain early when nothing is left

The post-stop drain was an unconditional sleep(5) placed after the reactor and pool were already torn down. It now polls the live-connection counter in 100 ms slices and exits as soon as nothing remains, and honors a new CWIST_DRAIN_TIMEOUT env override (0..3600 s, default unchanged at 5).

8. Slowloris: classic-pool header read had no deadline

fix(http): bound the classic-pool header read

cwist_http_receive_request() recv()d the header block on a blocking socket with no timeout, so a client dribbling the header byte-by-byte held a pool thread indefinitely. The read is now bounded by the same total budget the TLS path enforces (CWIST_HTTP_HEADERS_TIMEOUT_MS, default 120 s).

9. Idle reaper closed keep-alive sockets with RST

fix(http): drain before idle-reaper close

The keep-alive idle reaper closed the fd bare; a request pipelined into the post-timeout, pre-dispatch window was discarded with an RST. It now drains through the same graceful-teardown path as every other close.

10. WASI 0.2 smoke link broke

fix(wasi): restore the server-subsystem stubs dropped from compat.c

The deferred-teardown fix (5) made async.c's completion path survive link-time garbage collection on the WASI target, exposing references the WASM stubs no longer covered. Restored the full stub set; make wasip2-smoke passes again.

11. Latency-probe watchdog gave no diagnostics on a rare stall

test(reactor): make the latency-probe watchdog report where it stopped

A rare test_latency_probe stall in CI killed the process with a bare "Alarm clock". A SIGALRM handler now prints how far the test got before exiting with the same code, distinguishing a lost completion from a loaded runner.

API note (breaking, internal surface)

cwist_http_async_close was removed from the public header and is now an internal, engine-owned teardown. It had no callers outside the framework; handlers still signal teardown by returning CWIST_ASYNC_CLOSE, and deferred completions already own rearm/close. This closes the arbitrary-thread-close fd-reuse race the public symbol invited.

New knobs

  • CWIST_DRAIN_TIMEOUT: shutdown drain upper bound in seconds (default 5).
  • Existing TLS knobs unchanged: CWIST_HTTPS_IDLE_TIMEOUT_MS, CWIST_HTTPS_PARK=0, CWIST_HTTPS_HANDSHAKE_TIMEOUT_MS.

Tests

New pool-path coverage for the v3.7.1 TLS changes (PR #288, test_https_park): idle TLS parking (HTTP/1.1 and HTTP/2, including expiry), HTTP/2 deferred responses over the pool, and content-length after compression.

CWIST v3.7.1

Choose a tag to compare

@gg582 gg582 released this 29 Sep 10:26

Emergency patch for v3.7, cut from main at 07d042f8.

This release is not bug-fix-only. It carries everything merged to main since v3.7: the serious bug fixes below, and also new, still-experimental features. Treat the features as alpha: their APIs may change before v4.0.

Included experimental features (alpha)

  • Rust bindings (issue #36): cwist-sys, which holds raw FFI bindings generated by bindgen, with a struct-layout contract test. Also a safe cwist crate on top of it. These are not published to crates.io.
  • FFI out-of-line wrappers: exported wrappers for public static inline helpers, so bindings can call them.
  • Per-route user context: the cwist_app_*_ex() registration functions pass a context pointer to the handler.
  • cwist_err.h legacy enum kept in sync with the canonical header, needed by the bindings.

Fixes

1. HTTP/3 server crash (SIGSEGV) on the second connection and at shutdown

fix(build): rebuild liblsquic when the lsquic submodule pin moves

A build tree that had built liblsquic.a before the 2026-09-21 lsquic pin change kept linking that old archive against the new headers. struct lsquic_stream_if differs between the two revisions, so lsquic called callbacks through the wrong offsets and crashed when a client closed a connection. The archive is now built through a stamp keyed on the submodule commit. Fresh checkouts and release tarballs were not affected.

2. TLS connections starved: only a few dozen served at once

perf(https): park idle TLS connections instead of holding a pool thread, fix(https): use cwist_alloc/cwist_free in https_park

A pool thread waited in poll() on each idle TLS connection (30 s for HTTP/1.1 keep-alive, up to the HTTP/2 idle timeout). With the default C1M settings this capped concurrently served TLS connections at the number of HTTPS pool threads: 25 out of 395,729 held in a 1M-connection run. Idle TLS connections are now parked in a per-process epoll set and handed back to the pool when bytes arrive. HTTP/2 session state moves into the connection while it is parked. Measured through fly.board (12 workers): 1,000,000 / 1,000,000 held and served over TLS HTTP/1.1 and over TLS HTTP/2.

3. HTTP/2 deferred responses (cwist_async_defer) lost, stuck or freed twice

Browsers showed a blank page when the handler deferred its response over HTTP/2.

  • fix(h2): bind https_conn and client_fd to req and avoid double-free in async drain: HTTP/2 requests did not carry their connection, so the deferred-completion path could not route the response.
  • fix(http2): do not prematurely destroy deferred req/res in h2_async_drain: the request and response were freed before the queued completion was sent.
  • fix(http2): prioritize async queue wake event in h2_wait_readable: pending completions are sent before the connection blocks on the socket again.
  • fix(http2): flush buffered frames at end of h2_send_response_hc: a response could sit in the batch buffer until the next frame arrived.

4. HTTP/2 content-length could disagree with the DATA payload (RFC 9113 §8.1.1)

fix(http2): enforce RFC 9113 content-length alignment with actual frame payload

A Content-Length set by the handler was forwarded as is, even when the body sent differed (for example after compression middleware ran). Strict clients treat that as a malformed response. content-length is now always derived from the body actually sent.

5. HTTP client kept intermediate redirect headers

fix(http_client): keep only the final response's headers when following redirects

New knobs (fix 2)

  • CWIST_HTTPS_IDLE_TIMEOUT_MS: idle budget for parked HTTP/1.1 TLS connections (default 30000, the previous blocking-read timeout).
  • CWIST_HTTPS_PARK=0: disable parking and fall back to the previous behaviour.
  • Parking is off automatically when full GC is enabled. Rebuild code that includes <cwist/net/http/https.h>.

Other changes

  • README: the reactor and classic C10K–C1M tables were re-measured. Every held reactor connection costs about 24 KB; the older "a million connections in a quarter gigabyte" claim was removed. A TLS-at-C1M section was added.
  • compile_commands.json was regenerated with repository-relative paths.
  • CI on main aligned with dev: workflows, the browser/WASM component harness, the tutorials-check and component-browser-test targets, and strict durable-queue backend checks.
  • Release rules in CONTRIBUTING.md: patch releases are cut from main, and the notes list everything they carry.
  • test_inline_exports links with -rdynamic, so the wrapper-symbol check also passes under GCC 14 LTO.

CWIST v3.7

Choose a tag to compare

@gg582 gg582 released this 24 Sep 12:52

CWIST v3.7 — the last experimental train before v4

v3.7 is the release that implements 90% of planned feature of long-term roadmaps. where new or experimental capability may land. Everything rides here behind flags or marked experimental so v4.0 can freeze features and focus on stabilization. Major changes compared to v3.6:

  1. WASM edge deployment (issue #201)

    • Promote WASI 0.2 (wasm32-wasip2) from experimental to supported: CI gate, wasi:sockets smoke test, and docs/api/wasi.md reframed as reference documentation.
    • example/wasip2-kv/: edge persistence demo that round-trips a cwist_db blob through a preopened host directory and survives a wasmtime restart.
    • docs/deployment/wasmtime-appliance.md: production appliance guide with isolated state, restart verification, backup, and rollback.
    • Streaming producer API: cwist_http_response_stream_begin/write/end delivers chunked response bodies chunk-by-chunk from handlers.
  2. WASI component pipeline experiment (issue #203 / PR #204)

    • WIT-world validation in CI, jco guests over preview2-shim and preview3-shim under JSPI, browser-bundle packaging gate, and an async host.send-chunk streaming world.
    • The Emscripten bundle swap itself remains gated on WASI 0.3 stabilization and unflagged JSPI — a v4.0 decision.
    • Retire the WASI preview1 target (wasi-smoke).
  3. HTTP/HTTPS hot-path improvements (issue #237)

    • Wave A: request-string and static-header borrowing, route-lookup residue removal, and response serialization shortcuts.
    • Wave B: TLS record coalescing and non-blocking async file streams (Linux-only burst path).
    • Wave C: serializer fast path, static-cache residue, and route-lookup residue.
    • C1M-on + drain-chunk 8 is now the default deployment baseline (45b07f5).
  4. Reactor hardening for C1M tail latency (issue #166)

    • Grace-peek the CQ ring before committing to the kernel wait (62cfb06).
    • Grow the slot pool without holding the pool lock (#258, af30263).
    • Serialize SQ-consuming enters to stop tail-behind-head wedge (#259, 26bc314).
    • Flush parked re-arms when a dispatch round runs long (#260, c49345c).
    • Serve h2c connections on a per-connection thread, off the reactor (b0dec33).
    • Per-event latency probe remains env-gated (CWIST_LATENCY_PROBE=1); the HTTP batch-shed counter stays always-on.
  5. Full-stack framework primitives (issue #94)

    • Reusable HTML component render units (Phase 1), component-scoped CSS class names (Phase 2), HTML-over-the-wire page/fragment responses (Phase 3), content-hashed in-memory assets (Phase 4), and CSS minification/bundling (Phase 4).
    • Component-rendered fragment view in the WASM service worker example (Phase 5).
    • append_ok() CSS composer refactor with documented trust boundary.
  6. Full-GC tracking overhead fix (issue #65)

    • Replace the linear pending-sweep scan with O(1) hash-set lookups and a thread-local fast path (46ab2ec).
    • Add live-set-scaled benchmark (test_full_gc_tracking) and refresh docs/GC.md measurements (8d01489, 0cdc33b).
  7. HTTP correctness and hardening

    • Reject CR/LF in cwist_http_header_add and handle header string allocation failure.
    • Send the full body in cwist_send_error_response.
    • Check sstring and header results on the right error channel in SSE, cookies, WebTransport, and Redis paths.
    • Reject a chunked request when appending a chunk fails.
    • Constant-time HMAC comparison in session signature verification.
  8. Experimental ecosystem support (shipped behind flags)

    • GraphQL subscriptions over the non-blocking WebSocket transport (cwist/graphql_ws.h, topic broker, test_graphql_subscriptions).
    • Durable Redis/NATS job queue backends (cwist/sys/job/durable_queue.h).
  9. QUIC/WebTransport slipped to v3.8

    • WebTransport on the stable line and HTTP/3 connection-close correctness are blocked on upstream lsquic merges, so they move to the v3.8 queue rather than pinning to a topic branch.
  10. Also in this release

    • CWIST_PROFILE preset env var with performance / lowmem / lowlat / default overlays (issue #171).
    • Tutorial API corrections across all 30 tutorials; template engine, routing, hello-world, and CSS-composer examples rewritten against the current API.
    • Rate-limiter fail-open when the IP bucket table is exhausted; sstring rtrim returns OKAY for empty strings; db_sync rejects oversized blobs; template nesting-depth tracking and empty apply_filter trim guard.
    • Benchmark comments now describe mechanisms instead of pasting run numbers.

Notes:

  • lib/lsquic stays pinned to upstream master (2b30189), the same stable pin as v3.6.
  • Source tarball builds like v3.6: vendored dependencies included; make at the top level produces libcwist.a, the CLI, and cwist.pc.
  • Homebrew formula in this release line: packaging/homebrew/cwist.rb (tap: c4punks/homebrew-cwist).

CWIST v3.6

Choose a tag to compare

@gg582 gg582 released this 21 Sep 02:35

CWIST v3.6 — WASM client-side support: from in-tree target to usable from JavaScript

Major changes compared to v3.5:

  1. WASM correctness wave (issue #93 Phase 1, #176)

    • cwist_db (SQLite) compiles into libcwist_wasm.a: cwist_db_open_memory() / cwist_db_serialize() work under Emscripten.
    • EM_JS clang-format corruption fixed and guarded so it cannot recur.
    • Emscripten build + smoke test is a CI gate (.github/workflows/wasm.yml).
    • docs/api/wasm.md: scope, the dispatch_memory pattern, TypedArray helpers, the db round trip, session caveats.
  2. JavaScript consumption (issue #93 Phase 2, #185)

    • cwist-wasm npm package (wasm/npm/): fetch-style API over the WASM dispatch path, index.d.ts, README; make wasm-dist builds the tarball and CI verifies a clean install.
    • First-party JS wrapper (CWIST_WASM_DEFINE_ENTRY) so consumers do not hand-roll Emscripten glue.
  3. Streaming, sessions, and WASI (issue #93 Phase 3, #195)

    • Boundary streaming: cwist_app_dispatch_stream + cwist_stream_req_begin/feed/end; the entry macro exports _cwist_wasm_dispatch_stream pumping Module.cwistStreamChunk.
    • Session persistence model: pin the signing secret, let the signed client-side cookie carry the state; sessions actually link under WASM via a bundled header-only SHA-256/HMAC.
    • WASI targets land in-tree (experimental, see docs/api/wasi.md): preview1 runs the in-memory dispatch surface under wasmtime; WASI 0.2 binds real sockets and serves cleartext HTTP through wasi:sockets.
  4. End-to-end example (issue #93 Phase 4, #198)

    • example/wasm-service-worker/: routing + zod validation + template rendering + cwist_db + pinned-secret sessions served by a Service Worker fetch-interception layer with its own cookie jar; CI-gated build + node smoke.
  5. Also in this release

    • WebSocket non-blocking I/O on C1M reactors (issue #181, #182).
    • RX-uring receive path on io_uring reactors (issue #179, #187).
    • Per-event latency probe: CWIST_LATENCY_PROBE=1 histograms queue delay and callback runtime (issue #166, #186).
    • CWIST_PROFILE preset env var (issue #171, #188); C1M + drain-chunk 8 is the default baseline.
    • BDR per-connection cursor fast path for cached GET replies (#194).
    • SQLite WASM bundle decision recorded: single bundle, no opt-out — db-less consumers link ~64.8 KB (per-object archive linking); the ~1 MB for db users is SQLite's reachable core (#199).

Notes:

  • lib/lsquic stays pinned to upstream master (2b30189), the same stable pin as v3.5.
  • Source tarball builds like v3.5: vendored dependencies included; make at the top level produces libcwist.a, the CLI, and cwist.pc.
  • Homebrew formula in this release line: packaging/homebrew/cwist.rb (tap: c4punks/homebrew-cwist).

CWIST v3.5

Choose a tag to compare

@gg582 gg582 released this 15 Sep 09:44

CWIST v3.5 — HTTP/3 client, websocket RFC 6455 hardening wave, and C1M reactor scaling

Major changes compared to v3.4:

  1. HTTP/3 Client (489a976)

    • The HTTP/3 client is ported from dev to main: QUIC/HTTP3 request path over the pinned lsquic master, without the WebTransport extension.
  2. C1M Reactor Scaling (28941cc)

    • One reactor loop per CPU the worker actually owns, sized from the worker's real affinity instead of the machine's core count.
  3. GC: Header-Scoped Malloc Interception (037c24d)

    • CWIST_INTERCEPT_MALLOC lets a request handler route libc allocations through the header-scoped GC arena for the duration of a request.
  4. WebSocket RFC 6455 Hardening Wave

    • Validate Sec-WebSocket-Version on upgrade and reject non-13 (68a3bf8, 57481d8).
    • Reject frames with non-zero RSV bits per section 5.2 (971fb8e).
    • Reassemble fragmented messages per section 5.4 and cap the total reassembled size at 64 MiB (24bf4d2, fe4e709).
    • Auto-reply PONG to PING frames per section 5.5.3 and echo CLOSE on receipt per section 5.5.1 (55062f8, 5d8a7a6).
    • Per-frame payload cap to prevent OOM DoS (ce61941).
  5. Security Fixes

    • jwt: validate the alg header field in cwist_jwt_verify() per RFC 8725 section 3.1 (13b372e).
    • http_client: cap response bodies at CWIST_HTTP_MAX_BODY_SIZE and response header counts at 200 against OOM DoS (d01c3d5, 44d9ca6).
    • Security headers: HSTS removed from the generic set, Permissions-Policy added (73d5a30).
  6. Benchmark CI

    • Process-group PSS recorded alongside RSS, fixing the shared-pages double count (c097591).
    • The latency distribution chart publishes on dev too; the latency gate compares against the same runner CPU rather than one absolute number (253e569, 0b590e4).
  7. Reliability and Fixes

    • flash: allocation return checks and zero-sized map guard (f872cae).
    • writer_fast: null check and P2C worker selection seed safeguard (8c8f72d).
    • healthz: reuse inactive probe slots, guard the null probe buffer (7249a10).
    • json_builder: escape strings per RFC 8259 with null safety (51abac7).
    • scheduler: disown the delayed-job heap from the growing thread's GC scope (f2404f4).
    • sstring_data_or_empty renamed to sstring_as_text (1eec306).

Notes:

  • Source tarball builds the same way as v3.4: vendored dependencies included, make at the top level produces libcwist.a, the CLI, and cwist.pc.
  • Homebrew formula in this release line: packaging/homebrew/cwist.rb.

CWIST v3.4.1

Choose a tag to compare

@DPS0340 DPS0340 released this 14 Sep 03:37

CWIST 3.4.1

Reliability fixes on the main release line, including the HTTP tail-investigation corrections. This is not a claim of a universal P99.999 SLO or reproduction of the historical benchmark host.

Highlights

  • Response stringification copies the exact serialized header byte span instead of scanning a non-NUL-terminated stack buffer (PR84, backported by PR90).
  • Linux multiprocess workers remain inside the inherited CPU affinity mask, including sparse and shifted CPU sets; failures preserve inherited placement (PR89, backported by PR90).
  • Carries existing main-line fixes for io_uring posted-work wakeups, queued-demand classic-pool growth, file-limit hard-cap preservation, and buffered HTTP/2 input readiness, plus the other main-line fixes since 3.4.
  • Adds regressions and a source-distribution gate that builds and tests the vendored tarball after extraction without Git metadata.

Distribution

Use cwist-3.4.1.tar.gz, which includes recursively pinned submodule sources. The automatic GitHub source-code archives do not include vendored submodule contents. SQLite is still downloaded by the build when absent; this is not an offline package.

Build with make VERSION=3.4.1; build prerequisites remain documented in README. The accompanying SHA256SUMS, SOURCE_COMMIT, SOURCE_TREE and SUBMODULES identify the tested artifact and its source.

Benchmark boundary

The earlier three local screens (27 cases, 20,405,857 requests, reported errors zero) used the PR89 dev-based candidate, not this main-based release. Their numbers must not be attributed to this release artifact. Issue25 remains open pending fresh external benchmark evidence. Existing v3.4 tags/assets are not moved or replaced.

Source identity

  • Commit: c06781d376c7f3f3e72202fe129abe59b31c1cfc
  • Tree: c522c01ff2b0c5542d11fa36b3af8bc7af038196
  • Vendored source archive SHA-256: f247852fc3549eee38cb7896bcfe2fd3ede8aa13ef9baef13939391e0ab6e656
  • Exact postmerge source-distribution build/test: https://github.com/c4punks/CWIST/actions/runs/34802310190
  • All seven postmerge workflows succeeded. Parent verified 3,004 top-level Git files against the release commit and nine top-level submodule pins; archive-verifier negative cases rejected wrong commits/checksums, altered source and missing files.

CWIST v3.4

Choose a tag to compare

@gg582 gg582 released this 10 Sep 15:16

CWIST v3.4 — gRPC client, proto codegen completeness, and WASM client-side support

Major changes compared to v3.3:

  1. gRPC Client: Load Balancing & gRFC A6 Retries (714b9b6, bb04dae)

    • HTTP/2 gRPC client (h2c cleartext or TLS with ALPN "h2"): unary and server-streaming calls, grpc-timeout deadlines, RST_STREAM cancellation.
    • cwist_grpc_channel: dns/ipv4/ipv6 target resolution (doc/naming.md) into per-address subchannels; failed dials back off per doc/connection-backoff.md (1s initial, x1.6, 120s cap, +/-0.2 jitter, reset on SETTINGS handshake).
    • Client-side load balancing (doc/load-balancing.md): pick_first (default, sticky with in-order failover) and round_robin (eager connect, READY-set rotation), plus aggregated channel connectivity state.
    • gRFC A6 retry engine between the channel and the LB pick: maxAttempts (client-capped at 5), jittered exponential backoff, retryable status codes, call deadline shared across attempts, per-attempt remaining grpc-timeout, and the grpc-previous-rpc-attempts header on every retry.
    • Transparent retries (A6): RPCs that never left the client retry until the deadline; RPCs refused before server application logic (RST_STREAM REFUSED_STREAM, GOAWAY last-stream-id below the stream) get one immediate retry — neither counts against maxAttempts nor the throttle.
    • Server pushback: grpc-retry-pushback-ms honored in both directions (retry after exactly N ms / do not retry); retryThrottling token bucket per channel.
    • JSON service config subset (doc/service_config.md): loadBalancingConfig/loadBalancingPolicy, methodConfig (name matching, retryPolicy, waitForReady, timeout), retryThrottling — with A6 validation rules.
    • Server spec compliance: error responses are now Trailers-Only end-to-end (unary: single HEADERS frame with END_STREAM; streaming: Response-Headers delayed until the first message), so conforming clients can actually retry. cwist_grpc_stream_set_retry_pushback() emits pushback from handlers.
    • tests/test_grpc_channel: LB stickiness/failover/rotation, retry matrix (retryable/non-retryable codes, maxAttempts exhaustion, pushback both ways, throttling, deadline across attempts), JSON config validation, GOAWAY transparent retry against a raw-socket fake, and resolver schemes.
  2. cwist proto Codegen Completeness (b9a4fc7, 6ebd78a)

    • oneof, map, fixed-width types (fixed32/64, sfixed32/64, double) for proto3 fields.
    • protoc --descriptor_set_out binary input (auto-detected or via --descriptor-set), alongside the existing text path.
  3. WASM Client-Side Support Wave (df01f68, f0ac2ef, 04907c9, 6e0ee35)

    • cwist_app_dispatch_memory(): run cwist_app routing and handlers on in-memory request/response buffers with no sockets — the same C handlers run inside a Service Worker or a JS fetch-interception layer.
    • make wasm: libcwist_wasm.a Emscripten target shipping the socket-independent core (app dispatch, mux/middleware, HTTP/1 parser/serializer, query map, cJSON, memory utilities).
    • cwist_db_open_memory() / cwist_db_serialize(): official in-memory/blob database API over sqlite3_deserialize-style buffers.
    • <cwist/wasm/typedarray.h>: zero-copy serialization helpers mapping C struct arrays onto HEAP TypedArrays instead of round-tripping through snprintf JSON.
  4. gRPC Server Leftovers (cc4cd15)

    • The builtin grpc.health.v1.Health Watch method now streams status changes live over the HTTP/2 transport path (snapshot fallback on the buffered path).
  5. v3.4 Performance Wave (45e6552, 668625d, 48088c4, a046efe, 95ee64f, 86ded77, c5bc5cc)

    • C1M reactor tail latency: cooperative request-batch yields (CWIST_HTTP_YIELD_BATCH), batch responses coalesced into one writev per turn (256 KiB stash), reactor wake eventfds registered with io_uring (IORING_REGISTER_EVENTFD), post bursts coalesced to a single wake.
    • BDR cache: lock-free learn/read paths (CAS-published entries, atomic blob swaps, EBR reclamation) and hit-time revalidation hooks (cwist_bdr_put_revalidatable).
    • Classic pool: CWIST_POOL_PREWARM / CWIST_POOL_IDLE_TIMEOUT_MS tunables; HTTPS handshake shepherd shard count tunable via CWIST_HTTPS_HS_SHARDS.
    • WAF: signatures scanned with one Aho-Corasick pass.
  6. Distribution (cef1524, 9a77fd8)

    • Homebrew tap published: brew tap c4punks/cwist && brew install c4punks/cwist/cwist (verified end-to-end on Linuxbrew). vcpkg stays an in-tree draft under packaging/vcpkg/.

Known limits (see ROADMAP.md): client-side hedging (hedgingPolicy) is not implemented — gRPC C-core does not implement it either; the channel resolver supports dns (default), ipv4, and ipv6 targets, while unix/vsock transports, grpclb/xDS policies, and perAttemptRecvTimeout remain unimplemented.

--- Patch Updates (tag refreshed) ---

  1. HTTP/2 Flow-Control Hardening — Vulnerability Defense (e121dac)

    • Integer-overflow defense: the pacing token-bucket refill now caps the idle elapsed time at the exact bucket fill time (target_window / rate) before multiplying by the rate. Previously, a connection left idle for an arbitrarily long period combined with a very large computed pacing rate could overflow the uint64 multiplication (elapsed x rate) — the same arithmetic-defect class as the classic WINDOW_UPDATE integer overflows seen in RFC 7540/9113 implementations.
    • Availability defense (self-inflicted DoS): pacing_rate_bytes_per_sec is floored at min_window x 10, so a single spiked RTT sample (retransmission burst, scheduler interference) can no longer collapse the pacing rate and throttle the connection to a crawl. The peer's flow-control windows still gate in-flight bytes; loopback/small-RTT paths stay effectively unpaced as before.
    • Oscillation defense: each BDP window retune now moves at most a 2x/0.5x step from the current target, so a micro-interval scheduling-jitter sample cannot slam the window between its floor and ceiling under bursty load; fast-link convergence stays exponential, and a zero-length interval keeps the current target instead of collapsing it to the minimum.
  2. HTTP/3 Concurrency Hardening — Vulnerability Defense (6b4b4a0, bd8ba21)

    • g_h3_dgram was a single process-wide struct shared by every HTTP/3 connection with no lock: cwist_http3_send_datagram() on one connection could free() and overwrite another connection's still-pending payload mid-flight (double-free/UAF). Replaced with a per-connection, mutex-guarded FIFO datagram queue (h3_conn_ctx_t, torn down in on_conn_closed), which also fixes a same-connection bug where a second send before the first flushed silently dropped the first payload instead of queuing it.
    • cwist_h3_free_session_ticket_key() read the SSL_CTX ex_data index without the same pthread_once gate cwist_h3_setup_session_tickets() uses to write it; a context destroyed from a different thread than the one that created it had no happens-before edge to the writer. Both functions now take the same gate.
    • The use_file_stream write loop looped pread() + lsquic_stream_write() until EAGAIN with no bound: a large file transfer on a wide-open congestion window could hold the single H3 event-loop thread in back-to-back synchronous disk reads, delaying packet I/O/ACK processing for every other connection that thread services. Capped at 4 chunks per on_write() callback, re-arming wantwrite between bursts.
    • Follow-up perf pass: removed the per-header malloc/free pair in h3_process_stream_headers() (QPACK decode — was two heap round trips per header, tens of them per request), and moved the recvmmsg() batch scratch buffers (2.1MB, previously living on the H3 event-loop thread's stack) onto the heap, removing an implicit large-stack-thread requirement.
  3. Benchmark Fairness: Spring Boot Tuned Run (93a5eb5)

    • The "tuned low-latency run" (wrk -t4 -c100 vs the main -t12 -c400 profile) was CWIST-only and published in the README as a standalone headline number, with no equivalent Spring Boot figure anywhere nearby — comparing CWIST's best case against a number Spring was never measured at, contradicting this repo's own "no framework gets a hand-tuned advantage the others do not get" claim. Spring Boot now gets an identical second boot under the identical -t4 -c100 -d10s profile, replaying the same trained AOT cache used for its main run (no extra cold-start penalty CWIST doesn't also avoid), and the README callout now shows both side by side. Axum/Gin are not yet included in this second pass (documented as a known remaining asymmetry in docs/webserver-benchmark.md).

CWIST v3.3

Choose a tag to compare

@gg582 gg582 released this 05 Sep 15:27

CWIST v3.3 — gRPC streaming, deferred async handlers, and stability hardening

Major changes compared to v3.2 (including subsequent patch updates in this tag):

  1. Full gRPC Streaming Support (d3222d6)

    • Connect incremental decoders/output sinks directly to HTTP/2 DATA frames via per-connection stream hooks.
    • Send grpc-status and grpc-message as actual trailer HEADERS frames.
    • Parse grpc-timeout, enforce DEADLINE_EXCEEDED, and propagate RST_STREAM cancellations.
    • Metadata normalization (case-insensitivity, *-bin base64 encoding), gzip request compression negotiation.
    • cwist proto extensions: repeated fields (packed/unpacked), recursive encode/decode/free for nested messages, proto3 open enum support.
    • New tests: test_grpc_stream (wire-level h2c) and test_proto_gen (roundtrip).
  2. Deferred Async Handlers & Packaging (2352d0f)

    • cwist_async_defer/respond/abort: delegate response completion to worker job threads with 504 timeout enforcement.
    • Per-reactor wake fd (eventfd/pipe) and lock-free MPSC completion routing.
    • make install support for cwist.pc (pkg-config), make dist tarball, and Homebrew/vcpkg drafts.
  3. RFC 9218 HTTP/3 Priority Compliance (52b023b, e7d8297)

    • Removed PRIORITY_UPDATE frame transmission on request streams (resolving H3_FRAME_UNEXPECTED on strict stacks).
    • Preserved set_stream_priority ABI with deprecation warnings.
  4. HTTP Connection Stability (e719cf6, 1ab0e9f)

    • Enhanced graceful close, added error-checking helpers, and enabled sanitizer CI.
    • Implemented grace-period serving after HTTP/2 idle GOAWAY.
  5. Benchmarks (22e9059, 3c76340)

    • Added the-benchmarker/web-frameworks contract app; fixed v3.3 tag build.
    • Fixed the-benchmarker/web-frameworks main.c build command missing vendored include paths (sqlite3, libttak, BoringSSL, ...), causing a clean-container build to fail with 'sqlite3.h: No such file or directory' (the-benchmarker/web-frameworks#9745).

--- Patch Updates ---

  1. Status Code & Header Consistency

    • Expanded cwist_http_status_t to cover the full standard 1xx–5xx range (RFC 9110 + WebDAV) while retaining legacy aliases.
    • Added cwist_http_status_reason() to automatically apply standard reason phrases to error responses and status line fallbacks.
    • Cleaned up duplicate app.h declarations and resolved header guard collisions: unified top-level <cwist/app.h> and <cwist/sys/app/app.h> under an umbrella header to eliminate differing cwist_app struct layouts across translation units.
    • Standardized build baseline to -std=c17 (aligned with documentation) and updated CLI scaffolding templates.
  2. Concurrency & Memory Safety

    • io_queue: Fixed node reclamation race (UAF/ABA) in Michael-Scott queue by guarding push/pop in ttak EBR critical sections and retiring unlinked nodes via 2-stage retirement (new -> old -> ttak_epoch_retire) past epoch boundaries (100x clean TSAN, clean ASan/UBSan).
    • execute_chain(): Fixed static files returning 500 on apps with 0 middleware due to lost handler_data.
    • rdbms_auto_mount: Fixed uninitialized host pointer free (ASAN BUS) by replacing it with zeroed allocation.
    • http2: Fixed sequenced DATA message leak on flow-control wait abort paths.
    • Added cwist_error_dispose() and eliminated cJSON error payload leaks in sstring/nuke-db (resolving prior ASan CI failures).
    • test_static_and_range: Fixed test hanging on keep-alive responses waiting for EOF by checking Content-Length instead.
    • http2: Fixed HPACK dynamic table desync on stream errors — a header block that failed pseudo-header validation (e.g. duplicated :method) was abandoned mid-decode, skipping later incremental-indexing inserts and corrupting the shared compression context for subsequent requests on the same connection.
  3. Hardening & Interop CI

    • Linux: Enabled -fstack-protector-strong, _FORTIFY_SOURCE=2, PIE, and full RELRO by default.
    • Added WERROR=1 build toggle (excluding vendored objects) and integrated it into ASan/UBSan workflows.
    • Introduced interop workflow: h2spec h2c compliance checks with baseline diff against known failures (fails build on unexpected regressions).
    • tests/lsan.supp: Documented known lsquic teardown leaks and h3 header-set teardown gaps (ROADMAP known issues).
  4. Tooling & Documentation

    • make install now installs the cwist CLI into $(PREFIX)/bin; uninstall cleans it up.
    • README: Added CLI project workflows (new/watcher/openapi/proto/describe) and stability/conformance sections.
    • NOTICE.md: Added summary of vendored dependency licenses.
  5. Async v2 Writer, P2C TLS Shepherd & Idle Reaper (428df58)

    • cwist_async_complete: removed synchronous SO_SNDTIMEO blocking; speculative non-blocking send pipeline with zero added latency on the hot path.
    • TLS shepherd: SSL_accept crypto offload out of the accept loop, Power-of-Two-Choices shard load balancing, SO_REUSEPORT ingress distribution.
  6. HTTP/2 DoS Mitigations — Rapid Reset & Control-Frame Floods (f89fb78)

    • CVE-2023-44487 (Rapid Reset): token-bucket RST_STREAM budget per connection (burst 100, refill 100/s; CWIST_HTTP2_MAX_RST_BURST / CWIST_HTTP2_MAX_RST_RATE); excess triggers GOAWAY(ENHANCE_YOUR_CALM) and connection teardown, enforced on both blocking and non-blocking send loops.
    • RFC 7540 §5.1: RST_STREAM on idle streams (stream_id > last_processed_stream_id) is now a PROTOCOL_ERROR connection error.
    • CVE-2024-27983 (CONTINUATION flood): 64KB header-block cap (CWIST_HTTP2_MAX_HEADER_BLOCK_SIZE), 32-frame CONTINUATION limit (CWIST_HTTP2_MAX_CONTINUATIONS), 256 headers/request cap, and SETTINGS_MAX_HEADER_LIST_SIZE=65536 advertised in the handshake.
    • CVE-2019-9512 (PING flood): token-bucket rate limit on non-ACK PING frames with ENHANCE_YOUR_CALM enforcement.
  7. Async HTTPS over TLS (f9d447c)

    • Deferred handlers on TLS connections now respond via SSL_write through the connection wrapper (cwist_https_send_response/_head) instead of writing plaintext to the socket; keep-alive connections re-arm through https_pool_submit_conn.
    • Thread-local arena: _Thread_local fast-path TLS lookup (no pthread_getspecific call on the hot path), owner_tid tagging so cross-thread destroys bypass foreign caches, and arena struct + buffer recycled together.
    • HTTP/2 responses are created inside the request arena — one arena block per request-response cycle.
  8. HTTP/2 Async Defer — MPSC Queue + eventfd (0183305)

    • Deferred handlers no longer block the HTTP/2 connection loop: completed (stream_id, req, res) nodes are pushed onto a per-connection MPSC queue and the connection thread is woken via eventfd; HPACK encoding and flow control stay single-threaded (no cross-thread frame injection, no output mutex).
    • RST_STREAM on a deferred stream removes it from the stream table; late completions for cancelled streams are dropped at drain time. Refcounted queue survives teardown races.
  9. HTTP/3 UDP GSO Fix (485ce64)

    • Capped GSO super-packet runs at 65535 bytes (max UDP payload); an exactly-64KiB batch previously failed sendmsg with EMSGSIZE and permanently disabled GSO.
  10. POLLOUT-Resumable Async Writer (6eef7a7)

    • Deferred async completions on the reactor path no longer poll-wait in the completion thread on slow clients: unsent bytes are deep-copied into an owned buffer, parked in a one-shot POLLOUT slot (new cwist_reactor_add_out; io_uring POLL_ADD / epoll EPOLLOUT|ONESHOT / kqueue EVFILT_WRITE|EV_ONESHOT), and resumed on write-readiness.
    • Parked writers are bounded by a deadline (keep-alive timeout, refreshed on progress); file-stream bodies and the classic pool path keep the existing bounded poll loop.
    • Removes the last case where a slow client could occupy a reactor thread for the SO_SNDTIMEO budget (ROADMAP "Async v2" item closed).
  11. HTTP/3 Header-Set Leak Fix (151f61c)

    • lsquic never calls hsi_discard_header_set for streams still open on abortive engine destroy, leaking cwist_h3_hset_t objects.
    • Header sets are now tracked in an intrusive list on cwist_http3_context and swept after lsquic_engine_destroy; hsi_discard untracks before freeing so the sweep only touches true orphans.
    • The leak:cwist_h3_hsi_create entry is removed from tests/lsan.supp — LSan now passes on the HTTP/3 suite without that suppression.
  12. gRPC WINDOW_UPDATE Backpressure (097c4cb)

    • Handler-thread gRPC sends no longer fail with UNAVAILABLE on zero flow-control credit: cwist_http2_stream_send_data now flushes pending DATA, parks the handler on a per-connection condvar (h2_fc_wait_credit) signalled by the dispatcher on WINDOW_UPDATE/SETTINGS credit, and resumes chunk-by-chunk without holding the output mutex.
    • RST_STREAM (Rapid Reset), stream removal, connection teardown, and stall timeout still fail fast.
    • With this, every HTTP-layer item under ROADMAP "Known limits" that is controllable in-tree is closed; remaining limits are upstream-bound (e.g. WebTransport pending lsquic PR #629).