Skip to content

v0.4.0

Choose a tag to compare

@ehsanmok ehsanmok released this 21 Apr 23:23
· 1042 commits to main since this release

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