From a6e9cc2c5e9e630095264b1fecb4f45c87c29fcf Mon Sep 17 00:00:00 2001 From: eduard256 Date: Sat, 22 Nov 2025 19:48:03 +0300 Subject: [PATCH] Fix SSE timeout issues with long-running stream discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: - WriteTimeout was 30 seconds - Progress only sent when values changed - Long ffprobe tests (7-8s each) could cause 30+ seconds without writes - Result: "curl: (18) transfer closed with outstanding read data remaining" Solution: - Increase WriteTimeout from 30s to 5 minutes - Send progress every 1 second (instead of 3 seconds) - Always send progress, even if values unchanged - Guarantees write every second, preventing timeout Changes: - internal/config/config.go: WriteTimeout 30s → 5min - internal/camera/discovery/scanner.go: - Progress ticker 3s → 1s - Remove "only if changed" check - Always send progress to keep connection alive Testing: - HiWatch camera with 591 streams: Previously timed out at ~338/591 - Should now complete all 591 streams without timeout --- internal/camera/discovery/scanner.go | 20 +++++++------------- internal/config/config.go | 2 +- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/internal/camera/discovery/scanner.go b/internal/camera/discovery/scanner.go index 6296333..e8ff25d 100644 --- a/internal/camera/discovery/scanner.go +++ b/internal/camera/discovery/scanner.go @@ -409,26 +409,20 @@ func (s *Scanner) testStreamsConcurrently(ctx context.Context, streams []models. defer cancelProgress() go func() { - ticker := time.NewTicker(3 * time.Second) + ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() - lastTested := int32(0) - for { select { case <-progressCtx.Done(): return case <-ticker.C: - currentTested := atomic.LoadInt32(&tested) - // Only send if there's been progress - if currentTested != lastTested { - _ = streamWriter.SendJSON("progress", models.ProgressMessage{ - Tested: int(currentTested), - Found: int(atomic.LoadInt32(&found)), - Remaining: len(streams) - int(currentTested), - }) - lastTested = currentTested - } + // Send progress every second to prevent WriteTimeout + _ = streamWriter.SendJSON("progress", models.ProgressMessage{ + Tested: int(atomic.LoadInt32(&tested)), + Found: int(atomic.LoadInt32(&found)), + Remaining: len(streams) - int(atomic.LoadInt32(&tested)), + }) } } }() diff --git a/internal/config/config.go b/internal/config/config.go index 961aebe..719bd30 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -73,7 +73,7 @@ func Load() *Config { Server: ServerConfig{ Listen: ":4567", // Default listen address ReadTimeout: 30 * time.Second, - WriteTimeout: 30 * time.Second, + WriteTimeout: 5 * time.Minute, // Increased for SSE long-polling }, Database: DatabaseConfig{ DataPath: dataPath,