# orca.web.drain

> A module of morca's API.

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`

```mojo
struct Counting[H: Handler & Copyable]
```

`H`, counting the requests in it in `busy`.

Implements: `Copyable`, `Handler`, `Movable`

Fields:

- `inner: H`
- `busy: AtomicCounter`

#### `BodyType`

```mojo
comptime BodyType = H.BodyType
```

#### `__init__`

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

#### `serve`

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

### `Drain`

```mojo
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__`

```mojo
def __init__(out self, mut server: HttpServer)
```

#### `__deinit__`

```mojo
def __deinit__(deinit self)
```

#### `counting`

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

`handler`, counting its requests for the drain.

#### `draining`

```mojo
def draining(self) -> AtomicFlag
```

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

## Functions

### `drain_seconds`

```mojo
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

### `SIGINT`

```mojo
comptime SIGINT = Int32(2)
```

### `SIGTERM`

```mojo
comptime SIGTERM = Int32(15)
```

### `WAKE_FD`

```mojo
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`.

### `DEFAULT_DRAIN_SECONDS`

```mojo
comptime DEFAULT_DRAIN_SECONDS = 8
```
