mirror.peers names the mirrors kept in step with mirror.api. The mirror's management calls go to the first that can be reached, and each mirror is asked every fifteen seconds whether it answers: a shard is sent its model's and adapters' address on one that does while the mirror the catalog names is down. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
103 lines
2.6 KiB
Go
103 lines
2.6 KiB
Go
package mirror
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/tlsconf"
|
|
)
|
|
|
|
// Sources is where a node fetches a model while one mirror is down. A model
|
|
// in the catalog names one mirror's address; when that mirror does not
|
|
// answer, the address a node is given is the same file on one that does.
|
|
|
|
// SourcesInterval is how often each mirror is asked whether it answers.
|
|
const SourcesInterval = 15 * time.Second
|
|
|
|
// Sources watches mirrors kept in step, and rewrites a model's address to
|
|
// one that answers.
|
|
type Sources struct {
|
|
clients []*Client
|
|
log *slog.Logger
|
|
|
|
mu sync.Mutex
|
|
bases []string
|
|
healthy []bool
|
|
}
|
|
|
|
// NewSources watches the mirrors at these management addresses.
|
|
func NewSources(addrs []string, tc tlsconf.Config, log *slog.Logger) (*Sources, error) {
|
|
s := &Sources{log: log, bases: make([]string, len(addrs)), healthy: make([]bool, len(addrs))}
|
|
for _, a := range addrs {
|
|
c, err := NewClient(a, tc, 5*time.Second)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.clients = append(s.clients, c)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// Run asks each mirror, every while, until ctx ends.
|
|
func (s *Sources) Run(ctx context.Context) {
|
|
t := time.NewTicker(SourcesInterval)
|
|
defer t.Stop()
|
|
for {
|
|
s.Check(ctx)
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-t.C:
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check asks each mirror once: whether it answers, and the address its
|
|
// files are served at.
|
|
func (s *Sources) Check(ctx context.Context) {
|
|
for i, c := range s.clients {
|
|
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
list, err := c.List(ctx)
|
|
cancel()
|
|
s.mu.Lock()
|
|
was := s.healthy[i]
|
|
s.healthy[i] = err == nil
|
|
if err == nil && list.Base != "" {
|
|
s.bases[i] = strings.TrimSuffix(list.Base, "/")
|
|
}
|
|
s.mu.Unlock()
|
|
if was && err != nil {
|
|
s.log.Warn("a mirror does not answer; models it holds are fetched from another", "mirror", c.Addr(), "err", err)
|
|
} else if !was && err == nil {
|
|
s.log.Info("a mirror answers", "mirror", c.Addr(), "files", list.Base)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rewrite is a model's address on a mirror that answers: as it is when its
|
|
// own mirror answers or no other does, else the same path on the first that
|
|
// does.
|
|
func (s *Sources) Rewrite(src string) string {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
from := -1
|
|
for i, b := range s.bases {
|
|
if b != "" && (src == b || strings.HasPrefix(src, b+"/")) {
|
|
from = i
|
|
break
|
|
}
|
|
}
|
|
if from < 0 || s.healthy[from] {
|
|
return src
|
|
}
|
|
for i, b := range s.bases {
|
|
if i != from && b != "" && s.healthy[i] {
|
|
return b + strings.TrimPrefix(src, s.bases[from])
|
|
}
|
|
}
|
|
return src
|
|
}
|