Routes may share one lease (affinity = "route") and skip crossbar's queue (queue = false)
route.go gains Route.Affinity/Queue with PerRoute(), Queues() and affinity validation (checkRoutes moved here; config.go calls it once). limiter.Track counts a request without holding or refusing it; a release hands the slot to a waiter only while in flight <= parallel. The proxy leases a PerRoute() route under an empty fingerprint (the row keeps the real one) and uses Track when Queues() is false. Implemented by Ornith (OpenCode); owner review removed a release-on-first-flush workaround for a race in the owner's given test (see implementer log). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+36
-21
@@ -217,6 +217,12 @@ func (p *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
model = resolveModel(model, r, routeCfg)
|
||||
fp := fingerprint.Of(body)
|
||||
// A route-affinity route puts every request (chat or control) on one lease per model, so the
|
||||
// lease key's fingerprint is "" for all of them; the real fingerprint is kept for the row below.
|
||||
leaseFP := fp
|
||||
if routeCfg.PerRoute() {
|
||||
leaseFP = ""
|
||||
}
|
||||
started := time.Now()
|
||||
|
||||
// v0 compatibility path: no lease table, no limiter, no recording.
|
||||
@@ -231,14 +237,14 @@ func (p *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Lease. The route's ordered host list is the candidate set.
|
||||
host, reused, err := p.leases.Acquire(lease.Key{Route: route, FP: fp, Model: model}, routeCfg.Hosts, time.Now())
|
||||
host, reused, err := p.leases.Acquire(lease.Key{Route: route, FP: leaseFP, Model: model}, routeCfg.Hosts, time.Now())
|
||||
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}) {
|
||||
if p.waker != nil && p.wakeOnErrNoHost(w, r, route, routeCfg, rest, model, fp, started, lease.Key{Route: route, FP: leaseFP, Model: model}) {
|
||||
return
|
||||
}
|
||||
p.writeError(w, http.StatusServiceUnavailable, "no healthy host")
|
||||
@@ -265,10 +271,30 @@ func (p *Handler) serveLeased(w http.ResponseWriter, r *http.Request, route stri
|
||||
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 {
|
||||
if errors.Is(err, limiter.ErrQueueFull) {
|
||||
// Slot or track. A queue = false route leaves queueing to the client's own
|
||||
// llama-server slot: crossbar only counts the request on the host, never
|
||||
// holding it or refusing it.
|
||||
var waited time.Duration
|
||||
var release func()
|
||||
if routeCfg.Queues() {
|
||||
var err error
|
||||
release, waited, err = p.lim.Acquire(r.Context(), host, model)
|
||||
if err != nil {
|
||||
if errors.Is(err, limiter.ErrQueueFull) {
|
||||
p.writeRecord(store.Request{
|
||||
Route: route,
|
||||
FP: fp,
|
||||
Model: model,
|
||||
Host: host,
|
||||
Started: started,
|
||||
TotalMs: time.Since(started).Milliseconds(),
|
||||
Status: http.StatusServiceUnavailable,
|
||||
Err: "queue full",
|
||||
})
|
||||
p.writeError(w, http.StatusServiceUnavailable, "queue full")
|
||||
return
|
||||
}
|
||||
p.log.Warn("request", "route", route, "host", host, "method", r.Method, "path", rest, "status", 499)
|
||||
p.writeRecord(store.Request{
|
||||
Route: route,
|
||||
FP: fp,
|
||||
@@ -276,24 +302,13 @@ func (p *Handler) serveLeased(w http.ResponseWriter, r *http.Request, route stri
|
||||
Host: host,
|
||||
Started: started,
|
||||
TotalMs: time.Since(started).Milliseconds(),
|
||||
Status: http.StatusServiceUnavailable,
|
||||
Err: "queue full",
|
||||
Status: 499,
|
||||
Err: "client cancelled while queued",
|
||||
})
|
||||
p.writeError(w, http.StatusServiceUnavailable, "queue full")
|
||||
return
|
||||
}
|
||||
p.log.Warn("request", "route", route, "host", host, "method", r.Method, "path", rest, "status", 499)
|
||||
p.writeRecord(store.Request{
|
||||
Route: route,
|
||||
FP: fp,
|
||||
Model: model,
|
||||
Host: host,
|
||||
Started: started,
|
||||
TotalMs: time.Since(started).Milliseconds(),
|
||||
Status: 499,
|
||||
Err: "client cancelled while queued",
|
||||
})
|
||||
return
|
||||
} else {
|
||||
release = p.lim.Track(host, model)
|
||||
}
|
||||
defer release()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user