refactor(addons): bound a connector call once per hop

ConnectorCall, localConnectorCall and Carrier.connectorRequest each set
their own 45 second timeout. On a frontend the first and third nested
inside each other, and on the owner the first and second did. Each hop
now sets it once (connectorTimeout): the forwarding carrier on a frontend,
and localConnectorCall on the owner, which must bound the work whichever
door the call came through. ConnectorCall no longer adds a third one.

The kind lookup in localConnectorCall uses slices.IndexFunc instead of a
loop that kept scanning after a match.

Assisted-by: Claude Code:claude-sonnet-5-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto committed 2026-11-03 08:45:52 +00:00
1 parent 4fbfc4586b
commit 564ee50df2
1 file changed
+12 -13
+12 -13
View File
@@ -9,6 +9,7 @@ import (
"fmt"
"io"
"net/http"
"slices"
"time"
"github.com/mudler/LocalAI/core/services/addons"
@@ -33,6 +34,11 @@ type connectorCall struct {
// this pinned, peer-authenticated host-only route. Do not raise global limits.
const connectorWireLimit = 2 << 20
// connectorTimeout bounds one connector round trip. It is set once per hop: by
// the forwarding carrier on a frontend, and by the owner when it runs the call,
// since the owner must bound the work whichever door the call came through.
const connectorTimeout = 45 * time.Second
func (g *Gateway) connectorSource() ConnectorSource {
if g.Connectors != nil {
return g.Connectors
@@ -66,8 +72,6 @@ func (g *Gateway) localConnectorKinds(ctx context.Context) ([]connectors.Kind, e
return out, nil
}
func (g *Gateway) ConnectorCall(ctx context.Context, s connectors.ConnectorCallScope, req contract.ConnectorRequest) (contract.ConnectorResponse, error) {
ctx, cancel := context.WithTimeout(ctx, 45*time.Second)
defer cancel()
if g.Forward != nil {
if s.Purpose == connectors.ConnectorPurposeBinding {
req.Token = ""
@@ -79,7 +83,7 @@ func (g *Gateway) ConnectorCall(ctx context.Context, s connectors.ConnectorCallS
return g.localConnectorCall(ctx, s, req)
}
func (g *Gateway) localConnectorCall(ctx context.Context, s connectors.ConnectorCallScope, req contract.ConnectorRequest) (contract.ConnectorResponse, error) {
ctx, cancel := context.WithTimeout(ctx, 45*time.Second)
ctx, cancel := context.WithTimeout(ctx, connectorTimeout)
defer cancel()
source := g.connectorSource()
if source == nil || g.ConnectorAuthority == nil {
@@ -89,16 +93,11 @@ func (g *Gateway) localConnectorCall(ctx context.Context, s connectors.Connector
if err != nil {
return contract.ConnectorResponse{}, fmt.Errorf("%w: %w", ErrUnavailable, err)
}
found := false
for _, k := range kinds {
if k.AddonID == s.AddonID && k.ID == s.Kind {
found = true
if req.Op == contract.ConnectorOpPoll && k.Delivery != contract.DeliveryPull {
return contract.ConnectorResponse{}, ErrForbidden
}
}
i := slices.IndexFunc(kinds, func(k addons.ConnectorKind) bool { return k.AddonID == s.AddonID && k.ID == s.Kind })
if i < 0 {
return contract.ConnectorResponse{}, ErrForbidden
}
if !found {
if req.Op == contract.ConnectorOpPoll && kinds[i].Delivery != contract.DeliveryPull {
return contract.ConnectorResponse{}, ErrForbidden
}
req, err = g.ConnectorAuthority.AuthorizeConnector(ctx, s, req)
@@ -122,7 +121,7 @@ func (g *Gateway) localConnectorCall(ctx context.Context, s connectors.Connector
return out, nil
}
func (c *Carrier) connectorRequest(ctx context.Context, method, path string, body any, out any) error {
ctx, cancel := context.WithTimeout(ctx, 45*time.Second)
ctx, cancel := context.WithTimeout(ctx, connectorTimeout)
defer cancel()
var raw []byte
if body != nil {