Request Flow
This page traces an HTTP request from the moment it arrives at the ingress listener to the moment the response is delivered back to the client.
Sequence Diagram
Section titled “Sequence Diagram”sequenceDiagram
participant Client
participant Ingress as Ingress Listener
participant RL as Rate Limiter
participant Sem as Connection Semaphore
participant Body as Body Validator
participant Router as Ingress Router
participant Registry as Function Registry
participant Pool as Isolate Pool
participant Thread as Isolate Thread (V8)
participant Handler as JS/TS Handler
Client->>Ingress: HTTP request
Ingress->>RL: Check rate limit
RL-->>Ingress: Allow / Deny (429)
Ingress->>Sem: Acquire connection permit
Sem-->>Ingress: Permit granted / Rejected (503)
Ingress->>Body: Validate body size
Body-->>Ingress: OK / Rejected (413)
Ingress->>Router: Forward request
Router->>Router: Extract function name from first path segment
Router->>Registry: Lookup function
Registry-->>Router: Function handle / Not Found (404)
Router->>Pool: Request isolate for function
alt Warm isolate available
Pool-->>Router: Return warm isolate
else No warm isolate
Pool->>Thread: Spawn new isolate (cold start)
Thread-->>Pool: Isolate ready
Pool-->>Router: Return new isolate
end
Router->>Thread: Dispatch request to isolate thread
Thread->>Handler: Invoke JS/TS handler
Handler-->>Thread: Response object
Thread-->>Ingress: Stream response
Ingress-->>Client: HTTP response
Thread->>Thread: Resource cleanup
Thread->>Pool: Return isolate to pool
Step-by-Step Walkthrough
Section titled “Step-by-Step Walkthrough”1. Connection acceptance
Section titled “1. Connection acceptance”The ingress listener accepts the TCP connection (plain or TLS). If TLS is configured, the DynamicTlsAcceptor performs the handshake using the current certificate.
2. Rate limiter check
Section titled “2. Rate limiter check”A token-bucket rate limiter keyed by source IP decides whether the request is allowed. If the bucket is exhausted, the server responds immediately with 429 Too Many Requests.
3. Connection semaphore
Section titled “3. Connection semaphore”A bounded semaphore limits the total number of concurrent in-flight requests. If no permit is available, the server responds with 503 Service Unavailable. This protects downstream isolates from being overwhelmed.
4. Body size validation
Section titled “4. Body size validation”The Content-Length header (or streaming body size) is checked against the configured maximum. Oversized payloads are rejected with 413 Payload Too Large before any bytes are buffered into memory.
5. Function name extraction
Section titled “5. Function name extraction”The ingress router extracts the function name from the first segment of the request path. For example, a request to /my-function/api/hello resolves to the function named my-function. The remainder of the path (/api/hello) is forwarded to the handler as the local path.
6. Registry lookup
Section titled “6. Registry lookup”The FunctionRegistry is consulted using a lock-free DashMap read. If no function is registered under the extracted name, the server responds with 404 Not Found.
7. Isolate selection
Section titled “7. Isolate selection”The isolate pool attempts to hand out a warm (already-initialized) isolate for the target function using LRU eviction policy. If none is available, a cold start is triggered.
8. Request dispatch
Section titled “8. Request dispatch”The request is sent to the isolate’s dedicated OS thread via a channel. The V8 event loop on that thread picks up the request and invokes the JavaScript or TypeScript handler.
9. Handler execution
Section titled “9. Handler execution”User code runs inside the V8 sandbox. It has access to the standard Web APIs (fetch, crypto, TextEncoder, etc.), the Thunder runtime APIs, and the Node.js polyfills registered at boot time.
10. Response streaming
Section titled “10. Response streaming”The handler returns a Response object. The body is streamed back to the client through the Hyper layer without buffering the entire payload in memory.
11. Resource cleanup
Section titled “11. Resource cleanup”After the response is fully sent, per-request resources (timers, open fetch connections, pending futures) are cleaned up. The isolate is returned to the pool for reuse.
Cold Start vs Warm Start
Section titled “Cold Start vs Warm Start”| Aspect | Cold start | Warm start |
|---|---|---|
| Trigger | No idle isolate in the pool for the target function | Idle isolate available in the pool |
| Work performed | Spawn OS thread, create V8 isolate, load ESZIP, run bootstrap.js, compile module graph | Reuse existing thread and isolate, reset per-request state |
| Typical latency | 10—100 ms depending on bundle size | Sub-millisecond dispatch |
| When it happens | First request after deploy, after eviction, or after scale-to-zero | Subsequent requests within the idle timeout |
The pool uses an LRU eviction strategy: when the pool reaches its capacity limit, the least-recently-used isolate is terminated to make room for a new one.
Timeout Handling
Section titled “Timeout Handling”Thunder enforces wall-clock timeouts on handler execution using a watchdog thread pattern:
- When a request is dispatched to an isolate, a watchdog timer is started on a separate thread.
- If the handler completes before the deadline, the watchdog is cancelled.
- If the deadline expires, the watchdog thread terminates the V8 isolate by calling
isolate.terminate_execution(). This causes the handler to throw an unrecoverable exception. - The server responds with 504 Gateway Timeout and the isolate is discarded (not returned to the pool).
This two-thread approach (isolate thread + watchdog thread) ensures that even infinite loops or blocked synchronous code cannot prevent the timeout from firing.