chat gets its own core connection, so a turn stops blocking every page (V-638)

ipc.Client serialises every call on one mutex, and mavweb routed 28
handlers plus /api/chat through the shared client. A turn is bounded
only by phraser.timeout, 60s in deploy, so a page load behind one could
wait that long.

/models already had its own connection for this reason. Chat gets the
third one. A failed dial logs and falls back to the shared client, which
is how it behaved before.

/api/ptt needs nothing here: it proxies to the voice port and never
touches this client.

A connection pool inside ipc.Client is the general form and stays
unbuilt until a second module is measured queueing.
This commit is contained in:
2026-08-06 22:59:53 +04:00
parent cbd8077d2c
commit 60e64dd83c
+19 -1
View File
@@ -58,6 +58,12 @@ func main() {
// mutex, so sharing the connection would freeze every other page for the // mutex, so sharing the connection would freeze every other page for the
// length of the load. See handleModels. // length of the load. See handleModels.
var swapConn modelController var swapConn modelController
// turnConn — a third connection, for POST /api/chat and nothing else, for
// the same reason /models has one (V-638). A chat turn routes, phrases and
// may act, bounded only by phraser.timeout at 60s, and every other handler
// on this server queues behind it on the shared client's one mutex. Nil ⇒
// chat shares the main connection, which is how it behaved before.
var turnConn ipc.CoreAPI
if *coreSock != "" { if *coreSock != "" {
c, err := ipc.DialWait(*coreSock, 60*time.Second) c, err := ipc.DialWait(*coreSock, 60*time.Second)
if err != nil { if err != nil {
@@ -71,6 +77,12 @@ func main() {
defer sc.Close() defer sc.Close()
swapConn = sc swapConn = sc
} }
if tc, err := ipc.Dial(*coreSock); err != nil {
log.Printf("chat: third core connection failed (%v) — /api/chat will share the main one and a turn will block the other pages", err)
} else {
defer tc.Close()
turnConn = tc
}
} }
// stepUpSession stays nil unless the passkey endpoints are wired below — it // stepUpSession stays nil unless the passkey endpoints are wired below — it
@@ -208,7 +220,13 @@ func main() {
// decides how every utterance is routed and how every reply is worded. // decides how every utterance is routed and how every reply is worded.
mux.HandleFunc("/tools", gatedPage(handleTools)) mux.HandleFunc("/tools", gatedPage(handleTools))
mux.HandleFunc("/routines", gatedPage(handleRoutines)) mux.HandleFunc("/routines", gatedPage(handleRoutines))
mux.HandleFunc("/api/chat", gatedPage(handleChatAPI)) mux.HandleFunc("/api/chat", func(w http.ResponseWriter, r *http.Request) {
c := turnConn
if c == nil {
c = core
}
handleChatAPI(w, r, c, stepUpSession, *requireStepUp)
})
mux.HandleFunc("/api/revert", gatedPage(handleRevert)) mux.HandleFunc("/api/revert", gatedPage(handleRevert))
mux.HandleFunc("/api/correct", gatedPage(handleCorrectAPI)) mux.HandleFunc("/api/correct", gatedPage(handleCorrectAPI))
mux.HandleFunc("/models", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/models", func(w http.ResponseWriter, r *http.Request) {