From 7900b8c4dacb5adec7c0a38ceedbca65b0e81469 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Feb 2026 20:30:22 +0000 Subject: [PATCH] fix(bugseti): hold mutex during entire QueueService initialization Move shared state initialization (issues, seen) and the load() call inside the mutex scope in NewQueueService() to eliminate the race window where concurrent callers could observe partially initialized state. Remove the redundant heap.Init before the lock since load() already calls heap.Init when restoring from disk. Add documentation to save() and load() noting they must be called with q.mu held. Fixes #51 Co-Authored-By: Claude Opus 4.6 --- internal/bugseti/queue.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/internal/bugseti/queue.go b/internal/bugseti/queue.go index b89b6ff..6b06d5c 100644 --- a/internal/bugseti/queue.go +++ b/internal/bugseti/queue.go @@ -99,13 +99,17 @@ func (h *issueHeap) Pop() any { func NewQueueService(config *ConfigService) *QueueService { q := &QueueService{ config: config, - issues: make(issueHeap, 0), - seen: make(map[string]bool), } - heap.Init(&q.issues) + + // Hold the lock for the entire initialization sequence so that all + // shared state (issues, seen, current) is fully populated before + // any concurrent caller can observe the service. q.mu.Lock() - q.load() // Load persisted queue - q.mu.Unlock() + defer q.mu.Unlock() + + q.issues = make(issueHeap, 0) + q.seen = make(map[string]bool) + q.load() // Load persisted queue (overwrites issues/seen if file exists) return q } @@ -247,7 +251,7 @@ type queueState struct { Seen []string `json:"seen"` } -// save persists the queue to disk. +// save persists the queue to disk. Must be called with q.mu held. func (q *QueueService) save() { dataDir := q.config.GetDataDir() if dataDir == "" { @@ -278,7 +282,7 @@ func (q *QueueService) save() { } } -// load restores the queue from disk. +// load restores the queue from disk. Must be called with q.mu held. func (q *QueueService) load() { dataDir := q.config.GetDataDir() if dataDir == "" {