Skip to content

Releases: ehsanmok/flare

v0.10.0

Choose a tag to compare

@ehsanmok ehsanmok released this 12 Aug 02:31

flare v0.10.0 moves the toolchain onto Mojo 1.0.0 and MAX 26.5.0 (both stable, out of nightly), closes the remaining h2spec conformance gaps, and lands a breaking cut of the prelude. 70 commits since v0.9.0.

Toolchain

  • Mojo pin widened to >=1.0.0,<1.1.0; MAX pin to >=26.5.0. Both now resolve off the stable max conda channel instead of max-nightly.
  • json, mojodoc, and mozz are pinned to their own new tagged releases (v0.3.0, v0.1.0, v0.2.0) instead of tracking main, so a flare checkout resolves deterministically.
  • The API renames that came with 1.0.0 are done throughout: pointer arithmetic and .load/.free/memcpy on their unsafe_ names, .bitcast[] to .unsafe_bitcast[], __del__ to __deinit__, ImplicitlyDestructible to Deinitable, and the read argument convention to imm.

Breaking: prelude cut from 454 symbols to 125

flare.prelude re-exported everything, including wire-format internals (encode_varint, qpack_encode_field_section, huffman_encode, ...). It now exports exactly what the root flare package exports, generated from that list rather than hand-maintained. WithCancel, ok_json, Body, and ChunkSource moved into the root surface, since their absence there was the actual bug. Migration: import from the owning module -- from flare.http2 import Http2Connection, from flare.quic import encode_varint, from flare.runtime import Reactor.

HTTP/2 conformance

h2spec now runs for real in the conformance CI job instead of being advertised-only. The gaps it found are closed: connection errors are reported with GOAWAY and frame shape is validated, header blocks decode atomically, DATA/WINDOW_UPDATE enforce stream state and flow control, and buffered responses respect the peer's send window.

HTTP client and streaming

  • HttpClient.get_streaming_tls() -- streaming download over HTTPS; get_streaming() previously raised there and pointed callers at the buffered get().
  • Chunked request bodies decode on the Handler path.
  • Streaming responses coalesce chunks per pump / per writable edge instead of one write per chunk, with a bulk-copy on the framing path -- a latency and syscall-count win on the streaming benchmark.

Fixes

  • WebSocket: RSV1 without a negotiated extension is now rejected (RFC 6455).
  • SSE: the idle keep-alive heartbeat is rate-limited.
  • ASan: test_h3_0rtt_e2e, test_tls_acceptor, and test_tls_server_ffi retry up to three times on the forked-loopback timing flake instead of trusting a single run.

Server

bind_many() (multiple bind addresses) and num_workers>=2 can now be combined -- previously raised. Each (address, worker) pair gets its own SO_REUSEPORT listener.

Docs and testing

docs/features.md and docs/architecture.md corrected against what the code actually does (streaming chunk-coalescing contract, the retired io_uring bufring 64-conn crash claim, the prelude surface). tests/test_prelude_surface.mojo is a new ratchet asserting the prelude and root package export the same entities. A new interop test exercises flare's client against a server it did not write (and vice versa). Comments and docstrings across the codebase, tests, and README also got a pass: stale pre-1.0 nightly/1.0.0b1/1.0.0b2/rc0 version tags are gone, and where the underlying limitation is still real the note stays, just without the dated label.

Known limits (tracked for v0.10.x)

Full inventory in docs/features.md.

Full Changelog: v0.9.0...v0.10.0

v0.9.0

Choose a tag to compare

@ehsanmok ehsanmok released this 21 Jul 00:52

flare v0.9.0 is the release where a single Handler serves HTTP/1.1, h2c, HTTP/2, and HTTP/3 at once, and HttpClient speaks all four back. 149 commits since v0.8.1. The headline is the HTTP/3 client and the QUIC hardening that makes it hold up on a real network, but the cycle also closed the ergonomics gap the wire work had opened: streaming responses through the normal handler path, full gRPC, WebSocket over HTTP/2, sessions, OpenAPI, and reliability middleware.

This is a minor bump. Existing serve / Handler / ChunkSource call sites compile unchanged; everything below is additive.

HTTP/3 and QUIC

  • HttpClient(prefer_http3=True) / .with_prefer_h3() dials HTTP/3 over QUIC, discovers origins via Alt-Svc (RFC 7838), and races h3 against h2/h1 with happy-eyeballs so a dead QUIC path never costs a timeout. Connections pool and multiplex per origin; idempotent requests can ride 0-RTT and replay at 1-RTT if the server rejects early data.
  • QUIC server: Retry issuance plus client-side Retry handling (RFC 9000 sec 8.1), connection migration with a strict egress hold, RFC 9221 datagrams, stateless reset on unknown DCIDs, and structural PTO / ack-delay timers.
  • flare.quic.cc ships NewReno, CUBIC, and HyStart++; the 1-RTT path runs an RTT estimator and ACK-based loss detection (RFC 9002).
  • Batched UDP I/O: recvmmsg ingress on by default, sendmmsg / GSO egress available, behind an ENOSYS fallback.
  • On the gate workload flare HTTP/3 runs at 74,653 req/s, about 2.9% ahead of quiche 0.22.

Streaming-proxy surface

The shape an inference or reverse-proxy front needs, without a raw reactor loop or pointer smuggling. StreamHandler + StreamConn give you a typed lifecycle and framework-owned per-connection state; UpstreamChunkSource streams a body off a reactor-registered fd; FrameMux multiplexes logical streams over one Unix socket; watermark backpressure couples upstream reads to downstream writability. serve_streaming runs it single- or multi-worker. A complete single-upstream relay is on_open (attach a source) plus on_upstream (conn.relay_upstream()). ByteReader / ByteWriter replace raw-pointer framing.

gRPC

All four server shapes mount on HttpServer: unary, server-streaming (incrementally flushed, one DATA frame per message), client-streaming, and bidi. grpc-timeout deadlines are enforced and gzip message compression is negotiated. tools/proto_gen.py emits proto3 messages and service-block code (server trait, per-RPC adapters, typed client stub, serialized FileDescriptorProto). Server reflection answers list_services, file_by_filename, and file_containing_symbol; grpc.health.v1.Health ships Check and Watch. The client covers unary plus server-, client-, and bidi-streaming. Maps and oneof in the message codegen are the one remaining gap.

WebSocket

  • WebSocket over HTTP/2 (RFC 8441 Extended CONNECT), client and server, including reactor sidecar dispatch (WsH2Handler + HttpServer.serve[H, W]).
  • Stateful server handlers via the WsHandler trait and WsServer.serve[H].
  • permessage-deflate context-takeover (RFC 7692 §7.1) with a persistent compressor pair.
  • WsAutoClient picks the carrier wire (h1 or h2) from the negotiated ALPN and drives the handshake.

HTTP client

Connection pooling keyed on (scheme, host, port), with a TLS pool for HTTPS keep-alive. RequestBuilder for per-request method/headers/query/body, MultipartFormBuilder for multipart/form-data, chunked streaming upload (send_chunked), and streaming download (get_streaming) that reads the body in bounded memory. Redirect policies, a cookie jar, retry with backoff, and transparent gzip/deflate/brotli decompression bounded by a 16 MiB zip-bomb cap. HTTP proxy support via CONNECT tunnels and the HTTP_PROXY / HTTPS_PROXY / NO_PROXY env vars.

Routing, state, sessions, OpenAPI, caching

  • Router is now Defaultable and composes with stock middleware; Router.mount(prefix, sub) mounts sub-routers.
  • State[T] carries registration-time state (a DB pool, config) beside request extractors, the analogue of axum's State(db). Typed Json[T] / Form[T] / JsonAs[T] / Session extractors landed.
  • Sessions gained a pluggable SessionBackend with TTL expiry, CSPRNG session ids, and signed-cookie carriers.
  • spec_from_router derives an OpenAPI 3.1 spec by walking a runtime Router.
  • RFC 9111 HTTP caching: Cache[Inner, S] middleware, a directive parser, and an in-memory store.

Reliability and errors

RateLimit, CircuitBreaker, and PostHocDeadline middleware. DeadlineWatchdog flips a Cancel cell from a background thread, so cooperative handlers get real mid-flight deadline enforcement. Typed errors map to status now: ValidationError to 400, AuthError to 401/403, plus unauthorized() and forbidden() builders.

Security

The H2 DoS mitigations are enforced in code now, not advertised-only: max_header_list_size on HEADERS+CONTINUATION returns RST with ENHANCE_YOUR_CALM, a CONTINUATION frame cap (CVE-2024-27316), and an RST_STREAM flood answered with GOAWAY (CVE-2023-44487). The client decompression path is bounded by a 16 MiB cap.

Testing and fuzzing

62 fuzz harnesses (58 fuzz + 4 property), 9M+ runs combined, zero known crashes, an ASan CI gate, and h1/ws conformance corpora. H2cTestClient[H] exercises the HTTP/2 handler path in-process without TLS.

Known limits (tracked for v0.9.x)

  • QUIC server-side loss-driven retransmit and send pacing are not wired yet; the bench gate is met without them.
  • HttpClient.get_streaming() raises on HTTPS; use buffered get() there.
  • The HTTP/3 client rejects request bodies larger than one packet.
  • The io_uring bufring handler stays opt-in (FLARE_BUFRING_HANDLER=1).

Full inventory in docs/features.md.

Full Changelog: v0.8.1...v0.9.0

v0.8.1

Choose a tag to compare

@ehsanmok ehsanmok released this 22 Jun 16:26

Patch release on top of v0.8.0.

Fixes

  • deps: recipe.yaml now pulls the json v0.2.1 source tag for Mojo 1.0.0b2 compatibility (#4 — thanks @bowyern).

Build

  • Bump package version to 0.8.1 in pixi.toml and recipe.yaml.

No flare source or API changes — v0.8.0 consumers can move to v0.8.1 without code changes.

Full Changelog: v0.8.0...v0.8.1

v0.8.0

Choose a tag to compare

@ehsanmok ehsanmok released this 19 Jun 03:27

flare v0.8.0

A protocol-surface release: flare gains a sans-I/O QUIC v1 + HTTP/3
stack, a gRPC unary server, HTTP caching and reliability
middleware, and a full migration to the Mojo 1.0.0b2 toolchain. 214
commits since v0.7.0.

QUIC v1 (sans-I/O core + reactor)

  • Wire codecs: variable-length integers (RFC 9000 §16), long/short
    packet headers (§17), transport-frame codec (§19), and transport
    parameters (§18).
  • State machines: per-stream (§3) and per-connection (§10) lifecycle in
    flare.quic.state.
  • Crypto: pluggable QuicCrypto trait with RFC 9001 initial-secret
    math and an OpenSslQuicCrypto production backend — AEAD
    (RFC 9001 §5.3) and header-protection mask (§5.4) via dedicated
    flare_quic_aead_* / flare_quic_hp_mask FFI thunks.
  • Server I/O: UDP-listener bind with per-datagram dispatch,
    QuicConnection.handle_packet wired end-to-end, and a PTO / idle /
    ack-delay TimerWheel.
  • Optional rustls QUIC backend: a rustls_wrapper Rust crate +
    build_rustls.sh, FFI binding, and live-fire handshake fixtures
    (incl. miri).
  • Coverage: loopback integration plus handshake/CID and crypto fuzz
    harnesses; RFC 9001 Appendix A conformance vectors.

HTTP/3 + QPACK

  • Frame codec + SETTINGS payload (RFC 9114 §7).
  • Sans-I/O drivers: H3Connection, a request-stream reader state
    machine, and a response-stream writer.
  • Server path: peer uni-stream dispatch, SETTINGS/GOAWAY consumption,
    and the feed_stream_chunk -> take_request -> emit_response -> take_response_frames loop.
  • QPACK static-table encoder/decoder (RFC 9204).
  • Conformance round-trip suite + fuzz_h3_server (200K execs).

ALPN protocol routing

  • Wire-protocol routing decision layer; http.server.bind_with_h3
    opens the UDP listener and routes by ALPN; ALPN-driven WebSocket
    dispatch (WsClient.connect_prefer_h2, auto-client wire dispatcher).

gRPC (unary)

  • LPM framing codec + canonical status codes; GrpcServerAdapter
    bridges unary RPCs over HTTP/2.
  • Typed surface: GrpcRequestHeaders, GrpcUnaryReply with ok/err
    factories; grpc-status-details-bin base64 trailers; binary/text
    metadata key discipline.
  • Hardening: run_unary_call never raises (errors map to typed
    outcomes), case-insensitive te: trailers, fuzz_grpc_lpm_decoder.

HTTP caching & middleware

  • RFC 9111 Cache-Control directive parser + bounded store; a
    Cache[Inner, S] middleware with two-level Vary lookup and
    is_fresh freshness checks.
  • Retry + Timeout reliability middleware (RFC 9110-aligned backoff).
  • H1LeniencyConfig named relaxation flags wired into the H1 parser.

WebSocket

  • permessage-deflate context-takeover (persistent LZ77 across
    messages).
  • RFC 6455 §5 conformance fixture corpus + runner; forked-server I/O
    round-trip suite.

Developer surface

  • Template inheritance: {% block %} / {% extends %}.
  • OpenAPI 3.1 spec model + deterministic JSON emitter.
  • TestClient[H] for in-process handler exercises.

Performance

  • HPACK Huffman: table-driven decoder, 3.6x-4.2x over the scalar
    path.
  • One-pass exact-length response serialization; per-header
    to_lowercase allocation removed; UTF-8 validation bypassed on the
    H1 parse hot path.
  • No regression vs Go net/http and nginx (single-worker) or
    hyper/axum/actix-web (multi-worker); per-percentile sigma published in
    docs/benchmark.md.

Correctness & hardening

  • HTTP client: reject 16-digit chunk-size to stop a signed-Int
    overflow driving an out-of-bounds read in the chunked decoder.
  • HTTP parser: sanitize unvalidated request-line bytes before echoing
    them in error messages.
  • TLS FFI: keep owned path/SNI buffers live across OpenSSL calls;
    NUL-terminate cert/key/CA paths; install the rustls QUIC cdylib as
    .dylib on macOS.
  • Auth: forward caller-supplied Authorization on H1 and h2c-upgrade
    paths (needed for S3 / AWS SigV4).
  • io_uring: size SQE/CQE mmaps from the entry count, not the
    ring-entries offset.
  • HTTP/2: stop take_completed_streams re-dispatching already-served
    streams; stream slab keyed on stream_id with a fast-path table.
  • New fuzz harnesses across quic (varint, long-header,
    transport_params), qpack, h3 (frame), grpc, and cache-control; h1
    wire-grammar conformance corpus.

Structure (v0.8 decomposition)

  • A 1000-line-per-module bar enforced by
    tools/check_reactor_size.sh. Split the server reactor into epoll +
    uring modules, relocated unified-reactor dispatch, extracted client
    parse/send, the io_uring ABI codec, quic/http2 value types, and the
    extractor parsers.
  • Lifecycle: migrated __copyinit__ / __moveinit__ to __init__
    overloads; removed stub-only public surfaces and a stale
    prelude.mojo shadow.

Mojo 1.0.0b2 migration

  • reflect[T] alias form, non-nullable UnsafePointer,
    MutExternalOrigin -> MutUntrackedOrigin, and StringSlice
    construction via CStringSlice.
  • Dependencies pinned to json v0.2.1 and mozz v0.1.4; quiche
    baseline bumped 0.22.0 -> 0.24.5.

v0.7

Choose a tag to compare

@ehsanmok ehsanmok released this 12 May 00:46

First stable release on Mojo 1.0.0b1. v0.7 closes the v0.6 deferred backlog and ships the production-shape v0.7 line items end-to-end: HTTP/2 is now a first-class wire on the same HttpServer / HttpClient, the reactor gains an opt-in io_uring backend, the application layer fills in (sessions, signed cookies, structured logging, Prometheus metrics, SSE, PROXY-protocol, mTLS, UDS), the bench harness now self-detects the saturation cliff before publishing numbers, and the per-percentile σ over 5 runs is the canonical honesty meter for the tail.

151 commits since v0.6.0. Compatible with Mojo 1.0.0b1, json 0.1.6, mozz 0.1.3.

Highlights

HTTP/2 — same handler, version-aware

  • Unified HttpServer.serve(handler) auto-dispatches HTTP/1.1 + HTTP/2 per connection. Preface peek for cleartext, ALPN h2 for TLS. The same Router, middleware, and extractors run on both wires.
  • h2c via Upgrade (RFC 7540 §3.2): mid-stream switch from h1 to h2 on the same connection. HttpClient(h2c_upgrade=True) on the client side.
  • h2c via prior knowledge: HttpClient(prefer_h2c=True) sends the h2 preface immediately.
  • Http2ClientConnection driver (RFC 9113 client side) feeds the same HttpClient; no separate Http2Client to learn.
  • HPACK Huffman codec (RFC 7541): scalar-correct decoder + encoder for H=1 literals, with a SIMD shim shipped as parity fallback.
  • RFC 8441 Extended CONNECT — both server and client side. WebSocket can now ride a single h2 stream via WsOverH2Stream + bootstrap_ws_over_h2.
  • Per-stream Cancel propagation: peer RST_STREAM flips the per-stream cancel cell so handlers observe cancel.cancelled(). GOAWAY and drain do the same.
  • Http2Config with non-default SETTINGS emission on the initial frame.
  • Fuzz coverage for CONTINUATION-flood and RAPID-RESET (CVE-2023-44487) state machines.

Reactor — opt-in io_uring

Backend chosen at compile time per OS / kernel; epoll/kqueue remains the default everywhere.

  • io_uring substrate: direct-syscall FFI, SQE encoder + CQE decoder, mmap'd SQ / CQ rings with atomic head/tail. Fuzzed (encoder + decoder harness, 200k runs).
  • UringReactor with comptime backend selector and epoll-shaped API. FLARE_DISABLE_IO_URING=1 for one-off opt-out.
  • Buffer-ring path (IORING_REGISTER_PBUF_RING + IOSQE_BUFFER_SELECT, multishot recv) with IORING_SETUP_DEFER_TASKRUN / COOP_TASKRUN / SUBMIT_ALL. Closes the 1-worker throughput regression vs epoll; opt-in via FLARE_BUFRING_HANDLER=1 while the multi-worker wiring stabilises.
  • prep_multishot_accept, live IORING_ACCEPT_MULTISHOT round-trip.
  • run_uring_recv_reactor_loop[H] + _shared[H] dispatch loops.

Application layer (framework parity)

  • Router is Copyable via Arc-style refcounted boxed handlers — safe for srv.serve(router^, num_workers=N) without re-allocating per worker.
  • HttpServer.bind_many — multi-listener accept demux onto a single handler.
  • HTTP/1.1 trailer fields (RFC 7230 §4.1.2 / §4.4): Response.trailers, auto Trailer: header, smuggling guard, client-side parse off the chunked decoder.
  • HTTP/1.1 client connection pool: HttpClient.with_pool(...) keyed on (scheme, host, port), idle reuse, per-origin caps, stale-conn retry.
  • Server-Sent Events — first-class surface with backpressure-aware emit + last-event-id support.
  • Conditional[Inner] middleware (304 / 412 with ETag / Last-Modified).
  • RedirectPolicy with cross-origin gating.
  • StructuredLogger[Inner] JSON-per-line emitter.
  • Metrics[Inner] + MetricsRegistry — Prometheus exposition middleware.
  • Server-side auth extractors + CSRF helper.
  • askama-shape template engine.
  • RequestChunkSource for streaming inbound bodies.
  • PROXY protocol v1 + v2 parser (HAProxy upstream).
  • HandlerInfallible trait + WithRaises adapter — def home(req: Request) -> Response (no raises) is now a first-class shape; the Router.get(...) call site accepts both shapes at the same type.
  • HandlerExtractor convenience trait drops the turbofish on r.get[H](path, h).
  • ok_json_value typed-JSON response builder.

Transports

  • TLS session resumption (RFC 5077 / RFC 8446 §4.6.1) — server-side ticket cache + client-side reconnect.
  • flare.uds: UnixListener + UnixStream (AF_UNIX sidecar IPC).
  • WebSocket over HTTP/2 (RFC 8441) end-to-end.
  • WebSocket permessage-deflate (RFC 7692): codec, Sec-WebSocket-Extensions parser + emitter, negotiate_permessage_deflate, no_context_takeover enforced on both sides, 16 MiB per-message decompressed cap.
  • Multi-worker WsServer + WsClient ALPN locking.

Developer experience

  • flare.preludefrom flare.prelude import * gets you the everyday handler surface in one line.
  • flare.testing.fork_server helper for cookbook examples + integration tests.
  • Request.test_get / Request.test_post factories.
  • Examples regrouped into basic/ (14) / intermediate/ (20) / advanced/ (14); every example runs under pixi run tests.
  • Mojo typed errors adopted as the v0.7+ default error style across the public surface.

Hot-path performance work

  • Comptime PHF dispatch for ~70 standard HTTP headers.
  • Interned HTTP method names + common header values.
  • SIMD-friendly parser primitives.
  • Skip-header-decode fast-path for short requests.
  • Byte fast-path for Connection: keep-alive / close.
  • memcpy compaction for read_buf prefix-drop on pipelined keep-alive.
  • Tagged-pointer single-dict dispatch in the unified reactor.
  • serve_static_multicore — N-worker static fast path.
  • Per-worker BufferPool with 4 size classes, DateCache, ResponsePool (with Response.reset).
  • writev(2) vectored I/O primitive in the runtime.
  • FLARE_REUSEPORT_WORKERS=1 default for num_workers >= 2 — matches actix_web's listener strategy. Set =0 to opt into the shared-listener EPOLLEXCLUSIVE shape (uniformly tighter p99.99 σ under sustained load, for 7–22 % less req/s depending on path).

Safety + harness

  • debug_assert[assert_mode="safe"] coverage on every FFI / unsafe-pointer boundary. mojo build -D ASSERT=none for production builds.
  • OwnedDLHandle lifetime hardened across every FFI shim (post-Mojo 1.0.0b1 destructor-ordering tightening).
  • ASan + asserts-all sanitizer harness across the unified HTTP / WS-h2 FFI surface.
  • 24 fuzz harnesses, 5.4M+ runs combined, zero known crashes. New harnesses: fuzz-io-uring-sqe (encoder + decoder), fuzz-h2-continuation, fuzz-h2-rapid-reset, fuzz-huffman-simd (differential parity), fuzz-ws-deflate (codec round-trip + cap honesty).
  • Bench harness stabilisation: 20 s probes, cliff-fanout gate on p99.9 / p99.99, transient-blip retry, absolute-p99 growth gate, post-search validation pass with 8 % back-off on failure.
  • Per-percentile σ over 5 runs in summary.md — σ is the honesty meter for the tail.

Performance

TFB plaintext, wrk2 -t8 -c256 -d30s --latency (coordinated-omission corrected), Linux x86_64, AOT-built with no debug asserts on both sides (mojo build -D ASSERT=none for flare, cargo build --release --locked for the Rust baselines). Latency cells are median ± σ over 5 × 30 s measurement rounds at the calibrated sustainable rate.

4-worker comparison:

Server Req/s p99 (ms) p99.9 (ms) p99.99 (ms)
flare_mc_static 274,514 98.43 ± 406.17 133.63 ± 425.84 148.35 ± 430.22
actix_web 223,847 2.72 ± 0.08 3.18 ± 269.54 7.51 ± 305.02
hyper 215,508 2.83 ± 0.07 3.30 ± 125.23 10.85 ± 147.50
flare_mc 212,246 2.61 ± 0.02 2.93 ± 0.02 3.25 ± 0.10
axum 199,380 2.80 ± 0.14 3.23 ± 5.50 3.58 ± 7.55

flare_mc has the cleanest tail of the entire 4-worker pack — sub-100 µs σ at p99 / p99.9 / p99.99 against σ of 125–305 ms for the Rust baselines at p99.9 / p99.99. flare_mc_static takes the throughput crown at 274k (~23 % over actix_web), with σ surfacing that this rate is sitting at the saturation cliff.

Single-worker comparison:

Server Req/s p99 (ms) p99.9 (ms) p99.99 (ms)
nginx 80,239 3.45 ± 0.07 4.13 ± 0.11 4.80 ± 0.11
flare 71,619 3.01 ± 0.18 3.30 ± 1.49 3.43 ± 5.67
Go net/http 40,173 3.21 ± 0.01 3.74 ± 0.09 4.62 ± 0.32

flare 1w posts 89 % of nginx throughput with a tighter median p99, and 1.78× Go net/http at the same worker count.

Full methodology, calibration gates, and the listener-mode A/B (SO_REUSEPORT per-worker vs shared-listener EPOLLEXCLUSIVE) live in docs/benchmark.md.

Breaking changes from v0.6.0

  • Http2Client and Http2Server are removed. Use HttpClient (with prefer_h2c=True / h2c_upgrade=True / ALPN-aware over TLS) and HttpServer.serve(handler) (which dispatches both wires on the same accept loop). The application surface — Router, Handler, middleware, extractors — is unchanged.
  • HandlerExtractor convenience trait lets you drop the turbofish on r.get[H](path, h). Old turbofish call sites still compile.
  • Mojo 1.0.0b1 baseline. A handful of internal sites that previously held Span[UInt8, _] views over short-lived Lists have been migrated to explicit Span(ptr=..., length=...) so the lifetime tracks the named owner.
  • Conda channels: the pixi.toml workspace channels list https://conda.modular.com/max-nightly first (Mojo nightly). Downstream consumers pinning earlier flare versions should make sure their channel order matches.
  • Linux glibc floor: CI runs on ubuntu-latest with a glibc 2.34 sysroot floor (Ubuntu 22.04 baseline). Older distros may need a newer libc.

##...

Read more

v0.6.0

Choose a tag to compare

@ehsanmok ehsanmok released this 30 Apr 20:06

flare v0.6.0 is the five-track HTTP feature pass: HTTP/2, sessions + signed cookies, middleware + CORS + FileServer + brotli, request body parsing (cookies / urlencoded / multipart), and an EPOLLEXCLUSIVE shared-listener multi-worker scheduler with optional cross-worker handoff. Plus a perf pass on the response hot path and a real macOS arm64 FFI lifetime fix.

HTTP/2

  • RFC 9113 frame codec (flare/http2/frame.mojo): DATA, HEADERS, PRIORITY, RST_STREAM, SETTINGS, PUSH_PROMISE, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION; flag + length validation at parse time.
  • RFC 7541 HPACK encoder + decoder; static + dynamic table; static-table happy path inlined.
  • Connection + stream state machines (flare/http2/state.mojo); h2c upgrade detection on the HTTP/1.1 read path; ALPN dispatch.
  • H2Connection driver wired into HttpServer; new public surface: H2Connection, HpackEncoder, frame codec types under flare.http2.

Request parsing

  • Request/response cookie ergonomics + RFC 6265 cookie jars; Cookies extractor.
  • application/x-www-form-urlencoded parser + Form[T] extractor (parses, validates, and binds to a struct's fields at extraction time).
  • multipart/form-data (RFC 7578) streaming parser + Multipart extractor; bounded buffer, per-part disposition + content-type.

Sessions + signed cookies

  • flare.crypto.hmac — HMAC-SHA256 FFI, RFC 4231 vectors. Uses the same OpenSSL we already link for TLS.
  • signed_cookie_encode / signed_cookie_decode lower-level codec with constant-time tag compare.
  • Typed Session[T] over CookieSessionStore (stateless; signed cookie carries the payload) and InMemorySessionStore (server-side; signed cookie carries the id).

Middleware + content negotiation

  • Logger, RequestId, Compress, CatchPanic — each is a Handler that wraps another Handler, no callback chain. Compiler monomorphises the chain into one direct call sequence per request type.
  • Cors middleware: allowlist + preflight; Access-Control-* header writer.
  • FileServer with HEAD + Range (RFC 9110) support; safe path resolution via _file_exists (now FFI-lifetime-correct on macOS arm64).
  • gzip + brotli content-encoding (RFC 9110 §12.5.3 q-value parser). Brotli encode/decode through libbrotlienc / libbrotlidec.

Multi-worker scheduler

  • HttpServer.serve(handler, num_workers=N) over an EPOLLEXCLUSIVE shared listener on Linux (kernel-balanced accept, no thundering herd).
  • flare.runtime.HandoffQueue — bounded MPSC FIFO of opaque Int tokens guarded by pthread_mutex_t.
  • WorkerHandoffPool + HandoffPolicy.from_env (FLARE_SOAK_WORKERS=on) for application-level work-stealing on skewed-keepalive workloads. Default off; production remains opt-in until v0.7 reactor-side wiring.

Performance

  • Response.__init__ no longer copies the body (was body.copy() per request). New _string_to_bytes does one bulk resize + memcpy for the response builders. _serialize_response writes status + Content-Length via stack itoa (no String(int) heap allocs).
  • TFB plaintext, EPYC 7R32, wrk2 -t8 -c256 -d30s, calibrated peak with --latency (CO-corrected):
Server Workers Req/s p99 ms p99.99 ms
flare_mc (shared listener) 4 170,305 2.38 3.11
flare (reactor) 1 56,086 2.70 3.54

flare_mc holds the best p99 / p99.9 / p99.99 of the four 4-worker frameworks (vs. hyper / axum / actix_web). flare 1w is 88 % of nginx 1w throughput. Apple M-series 1w: ~157K req/s, ~1.10x Go net/http. Full tables in docs/benchmark.md.

Fixed

  • macOS arm64 FFI lifetime bug in flare/http/{middleware,encoding}.mojo. A function-local OwnedDLHandle was reclaimed by Mojo's ASAP-destruction rule before the cached function pointer was invoked; dlclose ran first and the pointer dangled, segfaulting the runtime under compress_brotli / decompress_brotli / _file_exists. Both call sites now route through borrow-the-handle helpers (read lib: OwnedDLHandle), the same idiom the zlib path already uses.
  • CI macOS leg bumped from macos-14 to macos-15 (Sequoia, arm64); mandatory again, no advisory mode, no retry loop.
  • 45 mojo warnings across tests / examples / lib audited to zero (deprecated aliascomptime, from os import *from std.os import *, implicit Int → UInt8, etc.).

Breaking

  • Response.__init__ now takes var body / reason / version and moves them in. Direct callers passing body=foo need body=foo^. The ok / ok_json / bad_request / not_found / internal_error helpers are unaffected.

Public API reorganisation

  • Top-level flare/__init__.mojo re-exports the common surface so from flare import HttpServer, Router, Request, Response, Handler, ok, ok_json, SocketAddr, IpAddr, HttpClient, get, post, num_cpus, default_worker_count resolves without reaching into sub-packages. Sub-package __init__.mojo files (flare.http, flare.http2, flare.runtime) aligned with the same versionless tone.
  • All internal version refs (v0.X.Y, track / phase / step identifiers, design-doc cross-references) stripped from public docstrings; mojo doc output now describes behaviour, not roadmap.

Compatibility & infra

  • mojo == 1.0.0b1.dev2026042717 pinned across pixi.toml + recipe.yaml.
  • Inlined json source bumped to v0.1.5 (same Mojo pin, verified against ehsanmok/json@v0.1.5/recipe.yaml).
  • mozz (fuzz harness runtime) bumped to v0.1.2.
  • CI: ubuntu-22.04 + macos-15, both mandatory.
  • Fuzz: 22 harnesses, 5M+ cumulative runs, zero known crashes (new this release: fuzz-form, fuzz-multipart, fuzz-session-decode, fuzz-fs-range, fuzz-h2-frame, fuzz-hpack-decoder).
  • Tests: 961 across 60 files + 36 runnable examples (every example part of pixi run tests).

Docs

  • README rewritten: feature list refreshed for v0.6, Quick start now exposes complexity gradually (Beginner: router + path params; Intermediate: typed extractors + middleware shape; Advanced: Cancel, ComptimeRouter, App[S] + middleware composition).
  • docs/cookbook.md: 10 new "I want to..." rows for examples 27–36 (request cookies, urlencoded forms, multipart uploads, signed-cookie sessions, middleware stack, CORS, FileServer, brotli, HTTP/2 driver, work-stealing).
  • docs/architecture.md: layer diagram now lists flare.http2 and flare.crypto as first-class modules; the v0.5 placeholder flare.h2 (planned) line is gone.

Install

[dependencies]
flare = { git = "https://github.com/ehsanmok/flare.git", tag = "v0.6.0" }

v0.5.0

Choose a tag to compare

@ehsanmok ehsanmok released this 28 Apr 00:23

Operational core

  • Request.peer: SocketAddr + Peer extractor; the kernel's view of the connecting peer threaded onto every parsed request.
  • Sanitised 4xx / 5xx response bodies (parse-error messages logged with request id, never echoed). Local-dev opt-out via ServerConfig.expose_error_messages = True.
  • Cancel token (peer FIN, timeout, drain unified through one cell). CancelHandler trait + WithCancel[H] adapter for handlers that opt into cooperative polling.
  • Per-request deadlines: read_body_timeout_ms enforced end-to-end on the cancel-aware reactor read path; handler_timeout_ms and request_timeout_ms configured + asserted at construction.
  • HttpServer.drain(timeout_ms) -> ShutdownReport on the single-threaded reactor; multi-worker Scheduler.drain returns one ShutdownReport per worker.

Buffer ownership + streaming

  • Router accepts Handler structs (not just def functions).
  • Concrete extractors (PathInt / PathStr / PathFloat / PathBool + Query / Header / Optional variants) — .value is the parsed primitive directly.
  • HeaderMapView[origin] zero-copy header storage on the read path; RFC 7230 § 3.2.4 / § 3.2.6 token + field-value validation at parse time.
  • Pool[T] typed allocator (replaces the _Boxed[H] pad after public size_of[T]()).
  • RequestView[origin] zero-copy request reads on the cancel-aware reactor; ViewHandler trait + HttpServer.serve_view[VH] entry point.
  • Streaming bodies: Body / ChunkSource traits, InlineBody, ChunkedBody[Source], StreamingResponse[B: Body] sibling type, RFC 7230 chunked serializer with lowercase hex chunk-size lines.
  • Cross-thread Cancel.SHUTDOWN flip via worker-self-walks-conns (no shared-state mutex).

Server-side TLS

  • TlsAcceptor + TlsServerConfig + TlsInfo. ALPN selection callback, mTLS opt-in with construction-time validation, cert reload without restart (acceptor.reload()), blocking-poll handshake_fd(fd) entry point.
  • Request.tls_info: Optional[TlsInfo] populated from the live handshake.
  • Server-side OpenSSL FFI surface: nine flare_ssl_* C exports covering SSL_CTX_new_server, SSL_accept, peer-cert / cipher / version / ALPN getters, ctx_reload, set_verify_client_cert.

Blocking-escape hatch

  • block_in_pool[T](work, cancel) raises -> T runs blocking syscalls on a fresh kernel thread (per-call pthread_create + pthread_join). Cancel contract: pre-flight + post-flight checks; user code polls cancel inside work() for mid-flight short-circuit. Crash isolation: a segfault in work() kills its own pthread, not the reactor's.

Bench + soak

  • Bench harness fully on wrk2 (CO-corrected), two-phase (find-peak then sustain at 90 % of peak). Per-run --latency distribution: p50 / p75 / p90 / p99 / p99.9 / p99.99 / p99.999.
  • Five workloads ship: micro-static, mixed_keepalive, uploads, downloads, slow_clients, churn.
  • Soak harness (bench-soak-*): three-tier slow-client / churn / mixed-load. Smoke (60 s/workload), extended (5 min/workload), 24 h release-gate via SOAK_DURATION_SECS=86400. Per-workload gates: zero non-2xx, rss_end <= 2 * rss_start, fd_end <= fd_start + 16.

Numbers (Linux EPYC 7R32, single-worker, plaintext)

  • ~80K req/s, on par with nginx (worker_processes 1), ~1.96× Go net/http (GOMAXPROCS=1).
  • flare_mc 4 pinned workers: 4.38× the single-thread reactor (near-linear SO_REUSEPORT scaling).
  • Tail at sustained 90 %-of-peak load: p50 ~ 1.2 ms, p99 ~ 3.1 ms, p99.99 ~ 3.8 ms.

Compatibility & infra

  • mojo == 0.26.3.0.dev2026042005 pinned across pixi.toml + recipe.yaml.
  • json dep bumped to v0.1.4; OpenSSL pinned at major 3.
  • CI Linux runners pinned to ubuntu-22.04 (Mojo runtime crash inside libKGENCompilerRTShared.so on ubuntu-latest / 24.04 — works around a documented glibc-2.39 ABI break).
  • CI generates the self-signed cert before pixi run tests (TLS test cert paths are repo-relative build/tls-bench-certs/).

Docs

  • README leads with ## Features + ## Numbers (full-library framing, not v0.5-changelog).
  • docs/benchmark.md carries methodology + single-worker / multi-worker discipline (no apples-to-oranges multi-vs-single ratios) + soak harness as ## Soak: long-running operational gates.
  • docs/cookbook.md, docs/architecture.md, docs/security.md updated.
  • Public docs are version-agnostic across the board.

Mojo-blocked, deferred to the v0.5.x patch line (do NOT block this tag)

  • install_drain_on_sigterm / install_sigterm_handler — Mojo "global variables are not supported"; production deployments wire their own signal(2) handler today (see examples/23_drain.mojo).
  • Reactor-state-machine TLS handshake (STATE_TLS_HANDSHAKE advanced via on_readable / on_writable) — gated on parametric trait method specialisation cost in a future Mojo nightly. Blocking-poll handshake_fd ships today.
  • Reactor pull-edge for StreamingResponse[B] — same parametric-trait specialisation block. Streaming serializer + handler-driven chunked body ship today; reactor-driven pull-loop integration is the follow-up.

v0.4.1

Choose a tag to compare

@ehsanmok ehsanmok released this 25 Apr 01:18

Comptime + extractor track

  • Typed extractors with reflective auto-injection (Extracted[H] works with any Handler; HandlerStruct trait removed).
  • Comptime route trie via ComptimeRouter; handlers carried inline (no more set_handler).
  • Pre-encoded literal responses via serve_static.
  • SIMD-width-parametric header scanner.

API breaks (within 0.x line)

  • QueryOpt / HeaderOpt renamed to OptionalQuery / OptionalHeader.
  • HandlerStruct trait removed; existing handlers continue to work via Extracted[H].

Compatibility & infra

  • Mojo 1.0.0b1 compatibility fix in _server_reactor_impl.
  • Native allocator pair for heap cells (unblocks mozz fuzz environment).
  • Source-only conda distribution (drops mojo package); pinned mojo == 0.26.3.0.dev2026042005.

Docs

  • README selling-points humanized; Quick Start reordered for gradual disclosure; three example bugs fixed; extractor / comptime-trie / static / SIMD highlights added.

v0.4.0

Choose a tag to compare

@ehsanmok ehsanmok released this 21 Apr 23:23

Composable HTTP release. Same v0.3.0 reactor underneath, one unified entry point (srv.serve(handler, num_workers=N)), and thread-per-core scaling that hits a measured 257,461 req/s at 4 workers on Linux EPYC: 4.4x linear, 3.6x nginx (1 worker), 7x Go net/http.

Headline: multicore reactor

var srv = HttpServer.bind(SocketAddr.localhost(8080))
srv.serve(router^, num_workers=default_worker_count())

One call. Same function as the single-threaded reactor, one extra argument. num_workers=1 (the default) keeps the v0.3.x hot path; num_workers >= 2 binds N SO_REUSEPORT listeners on N pthread workers via flare.runtime.scheduler.Scheduler, with optional per-core pinning on Linux (pin_cores=True by default, no-op on macOS). Shared-nothing per-connection ownership, no locks on the hot path.

Linux multicore benchmark (AWS EPYC 7R32, wrk -t8 -c256 -d30s, 5-run median, stdev <= 3%)

Server Req/s (median) p50 p99 vs Go net/http vs 1-thread flare
flare_mc (4 workers, pinned) 257,461 0.97 ms 1.58 ms 7.03x 4.38x
nginx (1 worker) 70,592 3.53 ms 4.23 ms 1.93x 1.20x
flare (single-threaded) 58,731 4.64 ms 1.60x 1.00x
Go net/http (GOMAXPROCS=1) 36,617 1.00x 0.62x

Near-linear scaling: each of the four pinned workers runs its own un-contended reactor. p99 tail latency collapses from 4.64 ms (single-thread flare under 256 concurrent connections) to 1.58 ms (multicore), and the flare-vs-Go gap widens from ~2x (single-thread) to 7x (4 workers) because Go net/http + netpoll overhead grows faster with concurrency on a slower EPYC core than flare's SO_REUSEPORT sharding does.

macOS loopback saturates at ~140K req/s regardless of worker count because wrk and the server share one client-side CPU; use Linux for multicore numbers.

Single-thread performance (carried over from v0.3.0)

  • Linux (AWS EPYC 7R32): on par with single-worker nginx, about 1.96x Go net/http (GOMAXPROCS=1).
  • macOS (Apple M-series): about 1.10x Go net/http (GOMAXPROCS=1).

Same reactor, same kqueue/epoll code path, no throughput regression. Intra-platform ratios are the invariant across Mojo nightlies.

Handlers, routing, state

  • Handler trait with a blanket impl, so every existing def(Request) raises -> Response handler is already a Handler.
  • Router with get / post / put / patch / delete, path params (/users/:id), wildcard tails (/files/*), auto 405 with Allow: + 404.
  • App[S: Copyable, H: Handler] + State[T] view for handing application state to middleware layers.
  • Middleware is a Handler that wraps another Handler. [Inner: Handler] generic structs, so composition monomorphises into one direct call chain at compile time. See examples/18_middleware.mojo for a five-layer production pipeline (RequestID -> Logger -> Timing -> Recover -> RequireAuth -> Router).
  • req.param(name), req.has_param(name), req.has_params(). Path params live on a lazily-allocated pointer so handlers that never route pay zero Dict allocation per request.

Compile-time server wiring

  • HttpServer.serve[H: Handler & Copyable](handler, num_workers, pin_cores): unified entry point, monomorphised against the concrete handler type.
  • HttpServer.serve_comptime[handler, config] takes both the handler and ServerConfig as comptime parameters and runs every invariant (max_body_size >= max_header_size, max_keepalive_requests >= 1, idle_timeout_ms >= 0, etc.) through constrained[...]. Misconfigured servers fail the build, not the first request.
  • FnHandlerCT[F] zero-size trait adapter for binding a function into the handler surface at compile time.

What's in

  • 460 tests, 18 runnable examples, 16 fuzz harnesses, over 1M aggregate fuzz runs, zero known crashes
  • New examples: 15_router, 16_state, 17_multicore, 18_middleware
  • New test tasks: test-handler, test-router, test-server-handler, test-app-state, test-serve-comptime, test-thread-ffi, test-reuseport, test-scheduler, test-server-multicore
  • New fuzz harnesses: fuzz_router_paths (200K runs), fuzz_scheduler_shutdown (10K runs)

What's deferred

  • Typed extractors (Path[T], Json[T], Query[T], Header[T]), comptime route trie, pre-encoded literal responses, SIMD-width-parametric header scanner: v0.4.1
  • HTTP/2 (h2 over TLS, RFC 9113 + 7541): v0.4.2
  • Streaming bodies (Body + ChunkSource traits): v0.4.3+

Install

[dependencies]
flare = { git = "https://github.com/ehsanmok/flare.git", tag = "v0.4.0" }

Pinned to Mojo 0.26.3.0.dev2026042005 + json v0.1.3.

Full changelog

v0.3.0...v0.4.0

v0.3.0

Choose a tag to compare

@ehsanmok ehsanmok released this 19 Apr 15:03

flare v0.3.0 cuts over to a single-threaded reactor architecture. The HTTP
server goes from ~50K req/s blocking to ~140-157K req/s on an event loop,
and the new flare.runtime subpackage exposes the reactor primitives
(Reactor, Event, TimerWheel) so you can build other protocols on the
same foundation.

Public API is byte-for-byte compatible with v0.2.0. HttpServer.bind() +
srv.serve(handler) just works, only now under a kqueue/epoll loop.

Headline

  • Single-threaded reactor HTTP server (kqueue on macOS, epoll on Linux).
    On Linux AWS EPYC: on par with single-worker nginx and ~2x Go's
    net/http. On Apple M-series: ~1.1x Go's net/http. Measured with
    the standard TFB plaintext test, GOMAXPROCS=1 and worker_processes 1.
  • 375 tests and 15 fuzz harnesses (over a million fuzz runs, zero
    known crashes).
  • Four new examples for UDP, TLS, cookies, and direct reactor usage.
  • Pinned toolchain for reproducibility: Mojo 0.26.3.0.dev2026041805 +
    json v0.1.2 (both built by the same compiler).

Benchmarks

macOS, Apple M-series, single-threaded, wrk -t1 -c64 -d30s, 5-run
median of middle 3 with stdev ≤ 3%.

Server Req/s (median) p50 p99 vs Go net/http
flare (reactor) 157,459 0.39 ms 0.80 ms 1.10x
Go net/http (1 thread) 143,500 0.44 ms 0.86 ms 1.00x

Linux, AWS EPYC 7R32 (64 vCPU).

Server Req/s (median) p50 p99 vs Go net/http
nginx (1 worker) 81,612 0.40 ms 0.79 ms 2.00x
flare (reactor) 79,965 0.78 ms 1.53 ms 1.96x
Go net/http (1 thread) 40,739 1.59 ms 3.10 ms 1.00x

Reproduce locally:

pixi run --environment bench bench-vs-baseline-quick   # flare vs Go, ~7 min
pixi run --environment bench bench-vs-baseline         # + nginx, + latency floor

See the README benchmarks section
for the full methodology (integrity check, pinned toolchains, warmup
protocol, per-run provenance dir).

What's new since v0.2.0

flare.runtime (new subpackage)

  • Reactor — uniform API over kqueue (macOS) and epoll (Linux),
    dispatched at compile time. Register fds with a token and interest
    bits, poll for readiness, wake from another thread.
  • TimerWheel — hashed timing wheel (512 slots + overflow) with O(1)
    schedule / cancel. Used for per-connection idle timeouts.
  • Event + INTEREST_READ / INTEREST_WRITE / EVENT_READABLE /
    EVENT_WRITABLE / EVENT_ERROR / EVENT_HUP / WAKEUP_TOKEN.

flare.http.HttpServer (rewritten)

  • Event loop replaces the blocking _handle_connection_buffered path.
    Old code fully removed. Public API unchanged: every existing
    test_server.mojo test passes without modification.
  • Per-connection state machine in flare/http/_server_reactor_impl.mojo:
    READING → WRITING → keep-alive loop → CLOSING.
  • Configurable idle_timeout_ms, write_timeout_ms,
    shutdown_timeout_ms per connection; hashed timing wheel enforces
    them without extra syscalls per request.
  • Graceful shutdown: HttpServer.close() stops accepting new
    connections and drains in-flight ones up to shutdown_timeout_ms.

Parser and serialiser cleanup

  • _read_line_buf: single-scan plus one-shot String(unsafe_from_utf8=...)
    construction instead of the per-byte line += chr(...) loop.
    Biggest single throughput win.
  • _is_content_length / _is_connection: inline case-insensitive
    byte compare, avoiding the per-header _ascii_lower allocation.
  • _ascii_lower / _lower / _append_str: bulk write through
    pre-sized buffers, memcpy replaces per-byte loops everywhere.
  • _ascii_strip_slice: single-allocation strip replacing the
    String(String(...)).strip() triple per header value.

New examples (all runnable via pixi run example-<name>)

  • 11_udp.mojoUdpSocket.bind + send_to + recv_from round
    trip, plus DatagramTooLarge.
  • 12_tls.mojoTlsConfig, TlsStream.connect to a real HTTPS
    endpoint, raw TLS handshake + minimal HTTP/1.0 GET, plus the
    TlsVerify.NONE escape hatch.
  • 13_cookies.mojoCookie, CookieJar, parse_cookie_header
    (client side), parse_set_cookie_header (server side) with
    Max-Age, Path, Secure attributes.
  • 14_reactor.mojo — direct flare.runtime.Reactor usage so you
    can build non-HTTP protocols on the same event loop.

Rigorous benchmark harness

  • pixi run --environment bench bench-vs-baseline orchestrates
    integrity check + 5-run measurement across flare, Go net/http,
    nginx. 3% stdev gate, full env capture per run, TFB plaintext
    workload.
  • Go and nginx versions pinned via [feature.bench.dependencies] in
    pixi.toml so the comparison cannot drift silently.

Fuzz surface

  • New harnesses: fuzz-reactor-churn (reactor register/modify/
    unregister/poll churn, 200K runs), fuzz-server-reactor-chunks
    (random HTTP-ish bytes fed to the state machine, 30K runs),
    prop-timer-wheel (property test for the timing wheel, 100K runs).
  • Existing harnesses unchanged, all still at 0 crashes.

Packaging and tooling

  • Pixi environments layered: default (lean, runtime only),
    dev (+ mojodoc, pre-commit), fuzz (+ mozz), bench
    (+ Go, nginx, wrk).
  • pixi run format moved behind -e dev so the lean default env
    stays small for users and CI.
  • json dep pinned to tag = "v0.1.2", both sides built against
    Mojo 0.26.3.0.dev2026041805. Same pin in recipe.yaml for
    rattler-build consumers.

Upgrading from v0.2.0

Your code does not need to change. The public API is the same. If you
had tag = "v0.2.0" in pixi.toml, bump to tag = "v0.3.0":

[dependencies]
flare = { git = "https://github.com/ehsanmok/flare.git", tag = "v0.3.0" }

Then pixi install. You're on the reactor.

Known caveats

  • nginx baseline on macOS is still flaky. The pinned nginx config
    occasionally fails to come up under macOS Pixi; the harness reports
    the row as broken when it happens. The Go baseline is reliable on
    both platforms and is the authoritative comparison. Linux nginx is
    stable.
  • Linux numbers in the README footnote are un-pinned wrk / server
    processes on a 64-vCPU EPYC instance
    , so absolute req/s is
    microarchitecture-dependent. Intra-platform ratios (flare vs Go,
    flare vs nginx) are the apples-to-apples comparison, not the cross-
    platform req/s.
  • HTTP/2 is not here yet. Scheduled for v0.4.0 alongside
    thread-per-core (SO_REUSEPORT). See the development notes for the
    v0.4.0 scope.

Commit range

v0.2.0...v0.3.0
is about 30 atomic commits covering the whole reactor migration:
epoll / kqueue / eventfd / pipe FFI, the flare.runtime.Reactor and
TimerWheel abstractions, the per-connection state machine, the
hard replacement of HttpServer, the rigorous benchmark harness, and
the parser / serialiser allocation cleanup.

Thanks for reading. If you hit anything weird, open an issue with
your pixi info output and the exact command that broke.