Skip to content

Lifecycle — SSE component

A per-request Server-Sent Events handler: a 3-arg action fn(Request, Params, Sse) that streams a text/event-stream body for the life of one connection. Because each request is its own process, the action may block for the whole connection. See the overview for the shared two-domain model and failure vocabulary.

Shape (what you write)

rust
use rusm_rs::http::{Params, Request, Sse};

#[rusm_rs::handlers]
pub mod api {
    use super::*;
    // routed from `[serve.routes]`:  "GET /feed" = "api#feed"
    pub fn feed(_req: Request, _p: Params, sse: Sse) {
        for n in 0.. {
            if !sse.data(format!("tick {n}").as_bytes()) {
                break; // the client disconnected — stop cleanly
            }
        }
    }
}
ts
// A wasi:http component returning a streaming text/event-stream body. The runtime
// pulls events and applies back-pressure; closing the controller ends the stream.
export default function handle(_request: Request): Response {
  const enc = new TextEncoder();
  let n = 0;
  const body = new ReadableStream({
    pull(controller) {
      controller.enqueue(enc.encode(`data: tick ${n++}\n\n`));
    },
  });
  return new Response(body, { headers: { "content-type": "text/event-stream" } });
}

In Rust, sse.data(bytes) writes one data: event and sse.run(heartbeat_ms, map) live-tails a source (e.g. a pub/sub topic) with idle heartbeats — the loop ends when a write returns false. In TypeScript the runtime drives the ReadableStream's pull, parking it on back-pressure and ending it on disconnect — the same lifecycle.

Platform owns / you write

  • Platform owns: the streaming text/event-stream head, the bounded, back-pressured byte stream from the guest into the chunked HTTP body, disconnect detection (a failed write returns false), and reclaim on exit.
  • You write: the event loop — produce events, react to a false write by stopping.

Lifecycle events

EventPlatform domainApplication domainResult
Normal (finite feed)drains each event into the body; closes the body when you returnwrites events, then returnsclean end-of-stream
Back-pressure (consumer slow)the bounded channel fills; the next write parks the fiber until drainedblocked inside sse.data(…); resumes laterpaced to the consumer — never a busy-spin
Client disconnectthe stream reader drops, so the next write returns falsethe loop breaks; the process exitsprompt teardown; a broker that monitored it prunes on the Down
Connection error (socket write fails mid-stream)same as a disconnect — the write returns falseloop breaksclean exit
Crash (trap) mid-streamthe stream writer drops → the chunked body simply ends → the process is Crashedthe panic! / .unwrap()a truncated stream; only this connection
Memory crash (OOM)the StoreLimiter cap trips a trap → body ends → Crashedexceeded max-memory-mbtruncated stream; the instance is discarded

Notes

  • No spins, ever. The byte stream is a bounded channel, so an endless feed is paced by the consumer (the write parks the fiber) — it cannot peg a core. And a disconnect always tears the process down. Both are regression-guarded by a test (an endless feed + a forced disconnect, asserting the process count returns to baseline).
  • Live fan-out pattern. Subscribe to a service component (a broker) and sse.run(...) to forward each published message. The service monitors subscribers, so this process's exit (on disconnect) prunes the subscription automatically — crash-safe cleanup with no unsubscribe call.

Prev: HTTP component · Next: WebSocket component

MIT licensed