morca · API · web

orca.web.drain

Stopping orca ui without dropping a request. docker stop sends SIGTERM and waits 10 s before it kills; Ctrl-C sends SIGINT. Either one starts a drain:

  1. /healthz answers 503 draining (the flag Drain.draining returns), so a health checker or proxy that asks over a kept-alive connection stops routing here; and the listening socket closes, so a new connection is refused at once (one waiting in its backlog is reset).
  2. The requests already in the handler finish and their answers go out; so does one a kept-alive connection sends meanwhile.
  3. Once no request has been in the handler for 200 ms, or after ORCA_UI_DRAIN_SECONDS (default 8, under docker stop's 10), the server's loop stops, which closes the kept-alive connections, and orca ui exits 0. A request still running half a second after that can't be cut short (the handler runs on the loop's thread), so the process then exits 1 without it.

A second signal during the drain stops the loop at once.

A signal handler can't reach a Mojo variable, so it writes a byte to a pipe whose write end is duplicated onto a fixed descriptor, WAKE_FD, and a thread of its own waits on the read end (as dista.signals does for a worker). The handler runs on whichever thread the kernel picks, and write is async-signal-safe; signal installs it with SA_RESTART, so a query it interrupts goes on.

Flare's HttpServer.drain(timeout_ms) always waits out its whole window, since its loop publishes no count of what's in flight, so the drain counts the requests in the handler itself (Counting) and stops the loop with HttpServer.close() once there are none.

Structs

Counting

struct Counting[H: Handler & Copyable]

H, counting the requests in it in busy.

Implements Copyable, Handler, Movable

Fields
inner: H
busy: AtomicCounter
comptime BodyType = H.BodyType

__init__

def __init__(out self, var inner: H, busy: AtomicCounter)

serve

def serve(self, req: Request) -> ResponseImpl[Self.BodyType]

Drain

struct Drain

A thread that drains the HttpServer it's given on SIGTERM or SIGINT, until this is dropped, which joins it. Serve the handler counting returns, so it knows what's in flight.

Implements Movable

__init__

def __init__(out self, mut server: HttpServer)

__deinit__

def __deinit__(deinit self)

counting

def counting[H: Handler & Copyable](self, var handler: H) -> Counting[H]

handler, counting its requests for the drain.

draining

def draining(self) -> AtomicFlag

Set from the moment a drain starts; valid while this lives.

Functions

drain_seconds

def drain_seconds() -> Int

ORCA_UI_DRAIN_SECONDS, or 8 if it's unset or not a number; 0 stops without waiting for anything.

Aliases

comptime SIGINT = Int32(2)
comptime SIGTERM = Int32(15)
comptime WAKE_FD = Int32(249)

The descriptor the handler writes to: below macOS's default limit of 256 open files, above what a process has open when it starts, and one below dista.signals.WAKE_FD.

comptime DEFAULT_DRAIN_SECONDS = 8