SagaSaga
SagaHttp

Server lifecycle

pub fun serve : Config -> (Request -> Response) -> Result ShutdownHandle String
  needs {Process, Actor SupMsg, Monitor, Timer, Server}

serve is non-blocking. It listens, spawns a supervisor that owns the listener, spawns an acceptor that does the blocking Tcp.accept, and returns. The supervisor tracks every live connection process.

Two ways to wait on it:

pub fun await_shutdown    : ShutdownHandle -> Unit
pub fun shutdown_and_wait : ShutdownHandle -> Int -> ShutdownResult
  • await_shutdown monitors the supervisor and returns when it dies, for any reason. Useful in main to keep the executable alive.
  • shutdown_and_wait h deadline_ms initiates a graceful drain: closes the listener (stops accepting), closes every tracked connection socket (wakes any blocked Tcp.recv so the handler exits), then waits for handlers to finish via Monitor. Returns:
pub type ShutdownResult =
  | Drained        # all handlers exited within the deadline
  | TimedOut       # deadline expired; count is in ShutdownTimedOut event
  | NoReply        # supervisor died for an unexpected reason

Handlers mid-response when shutdown begins will see a truncated send on a dead socket — that's the cost of bounded shutdown.

See src/Main.saga for a SIGTERM-driven wiring example.

The Server effect

pub effect Server { fun event : ServerEvent -> Unit }

Every serve invocation needs a Server handler in scope. The events cover places the library previously dropped errors silently:

VariantMeaning
AcceptError Stringaccept() returned an error; acceptor exits
ClientDisconnectedpeer closed during the initial header read
IdleTimeoutidle/read timeout during the header read
RequestParseError ParseErrorparse failed; error response already sent
HeadersTooLargecumulative header cap exceeded; 400 sent
BodyReadFailed Stringrecv failed mid-body; arg is the gen_tcp atom
BodyReadDeadlineExceededcumulative body-read deadline expired; 408 sent
SendFailed SendSite StringTcp.send failed at the named site
OwnershipTransferFailed Stringgen_tcp:controlling_process failed
PipelinedRequestDroppedleftover bytes after a request; connection closed
ConnectionLimitReachedmax_connections cap hit; new socket dropped
ShutdownTimedOut Intdrain deadline expired; arg is connections still alive
PeerAddressUnavailable StringTcp.peername failed; req.peer = ("", 0)

SendSite distinguishes which send failed: SendResponse, SendChunk, SendChunkTerminator, SendContinue.

Two ready-made handlers ship with the library:

pub handler discard_events for Server { event _ = resume () }
pub handler print_events   for Server { event e = { dbg e; resume () } }

discard_events for tests and benchmarks; print_events for development. For production you'll want your own handler that forwards to structured logging or metrics.