Control-plane requests follow the lease but take no slot and write no row

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-25 17:44:00 -07:00
parent 3518e84dd7
commit 33fa61bedb
6 changed files with 251 additions and 15 deletions
+16 -10
View File
@@ -131,9 +131,9 @@ func hasModel(loaded []string, model string) bool {
return false
}
// allowedPath reports whether rest may be proxied: under /v1/, or the two admin paths.
// allowedPath reports whether rest may be proxied: under /v1/, or the admin and control-plane paths.
func allowedPath(rest string) bool {
return strings.HasPrefix(rest, "/v1/") || rest == "/health" || rest == "/props"
return strings.HasPrefix(rest, "/v1/") || rest == "/health" || rest == "/props" || rest == "/slots" || rest == "/tokenize"
}
// route resolves the route name and the upstream path (rest) from the request, honouring the
@@ -208,15 +208,14 @@ func (p *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
routeCfg, _, _ := p.cfg.Route(route)
isControl := isControlCall(r.Method, rest)
model, body, err := peekModel(r)
if err != nil {
p.writeError(w, http.StatusRequestEntityTooLarge, "body too large")
return
}
if model == "" {
model = routeCfg.DefaultModel
}
model = resolveModel(model, r, routeCfg)
fp := fingerprint.Of(body)
started := time.Now()
@@ -227,7 +226,7 @@ func (p *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
p.writeError(w, http.StatusServiceUnavailable, "no healthy host")
return
}
p.forward(w, r, route, name, "", rest, fp, model, started, 0, 0, "")
p.forward(w, r, route, name, "", rest, fp, model, started, 0, 0, "", isControl)
return
}
@@ -252,13 +251,20 @@ func (p *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// Slot, context guard and forward, holding the slot for the leased host.
p.serveLeased(w, r, route, routeCfg, rest, model, fp, started, host, reused)
p.serveLeased(w, r, route, routeCfg, rest, model, fp, started, host, reused, isControl)
}
// 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) {
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, control bool) {
// A control-plane call follows the lease but takes no slot and runs no
// context guard: it is sent beside its own stream, so it must never wait
// for or hold a slot. forward writes no row and logs at Debug for it.
if control {
p.forward(w, r, route, host, leaseState(reused), rest, fp, model, started, 0, 0, "", true)
return
}
// 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 {
@@ -298,7 +304,7 @@ func (p *Handler) serveLeased(w http.ResponseWriter, r *http.Request, route stri
if done {
return
}
p.forward(w, r, route, host, leaseState(reused), rest, fp, model, now, waited, 0, header)
p.forward(w, r, route, host, leaseState(reused), rest, fp, model, now, waited, 0, header, false)
}
// wakeOnErrNoHost answers the request when no host was healthy. It asks, in
@@ -316,7 +322,7 @@ func (p *Handler) wakeOnErrNoHost(w http.ResponseWriter, r *http.Request, route
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)
p.serveLeased(w, r, route, routeCfg, rest, model, fp, started, newHost, true, isControlCall(r.Method, rest))
return true
}
}