52548abe02
Verbatim move: backend, borrowedBackend, llamaProc, lineTail, extractPort and listenRE. The spawn functions follow in the next commit.
88 lines
2.3 KiB
Go
88 lines
2.3 KiB
Go
// phraser/server.go — the llama-server this phraser talks to: the child
|
|
// process it may own, and the startup handshake that waits for the port.
|
|
//
|
|
// Nothing here knows what a prompt is. Split out of llmphraser.go so the
|
|
// phrasing paths and the process lifetime read as two subjects.
|
|
package phraser
|
|
|
|
import (
|
|
"context"
|
|
"os/exec"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
var listenRE = regexp.MustCompile(`listening on (https?://\S+)`)
|
|
|
|
// backend — one llama-server this phraser talks to. Two implementations: a
|
|
// llamaProc we spawned and must reap, and a borrowedBackend someone else owns.
|
|
type backend interface {
|
|
BaseURL() string
|
|
Close() error
|
|
}
|
|
|
|
// borrowedBackend — a server started and owned by someone else (the phrasing
|
|
// scorer's shared llama-server). Closing it is a no-op by construction.
|
|
type borrowedBackend string
|
|
|
|
func (b borrowedBackend) BaseURL() string { return string(b) }
|
|
func (b borrowedBackend) Close() error { return nil }
|
|
|
|
// llamaProc — a llama-server child process plus the goroutine reading its
|
|
// stderr. Close kills and reaps it; see the Pdeathsig note in spawnLlamaServer.
|
|
type llamaProc struct {
|
|
base string
|
|
cmd *exec.Cmd
|
|
cancel context.CancelFunc
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
func (l *llamaProc) BaseURL() string { return l.base }
|
|
|
|
func (l *llamaProc) Close() error {
|
|
l.cancel()
|
|
if l.cmd != nil && l.cmd.Process != nil {
|
|
_ = l.cmd.Process.Kill()
|
|
_ = l.cmd.Wait() // reap the process — without Wait, the child becomes a zombie
|
|
}
|
|
l.wg.Wait()
|
|
return nil
|
|
}
|
|
|
|
// lineTail keeps the last few startup lines so a server that dies before it
|
|
// listens can say why in the error, not just "EOF". Written by the reader
|
|
// goroutine and read by whoever gives up on startup, so it takes a lock.
|
|
type lineTail struct {
|
|
mu sync.Mutex
|
|
lines []string
|
|
}
|
|
|
|
const lineTailMax = 12
|
|
|
|
func (t *lineTail) add(line string) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
t.lines = append(t.lines, line)
|
|
if len(t.lines) > lineTailMax {
|
|
t.lines = t.lines[len(t.lines)-lineTailMax:]
|
|
}
|
|
}
|
|
|
|
func (t *lineTail) String() string {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
if len(t.lines) == 0 {
|
|
return "(no output)"
|
|
}
|
|
return strings.Join(t.lines, " | ")
|
|
}
|
|
|
|
func extractPort(listen string) string {
|
|
_, port, _ := strings.Cut(listen, ":")
|
|
if port == "" {
|
|
return "0"
|
|
}
|
|
return port
|
|
}
|