From 359ae81d1fed3feb4986c1bafb43c2e873931d00 Mon Sep 17 00:00:00 2001 From: kami Date: Fri, 3 Jul 2026 12:11:24 +0200 Subject: [PATCH] piper: pipe cleanup on early return, write error; misc error hygiene piper_handler: close stdin/stdout pipes on Start() failure and on WriteString error instead of leaking fds. Propagate WriteString error. worker/client: log SetDeadline errors instead of discarding them. voice/session: pushAudio marshals params inline and returns the marshal error instead of swallowing it via mustParams (removed). tool/matcher: log ListTools errors instead of silently returning an empty allowlist that refuses every act. config: applyDefaults now sets RouterThreshold and ToolTimeout defaults so consumers self-contained defaults are belt-and-suspenders. --- cmd/mavttsd/piper_handler.go | 11 ++++++++++- internal/config/config.go | 10 ++++++++++ internal/tool/tool.go | 2 ++ internal/voice/session.go | 14 +++++--------- internal/worker/client.go | 9 +++++++-- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/cmd/mavttsd/piper_handler.go b/cmd/mavttsd/piper_handler.go index 308a193..2853683 100644 --- a/cmd/mavttsd/piper_handler.go +++ b/cmd/mavttsd/piper_handler.go @@ -56,17 +56,26 @@ func (h *piperHandler) Synthesize(ctx context.Context, req worker.SynthesizeReq) stdout, err := cmd.StdoutPipe() if err != nil { + stdin.Close() return worker.SynthesizeResp{}, fmt.Errorf("piper: stdout pipe: %w", err) } if err := cmd.Start(); err != nil { + stdin.Close() + stdout.Close() return worker.SynthesizeResp{}, fmt.Errorf("piper: start: %w", err) } - _, _ = io.WriteString(stdin, req.Text) + if _, err := io.WriteString(stdin, req.Text); err != nil { + stdin.Close() + stdout.Close() + _ = cmd.Wait() + return worker.SynthesizeResp{}, fmt.Errorf("piper: write text: %w", err) + } stdin.Close() rawPCM, readErr := io.ReadAll(stdout) + stdout.Close() waitErr := cmd.Wait() if waitErr != nil { diff --git a/internal/config/config.go b/internal/config/config.go index 7cfa417..992cef0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -223,6 +223,7 @@ const ( DefaultRepeatInterval = 5 * time.Minute DefaultAutotuneInterval = 10 * time.Minute DefaultRouterThreshold = 0.35 + DefaultToolTimeout = 30 * time.Second ) // Load reads the JSON config at path and applies defaults. A missing file is @@ -274,6 +275,15 @@ func (c *Config) applyDefaults() { c.SocketPath = filepath.Join(defaultRuntimeDir(), "mavend.sock") } } + + if c.Voice != nil { + if c.Voice.RouterThreshold <= 0 { + c.Voice.RouterThreshold = DefaultRouterThreshold + } + if c.Voice.ToolTimeout <= 0 { + c.Voice.ToolTimeout = Duration(DefaultToolTimeout) + } + } } func (c *Config) validate() error { diff --git a/internal/tool/tool.go b/internal/tool/tool.go index a0363b6..24478b1 100644 --- a/internal/tool/tool.go +++ b/internal/tool/tool.go @@ -24,6 +24,7 @@ import ( "context" "errors" "fmt" + "log" "os/exec" "strings" "time" @@ -117,6 +118,7 @@ func NewMatcher(api API) *Matcher { return &Matcher{api: api} } func (m *Matcher) names() []string { ts, err := m.api.ListTools(context.Background(), "enabled") if err != nil { + log.Printf("tool: list enabled tools: %v", err) return nil } names := make([]string, len(ts)) diff --git a/internal/voice/session.go b/internal/voice/session.go index b72a161..f7e2f2a 100644 --- a/internal/voice/session.go +++ b/internal/voice/session.go @@ -69,7 +69,11 @@ func (s *Session) pushAudio(p AudioNudgePush) error { if s.closed || s.conn == nil { return fmt.Errorf("voice: session %d closed: %w", s.ID, ErrNoSession) } - return writeFrame(s.conn, Push{Kind: PushKindAudioNudge, Params: mustParams(p)}) + params, err := json.Marshal(p) + if err != nil { + return fmt.Errorf("voice: marshal push params: %w", err) + } + return writeFrame(s.conn, Push{Kind: PushKindAudioNudge, Params: params}) } func (s *Session) shutdown() { @@ -167,11 +171,3 @@ func (r *Sessions) PushToMostRecent(ctx context.Context, p AudioNudgePush) error return best.pushAudio(p) } -// helper that returns a fixed nil-error marshal so the push call site is short. -func mustParams(v any) json.RawMessage { - if v == nil { - return nil - } - b, _ := jsonMarshal(v) - return b -} \ No newline at end of file diff --git a/internal/worker/client.go b/internal/worker/client.go index c9e5976..23e3a40 100644 --- a/internal/worker/client.go +++ b/internal/worker/client.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "log" "net" "sync" "time" @@ -95,9 +96,13 @@ func (c *Client) call(ctx context.Context, m Method, params any, out any) error // half-sent frame would desync the stream. Teardown is the clean // recovery; the next call re-dials. if dl, ok := ctx.Deadline(); ok { - _ = c.c.SetDeadline(dl) + if err := c.c.SetDeadline(dl); err != nil { + log.Printf("worker: set deadline: %v", err) + } } else { - _ = c.c.SetDeadline(time.Now().Add(defaultCallTimeout)) + if err := c.c.SetDeadline(time.Now().Add(defaultCallTimeout)); err != nil { + log.Printf("worker: set deadline: %v", err) + } } defer c.c.SetDeadline(time.Time{})