Graceful drain
Draining a worker so a deploy or scale-in never drops a call.
Voice sessions can last minutes. A rolling deploy or a scale-in must never cut a live call — so before a worker terminates, you drain it: stop taking new work, let the in-flight work finish.
Draining a worker
func (w *Worker) Drain(ctx context.Context) errorDrain stops the worker accepting new jobs and waits for outstanding work to
complete before returning. "Outstanding" counts both:
- running jobs — active sessions still on a call, and
- accepted-but-not-yet-assigned reservations — jobs the worker has taken but not started.
Counting reservations too is what closes the race where a job is accepted the instant a naive drain would have declared itself done.
Deploy / scale-in flow
Signal shutdown
On SIGTERM (or your orchestrator's pre-stop hook), call Drain instead of
exiting immediately.
Let calls finish
The worker reports full, so the dispatcher places new sessions on other workers; existing sessions run to their natural end.
Exit
Drain returns once everything has finished (or your drain-deadline context
fires). Then terminate the process.
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
go func() { _ = w.Run(runCtx, source) }()
<-ctx.Done() // shutdown signal
drainCtx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
_ = w.Drain(drainCtx) // wait out live calls, bounded by the deadlineGive the drain a deadline (via the context) that matches your orchestrator's termination grace period, so a stuck session can't hold a deploy open forever.
Boundaries
Drain covers in-process completion. Reconnect/retry and job migration across nodes are handled by the LiveKit wire adapter and remain future work — a drained worker finishes its own calls; it doesn't hand live calls to another worker.