Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/content/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,10 @@ responses at the handler level.

### `ShutdownTimeout` only applies to context-based start

`ShutdownTimeout` bounds the drain of in-flight requests during graceful shutdown.
It is consumed by `StartWithContext` and `StartWithListenerAndContext`
(`celeris/server.go:710-712`, `765-767`), which default it to 30s when left zero.
`ShutdownTimeout` is the deadline of the graceful shutdown a cancelled context starts:
one deadline for the drain of in-flight requests and then the `OnShutdown` hooks. It is
consumed by `StartWithContext` and `StartWithListenerAndContext`
(`celeris/server.go` (`listenUntilCancelled`)), which default it to 30s when left zero.
Plain `Start()` blocks until you call `Shutdown(ctx)` yourself, in which case the
deadline comes from the context *you* pass to `Shutdown`, not from this field.

Expand Down
11 changes: 6 additions & 5 deletions src/content/docs/core-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@ if err := s.Start(); err != nil {

### Graceful shutdown

`Shutdown(ctx)` stops the engine, then fires any hooks you registered with
`OnShutdown` — in registration order, with the shutdown context. On `std` and
`adaptive` it waits for in-flight requests before the hooks; on `epoll` and
`io_uring` the engine drains as its listen context is cancelled, and the hooks do
not wait for that (see [Graceful shutdown](/docs/graceful-shutdown#shutdown-sequence)).
`Shutdown(ctx)` stops the engine, waits for the in-flight requests to drain (bounded
by `ctx`), then fires any hooks you registered with `OnShutdown` — in registration
order, with the shutdown context. The order is the same on every engine, apart from
two kinds of HTTP/2 stream the drain does not wait for (see
[Graceful shutdown](/docs/graceful-shutdown#what-the-drain-waits-for)). A `Start` that
`Shutdown` stops returns only after `Shutdown` has returned.

`StartWithContext` wires this up for you: when the context is cancelled, the server
shuts down using `Config.ShutdownTimeout` (default 30s), and `StartWithContext`
Expand Down
54 changes: 30 additions & 24 deletions src/content/docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -534,8 +534,8 @@ drain finish before sending `SIGKILL`.

Use `StartWithContext` with a signal-cancelled context so a rolling deploy drains
in-flight requests instead of dropping them
(`celeris/server.go:753-784`). `Config.ShutdownTimeout` bounds the drain (default
30s):
(`celeris/server.go` (`StartWithContext`)). `Config.ShutdownTimeout` (default 30s) is
Comment thread
FumingPower3925 marked this conversation as resolved.
one deadline for the drain and then the `OnShutdown` hooks:

```go
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
Expand All @@ -553,42 +553,47 @@ if err := s.StartWithContext(ctx); err != nil {
Neither `Shutdown` nor `PauseAccept` touches the readiness probe — the
`healthcheck` middleware only ever returns what your `ReadyChecker` returns
(`celeris/middleware/healthcheck/healthcheck.go:83`). To stop the LB sending new
traffic during a drain you have to flip readiness yourself. The idiomatic wiring is
an `atomic.Bool`, set `true` at startup, flipped to `false` from an
[`OnShutdown`](#graceful-shutdown-during-deploys) hook (fired during `Shutdown`,
`celeris/server.go:218-225`), and read by the `ReadyChecker`:
traffic during a drain you have to flip readiness yourself, and before the drain
begins: the `OnShutdown` hooks run only after the requests in flight have finished, on
every engine since celeris v1.6.0
([celeris#703](https://github.com/goceleris/celeris/issues/703); HTTP/2 has two
exceptions, see
[What the drain waits for](/docs/graceful-shutdown#what-the-drain-waits-for)), which is
too late to steer the load balancer (see
[Shutdown sequence](/docs/graceful-shutdown#shutdown-sequence)). The idiomatic wiring
is an `atomic.Bool`, set `true` at startup, flipped to `false` by your `SIGTERM`
handler *before* it cancels the context, and read by the `ReadyChecker`:

```go
var ready atomic.Bool
ready.Store(true) // serving as soon as we're up

s := celeris.New(celeris.Config{Addr: ":8080", ShutdownTimeout: 15 * time.Second})

// Flip readiness to 503 when Shutdown runs its hooks (see below for when that is).
s.OnShutdown(func(_ context.Context) {
ready.Store(false)
})

s.Use(healthcheck.New(healthcheck.Config{
ReadyChecker: func(_ *celeris.Context) bool { return ready.Load() },
}))

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
sig, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

// On SIGTERM: fail readiness, give the load balancer time to see it, then drain.
ctx, cancel := context.WithCancel(context.Background())
go func() {
<-sig.Done()
ready.Store(false) // /readyz now answers 503
time.Sleep(5 * time.Second) // at least one readiness-probe period
cancel() // stop accepting, drain, then run the OnShutdown hooks
}()

if err := s.StartWithContext(ctx); err != nil {
log.Fatal(err)
}
```

When that hook runs depends on the engine. On `epoll` and `io_uring` it runs as the
drain begins, while requests are still in flight. On `std` and `adaptive` it runs
only after in-flight requests have finished, which is too late to steer the load
balancer during the drain (see
[Shutdown sequence](/docs/graceful-shutdown#shutdown-sequence)). To flip readiness
before the drain on every engine, set `ready.Store(false)` in your own `SIGTERM`
handler *before* cancelling the context or calling `Shutdown`. Either way the flip is
yours to make. (`atomic.Bool` is in the standard library's `sync/atomic`.)
The sleep is how long the load balancer needs to notice (at least one readiness-probe
period); the server keeps serving meanwhile. The flip is yours to make on every
engine. (`atomic.Bool` is in the standard library's `sync/atomic`.)

For true zero-downtime restarts on the same host, inherit the listening socket
across the exec with `InheritListener` + `StartWithListener`
Expand All @@ -610,10 +615,11 @@ drain ordering, and the native engines' `SO_REUSEPORT` rebind — is covered in
[Graceful shutdown and zero-downtime restarts](/docs/graceful-shutdown).

In Kubernetes, the rolling-update pattern is: container receives `SIGTERM` →
your readiness flip fires (in your `SIGTERM` handler, or on `epoll` and `io_uring` in
the `OnShutdown` hook above) so `/readyz` returns 503 → LB stops new traffic →
in-flight requests drain within `ShutdownTimeout` → process exits. Set
`terminationGracePeriodSeconds` greater than `ShutdownTimeout`.
your `SIGTERM` handler flips readiness so `/readyz` returns 503 → LB stops new
traffic → the context is cancelled → in-flight requests drain within
`ShutdownTimeout` → the `OnShutdown` hooks run → process exits. Set
Comment thread
FumingPower3925 marked this conversation as resolved.
`terminationGracePeriodSeconds` greater than the readiness delay plus
`ShutdownTimeout`.
Comment thread
FumingPower3925 marked this conversation as resolved.

## Capacity and timeout tuning

Expand Down
25 changes: 14 additions & 11 deletions src/content/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,30 +188,33 @@ func main() {
return c.String(200, "pong")
})

// Blocks until ctx is canceled; returns after in-flight requests
// have drained and the OnShutdown hooks have run.
// Blocks until ctx is canceled; returns after the in-flight requests
// have drained and then the OnShutdown hooks have run.
Comment thread
FumingPower3925 marked this conversation as resolved.
if err := s.StartWithContext(ctx); err != nil {
log.Fatal(err)
}
}
```

When the context is canceled, Celeris stops accepting new connections, and
`StartWithContext` returns only after in-flight requests have finished and your
shutdown hooks have run. The drain window is bounded by `Config.ShutdownTimeout`
(default **30s**). To run cleanup when the server stops — close a database pool,
flush a buffer — register a hook with `s.OnShutdown`:
When the context is canceled, Celeris stops accepting new connections, drains the
in-flight requests, then runs your shutdown hooks, and `StartWithContext` returns only
after both. `Config.ShutdownTimeout` (default **30s**) is one budget for the drain and
the hooks. A request still running when it expires is not interrupted (see
[what happens at the deadline](/docs/graceful-shutdown#faq)). To run cleanup when the
Comment thread
FumingPower3925 marked this conversation as resolved.
server stops — close a database pool, flush a buffer — register a hook with
`s.OnShutdown`:

```go
s.OnShutdown(func(ctx context.Context) {
pool.Close()
})
```

Shutdown hooks fire in registration order with the shutdown context. On `std` and
`adaptive` they run after in-flight requests finish; on `epoll` and `io_uring` they
can run while requests are still draining. Either way `StartWithContext` returns only
after they have run, so a hook must not wait for it to return.
Shutdown hooks fire in registration order with the shutdown context, after the
in-flight requests have drained, on every engine (HTTP/2 has two exceptions; see
[What the drain waits for](/docs/graceful-shutdown#what-the-drain-waits-for)).
`StartWithContext` returns only after they have run, so a hook must not wait for it to
return.

> **Tip:** `Config.ShutdownTimeout` only applies to `StartWithContext`. If you
> need a custom drain deadline, set it on the `Config` you pass to
Expand Down
Loading
Loading