summaryrefslogtreecommitdiff
path: root/docs/design.md
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-20 03:53:17 -0300
committerGabriel Schneider <[email protected]>2026-09-20 03:53:17 -0300
commitf1b53c1533539aecbf16ad19fd9156deae091f92 (patch)
tree38ff00810ef5e0ad271c26e218cf6af4578c71ce /docs/design.md
parentba7ec40782ba7020d82a56896a5eb1b52578d6aa (diff)
downloadcloud9-f1b53c1533539aecbf16ad19fd9156deae091f92.tar.gz
cloud9-f1b53c1533539aecbf16ad19fd9156deae091f92.zip
9web: multiplexer, HTTP view of the tree, live streams, richer page
One upstream 9P connection now serves any number of browser WebSocket sessions and, with --serve, plain 9P clients over TCP or Unix (a 9pserve- style frame remux: tags and fids remapped, Tflush forwarded, reconnect on upstream loss). /fs/<path> maps HTTP onto the tree: GET file or directory (JSON or HTML), Range, HEAD with 9P headers, PUT (create, truncate, append, trailing slash makes a directory), DELETE, and ?follow=1 or text/event-stream turning a blocking read into server-sent events with Tflush on disconnect. The page gains a lazy tree, stat panel, create, rename, delete, upload and follow mode. --probe embeds 9proc for self-introspection. New mux-test and http-fs-test steps; e2e still passes. Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'docs/design.md')
-rw-r--r--docs/design.md47
1 files changed, 47 insertions, 0 deletions
diff --git a/docs/design.md b/docs/design.md
index 3ad9338..f34a3ea 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -173,6 +173,53 @@ that waits on another thread must stop waiting once `stop()` has begun.
Unix socket paths are the application's: the runner neither unlinks,
chmods nor removes them.
+# Multiplexer (9web)
+
+`9web` (`web/`) is a gateway and a 9P multiplexer. It keeps **one** upstream
+connection — a single `cloud9.Client`, all sixteen tags — and fans it out to
+any number of downstream sessions: the browser WASM client over a WebSocket at
+`/_cloud9/9p`, plain 9P clients over an optional `--serve` TCP/Unix listener,
+and an HTTP view of the tree under `/fs/`. `web/mux.zig` owns the shared
+upstream; `web/httpfs.zig` is the HTTP view; `web/probe.zig` embeds 9proc under
+`--probe`.
+
+The mux is a **frame-level remux**, not an `fs.Server`/`serve.Runner` backend.
+Each downstream request is forwarded upstream on a remapped tag with its fids
+remapped into the shared upstream fid space, and each upstream reply is routed
+back by tag. `Tversion` is answered locally (the upstream is negotiated once at
+connect); `Tattach` is forwarded, so each downstream gets its own upstream tree
+root; `Tflush` is forwarded as `Tflush`. Downstream fid spaces are isolated by
+construction — two downstreams never share an upstream fid. The choice is
+deliberate: the file-server engine re-decomposes each request into filesystem
+operations and re-encodes directories with synthetic stats and an entry-index
+cursor, which would lose the upstream's real directory stats, iounit and qids
+and turn the readdir byte offset into an index mapping. Forwarding frames
+unchanged is exactly the "forward each request upstream" contract, the shape
+plan9port's 9pserve has, and it keeps the upstream's bytes intact. `serve.Runner`
+remains the right tool for a *server* whose backend is a real filesystem; a
+transparent *proxy* is not that.
+
+The shared upstream runs one reader task and any number of downstream forwarder
+tasks under one mutex; the tag, fid and pending tables are comptime-sized and
+nothing allocates per request. Sockets are only written under the mutex with the
+client's own output buffer (disjoint from the stream writer's), and replies are
+encoded into per-tag buffers and sent to downstreams outside the lock so a slow
+downstream cannot stall the client. The WebSocket and `--serve` downstreams
+drive the mux the same way — each reads whole frames (WebSocket binary messages
+or `transport.readFrame`) and calls `forward`; the runner's socket-only listener
+is not used for them because WebSocket framing and the HTTP `/fs` view do not fit
+it. When all sixteen upstream tags are in flight, a further downstream request
+waits for one to free (Tflush keeps its reserved seventeenth tag). On an upstream
+I/O failure the reader fails every outstanding request, reconnects, bumps a
+generation, and answers any downstream request that still names a pre-reconnect
+fid with EIO until it re-attaches.
+
+The HTTP `/fs` view uses the same shared upstream at the fid level through a
+blocking `rpc`: it allocates upstream fids from the mux pool and walks, stats,
+reads and writes directly, so `GET`/`PUT`/`DELETE`, directory JSON, `Range` and
+`?follow=1` server-sent events all ride the one upstream connection. A parked
+`follow` read is cancelled with an upstream `Tflush` when its client goes away.
+
# Related programs
Programs built on the library ship from this repository as `cloud9/<name>/`,