m0serve Realtime from a synchronous Python app

Design notes

Long-form, dated notes on what was built and measured, what was refused and why, and the post-mortems, kept as written.

  • A conformance-suite tier

    The server is pinned by hand-written probes against the RFC text -- smoke-ws speaks RFC 6455 from stdlib sockets, test_parsing.mojo covers the smuggling shapes directly -- and by no external suite on any cadence. A…

  • The desktop-Mac server, and what the wheel gives up to ship

    position may be as a desktop server — a Mac mini or Studio running an application on hardware someone already owns — rather than as a Linux container competing with Granian and uvicorn on rps. The reasoning is that…

  • The Django server aims

    Where the WSGI work is headed, and what gates each step — the full analysis with evidence is the design

  • The drain does not read a request body in flight — resolved

    Found by the soak driver's uploads population on color-separation (2026-09-02): with 9.7 MB multipart uploads in flight, every SIGTERM drain took exactly its 5 s budget, and the bisection put it on uploads alone.…

  • v0.1.0: the first release

    backpressure and Last-Event-ID replay, auth, CORS, config, health, JSON-lines access logging, graceful shutdown, multi-worker fork

  • Hold on a pool thread: the refusal that keeps --realtime off real applications

    (REAL_APP_VALIDATION.md, 2026-08-26) showed the realtime mode is not deployable for the application class it exists for, and the reason is a refusal this server makes on purpose: --realtime refuses --blocking-threads,…

  • Inbound WebSocket flow control — shipped 2026-08-31

    The outbound direction was credit-gated (websocket.send awaits its window) and the inbound direction had no backpressure of any kind. Once the executor's submit channel filled, WSGIHandler.ws_message discarded each…

  • The loop inversion — in progress 2026-08-28

    The handoff's item 1: run the Mojo loop's pass as a callback inside the executor's run_forever, on one thread, so a request goes parse → app → response with no datagram and no cross-thread wake. At c16 the pump batches…

  • The Mojo handler pool — shipped 2026-08-28

    The offload pool, for handlers written in Mojo. Planned as a kill-criterion spike: if a slow Mojo handler did not strand the connections behind it the way a slow Python view does, the branch was to be deleted and this…

  • Mojo language capabilities, surveyed 2026-08-28

    A pass over what the tree uses of the language, prompted by "are we fully tapping Mojo?". The short answer is yes wherever it was measured to pay — SoA span-based headers (+72%, the largest single win here), SIMD…

  • The outbox sweep — taken, scoped (2026-08-29)

    The second lever the inversion entry named. Every pass swept all 1,024 slots for a streaming one to drain, and the miss path — two flag loads per slot, none set — measured 1.2–1.3 µs per pass in isolation, on a pass…

  • Proven once, unloaded: an inventory of the gates with that shape

    The v0.15.1 bug came from a shape rather than an oversight: proven once, by a smoke, and stressed not at all. The measure of how little that guarantees is exact. Reverting the websocket.send credit gate was caught on…

  • Pacing the pump's loop thread

    What the sweep measurement exposed: at c16 the pump's throughput depends on how long its loop thread spends per pass, in a way that an accidental 1.2 µs improved by 3%. An explicit pause before flushing a partial batch…

  • A request body still arriving at SIGTERM held the drain to its deadline — resolved

    The drain loop read nothing new, so a half-received upload was neither completed nor closed until the 5 s budget expired: the client was reset and the process exited at 5.09 s. Found by the soak driver's uploads…

  • Considered, not built: routes that carry a function

    Router.match returns an Int and the caller dispatches on it, which is why three of five Mojo apps skip the router and hand-write if path == …. Route-to-function is reachable on Mojo 1.0 — verified by spike, not assumed.…

  • Streamed WSGI bodies — shipped 2026-08-27

    The last buffered shape. A generator the application did not size — Django's StreamingHttpResponse, the thing every Django SSE tutorial returns — was joined whole by the shim, so a never-ending one never answered and…

  • Hardening the streaming seam — shipped 2026-08-27

    0.14.0 shipped a fix for a hang that had been live all day: the shim keyed a connection slot's executor state — credit window, event, disconnect mark — by SLOT, while the loop recycles a slot the instant it closes a…

  • Structured CI results

    to $M0_RESULTS as one JSON line; the smoke job renders them into the run summary with a headroom column and uploads ci-results-<os>. Five sites are instrumented: the WSGI and ASGI RSS guards, the pool's fast-request…

  • Traceability: stable ids, then declared coverage

    SPEC.md is a requirements traceability matrix -- the standard artifact in safety-critical software, whose defining property is that it traces BOTH ways: every capability to its evidence, and every piece of evidence back…

  • The WebSocket close path RSTing instead of FINning — resolved v0.15.1

    Listed as a suspected race for two sightings, both on macOS CI and never locally. It was not a flake: RFC 6455 §5.5.1 requires the endpoint that sends Close to WAIT to receive one, and the loop closed as soon as its own…

  • The WebSocket send window — shipped 2026-08-28

    The one path above that an ordinary application reaches, made to work rather than merely to fail honestly. websocket.send had no window: an app faster than its client filled the loop's 64 KB per-slot outbox and every…

  • WSGI vs ASGI: the design record

    The obvious roadmap item after a conformance-tested WSGI host is an ASGI host. This document is the case for not building one now. The short form: ASGI answers two independent questions — how does Python code overlap…