Wire wake and identity into crossbar; v2 smoke and README
Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
@@ -7,6 +7,7 @@ package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
@@ -45,6 +46,11 @@ type Recorder interface {
|
||||
RecordRequest(store.Request) error
|
||||
}
|
||||
|
||||
// Waker rouses a sleeping host. *wake.Waker satisfies it.
|
||||
type Waker interface {
|
||||
Wake(ctx context.Context, host string) bool
|
||||
}
|
||||
|
||||
// Handler forwards requests for a route to one of the route's healthy hosts, choosing by lease when
|
||||
// one is configured and by health alone otherwise.
|
||||
type Handler struct {
|
||||
@@ -54,6 +60,13 @@ type Handler struct {
|
||||
lim *limiter.Limiter
|
||||
rec Recorder
|
||||
log *slog.Logger
|
||||
waker Waker
|
||||
}
|
||||
|
||||
// SetWaker installs the waker the consults when a route has no healthy host left. A nil waker
|
||||
// (the default) leaves the ErrNoHost answer as it was in v0: a plain 503.
|
||||
func (p *Handler) SetWaker(w Waker) {
|
||||
p.waker = w
|
||||
}
|
||||
|
||||
// New builds a Handler. A nil logger becomes slog.Default(). With a nil lease table it behaves like
|
||||
@@ -223,6 +236,12 @@ func (p *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, lease.ErrNoHost):
|
||||
// No host healthy. Ask a waker to rouse a sleeping one; it answers
|
||||
// (served or 503) when it has had a turn, else falls through to the
|
||||
// plain 503.
|
||||
if p.waker != nil && p.wakeOnErrNoHost(w, r, route, routeCfg, rest, model, fp, started, lease.Key{Route: route, FP: fp, Model: model}) {
|
||||
return
|
||||
}
|
||||
p.writeError(w, http.StatusServiceUnavailable, "no healthy host")
|
||||
case errors.Is(err, lease.ErrPinnedDown):
|
||||
p.writeError(w, http.StatusServiceUnavailable, "pinned host down")
|
||||
@@ -232,6 +251,14 @@ func (p *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Slot, context guard and forward, holding the slot for the leased host.
|
||||
p.serveLeased(w, r, route, routeCfg, rest, model, fp, started, host, reused)
|
||||
}
|
||||
|
||||
// serveLeased queues the request against the leased host's limiter, runs the
|
||||
// context guard, and forwards. The slot is held for the originally leased host
|
||||
// even if the guard relocates the lease: the guard already moved it.
|
||||
func (p *Handler) serveLeased(w http.ResponseWriter, r *http.Request, route string, routeCfg config.Route, rest, model, fp string, started time.Time, host string, reused bool) {
|
||||
// Slot. A full queue is a 503; a context done while waiting means the client left.
|
||||
release, waited, err := p.lim.Acquire(r.Context(), host, model)
|
||||
if err != nil {
|
||||
@@ -273,3 +300,26 @@ func (p *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
p.forward(w, r, route, host, leaseState(reused), rest, fp, model, now, waited, 0, header)
|
||||
}
|
||||
|
||||
// wakeOnErrNoHost answers the request when no host was healthy. It asks, in
|
||||
// route order, each candidate with a wake target to rouse itself; a host that
|
||||
// wakes is leased once more and then served. When none wakes, it answers 503
|
||||
// with the hosts it tried. It returns true when the request has been answered.
|
||||
func (p *Handler) wakeOnErrNoHost(w http.ResponseWriter, r *http.Request, route string, routeCfg config.Route, rest, model, fp string, started time.Time, key lease.Key) bool {
|
||||
var tried []string
|
||||
for _, name := range routeCfg.Hosts {
|
||||
if p.cfg.Hosts[name].Wake == nil {
|
||||
continue
|
||||
}
|
||||
tried = append(tried, name)
|
||||
if !p.waker.Wake(r.Context(), name) {
|
||||
continue
|
||||
}
|
||||
if newHost, _, err := p.leases.Acquire(key, routeCfg.Hosts, time.Now()); err == nil {
|
||||
p.serveLeased(w, r, route, routeCfg, rest, model, fp, started, newHost, true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
p.writeNoHealthyHost(w, route, model, fp, started, tried)
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user