From 54932b1ac29c2f0bcac7df1e2a9126caf619e6c1 Mon Sep 17 00:00:00 2001 From: Deivid Soto Date: Wed, 27 May 2026 15:19:51 +0200 Subject: [PATCH] fix(daemon): defensive IsClosed check in watchSessionReady poll loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the deferred bajo-priority item from the fase 3.3b critico. Without this the watcher kept polling a torn-down HLSSession for up to 60 s — fine in current code paths (Close always pairs with ctx cancel which makes the select{} branch fire), but the function's correctness then leaned on a caller invariant rather than its own state check. Adding IsClosed() as a public wrapper around the existing isClosed() lets the watcher detect any future session-shutdown path (registry replace, idle sweep, internal kill) without touching the unexported helper. --- internal/cmd/daemon.go | 7 +++++++ internal/engine/hls.go | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/internal/cmd/daemon.go b/internal/cmd/daemon.go index a351c1c..2e0c074 100644 --- a/internal/cmd/daemon.go +++ b/internal/cmd/daemon.go @@ -960,6 +960,13 @@ func watchSessionReady(ctx context.Context, client *agent.Client, hsess *engine. ticker := time.NewTicker(200 * time.Millisecond) defer ticker.Stop() for { + // Session torn down through a path that didn't cancel ctx (registry + // replace, idle sweep, internal kill). Bail before polling further — + // without this check the watcher could keep alive for up to 60 s on + // a dead HLSSession that's never going to become ready. + if hsess.IsClosed() { + return + } // Cache HIT or seg-0 ready → notify + done. if hsess.FromCache() || hsess.ReadyCount() >= 1 { // Parent ctx so a session cancel mid-POST (user closed tab, diff --git a/internal/engine/hls.go b/internal/engine/hls.go index 4938c11..6acde30 100644 --- a/internal/engine/hls.go +++ b/internal/engine/hls.go @@ -534,6 +534,13 @@ func (s *HLSSession) ReadyCount() int { // circuit polling — a cache HIT is ready the moment we return. func (s *HLSSession) FromCache() bool { return s.fromCache } +// IsClosed reports whether Close() has been invoked. Exposed (vs the +// internal isClosed) so external watchers — the ready-webhook +// goroutine in cmd/daemon.go — can short-circuit polling on a session +// that was torn down through a different code path (registry replace, +// idle sweep) without racing on the unexported helper. +func (s *HLSSession) IsClosed() bool { return s.isClosed() } + // MasterPlaylist returns the rendered master.m3u8 contents. func (s *HLSSession) MasterPlaylist() string { return s.manifestRoot }