Skip to content

CWIST v3.7.1

Choose a tag to compare

@gg582 gg582 released this 29 Sep 10:26
· 697 commits to main since this release

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.