Architecture
Understanding how NANO works under the hood
High-Level Architecture
NANO consists of a Rust-based runtime that embeds V8 to execute JavaScript in isolated environments.
[Client Request]
|
v
[HTTP Router] --> [App Registry]
| |
v v
[Worker Pool] <-- [Isolate Cache]
|
v
[V8 Isolate] --> [JavaScript Execution]
V8 Isolates
Each application runs in its own V8 isolate, providing complete isolation from other applications while sharing the same OS process.
Isolate Properties
- Memory limit: Configurable heap size per isolate
- Context reset: ~5ms between requests (not full recreate)
- Thread-local: Isolates never move between threads
Sliver Bundles
A sliver is a portable tar bundle of an app's files plus optional V8 bytecode compiled at pack time. It carries no heap snapshot; the app runs from its VFS source and uses the bytecode to skip parse + compile when the V8 version matches.
1. Collect the app's files into vfs/ 2. Compile the entrypoint JS to V8 bytecode 3. Record the V8 cache-version tag in meta.json 4. Write meta.json + vfs/ + bytecode.v8bc into a tar At serve time: if the bytecode's version tag matches the running V8, the first request skips JavaScript parse+compile.
The serving path runs from the sliver's VFS source plus the optional precompiled bytecode; it does not restore an isolate from a baked V8 heap snapshot.
Worker Pool
The worker pool manages concurrent request handling with thread-local isolates.
Request Flow
- HTTP request arrives at router
- Router determines target app from hostname/path
- Request is queued for worker pool
- Available worker picks up request
- Worker resets isolate context (~5ms)
- JavaScript handler executes
- Response is returned to client
WebSocket Upgrade Path v2.0a
WebSocket connections use a dedicated worker thread per connection. The isolate lifetime is bound to the connection — when the connection closes, the isolate is recycled.
HTTP GET + Upgrade: websocket
|
v
detect_ws_upgrade() — router.rs
| axum 101 handshake
v
tokio relay task — WsChannels (mpsc)
| WsInbound / WsOutbound
v
TenantPool::dispatch_ws()
| lazy-spawn WS worker thread
v
JS fetch() handler (registers addEventListener)
|
v
'ws_messages loop
| recv_timeout(idle_timeout_ms) per frame
| CpuTimeoutGuard + OOM check per message
v
Close/Disconnect → break 'requests → isolate recycled
Key Properties
- One isolate per connection: Worker thread dedicated to single WS connection
- Isolate recycled on close: Fresh isolate for next connection — no state leakage
- Per-frame enforcement: CPU timeout guard and OOM check on every message
- Idle timeout: Connection closed after
ws_idle_timeout_mswith no frames (default 60s) - Max message size: 32 MiB default (
ws_max_message_bytes)