From 6f676d1bd412ca89355cfa7b4df741cd88fb4453 Mon Sep 17 00:00:00 2001 From: Goon Date: Sun, 31 May 2026 17:01:35 +0700 Subject: [PATCH] fix(cron): wait for job goroutines on stop --- internal/cron/service.go | 12 ++++++++++-- internal/cron/service_execution.go | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/cron/service.go b/internal/cron/service.go index 0e8d7a03..eee8765f 100644 --- a/internal/cron/service.go +++ b/internal/cron/service.go @@ -13,6 +13,7 @@ type Service struct { onJob JobHandler running bool stopChan chan struct{} + wg sync.WaitGroup mu sync.Mutex runLog []RunLogEntry // in-memory run history (last 200 entries) retryCfg RetryConfig // retry config for failed jobs @@ -89,7 +90,11 @@ func (cs *Service) Start() error { // after a previous Stop() returned but the runLoop goroutine hasn't yet // executed its ticker construction. tick := runLoopTickInterval - go cs.runLoop(cs.stopChan, tick) + cs.wg.Add(1) + go func() { + defer cs.wg.Done() + cs.runLoop(cs.stopChan, tick) + }() slog.Info("cron service started", "jobs", len(cs.store.Jobs)) return nil @@ -98,14 +103,17 @@ func (cs *Service) Start() error { // Stop halts the scheduling loop. func (cs *Service) Stop() { cs.mu.Lock() - defer cs.mu.Unlock() if !cs.running { + cs.mu.Unlock() return } close(cs.stopChan) cs.running = false + cs.mu.Unlock() + + cs.wg.Wait() slog.Info("cron service stopped") } diff --git a/internal/cron/service_execution.go b/internal/cron/service_execution.go index fd8a7a5d..fdd13485 100644 --- a/internal/cron/service_execution.go +++ b/internal/cron/service_execution.go @@ -216,7 +216,9 @@ func (cs *Service) checkJobs() { // agent loop stuck), the entire cron scheduler would stop checking for new // due jobs. Now each job runs independently with panic recovery. for _, dj := range dueJobs { + cs.wg.Add(1) go func(id string, scheduledAtMS int64) { + defer cs.wg.Done() defer safego.Recover(nil, "job_id", id) cs.executeJobByID(id, scheduledAtMS) }(dj.id, dj.scheduledAtMS)