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:
@@ -1,13 +1,41 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// templateName matches a route template: a valid route name ending in "-*".
|
||||
var templateName = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*-\*$`)
|
||||
|
||||
// routeName matches a route (or template) name: the pattern a concrete or template route key must
|
||||
// match, so a name with '*' or an invalid prefix never resolves.
|
||||
var routeName = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
|
||||
|
||||
// Route is an ordered list of hosts to try, with an optional default model, the peers allowed to
|
||||
// reach it, how its requests are placed (affinity), and whether crossbar queues them.
|
||||
type Route struct {
|
||||
Hosts []string `toml:"hosts"`
|
||||
DefaultModel string `toml:"default_model"`
|
||||
Peers []string `toml:"peers"`
|
||||
Affinity string `toml:"affinity"` // "" or "conversation" (the default), or "route"
|
||||
Queue *bool `toml:"queue"` // nil means true
|
||||
}
|
||||
|
||||
// PerRoute reports affinity = "route": every request on the route (chat or control) shares one
|
||||
// lease per model, so the route lives on one host.
|
||||
func (r Route) PerRoute() bool {
|
||||
return r.Affinity == "route"
|
||||
}
|
||||
|
||||
// Queues reports whether the route's requests wait in (and can be refused by) crossbar's per-(host,
|
||||
// model) queue; false only for queue = false, which leaves queueing to the client's own slot.
|
||||
func (r Route) Queues() bool {
|
||||
return r.Queue == nil || *r.Queue
|
||||
}
|
||||
|
||||
// Route resolves a request route name: an exact entry wins; else the longest template
|
||||
// "<prefix>-*" whose prefix (including the dash) starts name with a non-empty remainder;
|
||||
// else ok is false. key is the config key that matched (the template's name for a template).
|
||||
@@ -38,3 +66,64 @@ func (c *Config) Route(name string) (r Route, key string, ok bool) {
|
||||
}
|
||||
return best, bestKey, true
|
||||
}
|
||||
|
||||
// checkRoutes validates and defaults one route's hosts, model, affinity and peers in a fixed order.
|
||||
// A name that is neither a valid route nor a template, a missing or unknown host, a default model no
|
||||
// host serves, an unrecognised affinity, or a peers list that breaks the identity contract each
|
||||
// wins as the first error.
|
||||
func (c *Config) checkRoutes(peersDefined map[string]bool, identityDefined bool) *Error {
|
||||
if len(c.Routes) == 0 {
|
||||
return &Error{Field: "routes", Msg: "at least one required"}
|
||||
}
|
||||
names := make([]string, 0, len(c.Routes))
|
||||
for name := range c.Routes {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
r := c.Routes[name]
|
||||
|
||||
if !routeName.MatchString(name) && !templateName.MatchString(name) {
|
||||
return &Error{Field: fmt.Sprintf("routes.%s", name), Msg: "must match [a-z0-9][a-z0-9-]*"}
|
||||
}
|
||||
|
||||
hostsField := fmt.Sprintf("routes.%s.hosts", name)
|
||||
if len(r.Hosts) == 0 {
|
||||
return &Error{Field: hostsField, Msg: "at least one required"}
|
||||
}
|
||||
seen := make(map[string]bool, len(r.Hosts))
|
||||
for _, h := range r.Hosts {
|
||||
if seen[h] {
|
||||
return &Error{Field: hostsField, Msg: "host listed twice"}
|
||||
}
|
||||
seen[h] = true
|
||||
if _, ok := c.Hosts[h]; !ok {
|
||||
return &Error{Field: hostsField, Msg: "unknown host"}
|
||||
}
|
||||
}
|
||||
|
||||
if r.DefaultModel != "" {
|
||||
served := false
|
||||
for _, h := range r.Hosts {
|
||||
if _, ok := c.Hosts[h].Models[r.DefaultModel]; ok {
|
||||
served = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !served {
|
||||
return &Error{Field: fmt.Sprintf("routes.%s.default_model", name), Msg: "not served by any host in route"}
|
||||
}
|
||||
}
|
||||
|
||||
switch r.Affinity {
|
||||
case "", "conversation", "route":
|
||||
default:
|
||||
return &Error{Field: fmt.Sprintf("routes.%s.affinity", name), Msg: `must be "conversation" or "route"`}
|
||||
}
|
||||
|
||||
if e := checkPeers(name, r.Peers, peersDefined[name], identityDefined, c.Identity); e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user