From d27cf3545baa8dd265342851239460099eeea14a Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Fri, 27 Mar 2026 23:27:03 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20scheduler=20race=20condition=20=E2=80=94?= =?UTF-8?q?=20guard=20initial-run=20goroutines=20with=20atomic=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "run immediately on start" goroutines in 5 scheduler loops did not set the idempotency guard (atomic.Bool), allowing the first ticker tick to spawn a concurrent execution. The race detector caught overlapping goroutines calling the same service method simultaneously. Fix: set the Running flag before spawning the initial goroutine and clear it in the defer, same pattern as ticker-triggered goroutines. Co-Authored-By: Claude Opus 4.6 --- internal/scheduler/scheduler.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/internal/scheduler/scheduler.go b/internal/scheduler/scheduler.go index 2c67359..6db1211 100644 --- a/internal/scheduler/scheduler.go +++ b/internal/scheduler/scheduler.go @@ -157,10 +157,12 @@ func (s *Scheduler) renewalCheckLoop(ctx context.Context) { ticker := time.NewTicker(s.renewalCheckInterval) defer ticker.Stop() - // Run immediately on start + // Run immediately on start (with idempotency guard) + s.renewalCheckRunning.Store(true) s.wg.Add(1) go func() { defer s.wg.Done() + defer s.renewalCheckRunning.Store(false) s.runRenewalCheck(ctx) }() @@ -204,10 +206,12 @@ func (s *Scheduler) jobProcessorLoop(ctx context.Context) { ticker := time.NewTicker(s.jobProcessorInterval) defer ticker.Stop() - // Run immediately on start + // Run immediately on start (with idempotency guard) + s.jobProcessorRunning.Store(true) s.wg.Add(1) go func() { defer s.wg.Done() + defer s.jobProcessorRunning.Store(false) s.runJobProcessor(ctx) }() @@ -251,10 +255,12 @@ func (s *Scheduler) agentHealthCheckLoop(ctx context.Context) { ticker := time.NewTicker(s.agentHealthCheckInterval) defer ticker.Stop() - // Run immediately on start + // Run immediately on start (with idempotency guard) + s.agentHealthCheckRunning.Store(true) s.wg.Add(1) go func() { defer s.wg.Done() + defer s.agentHealthCheckRunning.Store(false) s.runAgentHealthCheck(ctx) }() @@ -297,10 +303,12 @@ func (s *Scheduler) notificationProcessLoop(ctx context.Context) { ticker := time.NewTicker(s.notificationProcessInterval) defer ticker.Stop() - // Run immediately on start + // Run immediately on start (with idempotency guard) + s.notificationProcessRunning.Store(true) s.wg.Add(1) go func() { defer s.wg.Done() + defer s.notificationProcessRunning.Store(false) s.runNotificationProcess(ctx) }() @@ -383,10 +391,12 @@ func (s *Scheduler) networkScanLoop(ctx context.Context) { ticker := time.NewTicker(s.networkScanInterval) defer ticker.Stop() - // Run immediately on start + // Run immediately on start (with idempotency guard) + s.networkScanRunning.Store(true) s.wg.Add(1) go func() { defer s.wg.Done() + defer s.networkScanRunning.Store(false) s.runNetworkScan(ctx) }()