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 <noreply@anthropic.com>
This commit is contained in:
Claude 2026-02-12 20:30:22 +00:00 committed by Snider
parent 68065df140
commit b512d7192d

View file

@ -99,13 +99,17 @@ func (h *issueHeap) Pop() any {
func NewQueueService(config *ConfigService) *QueueService { func NewQueueService(config *ConfigService) *QueueService {
q := &QueueService{ q := &QueueService{
config: config, 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.mu.Lock()
q.load() // Load persisted queue defer q.mu.Unlock()
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 return q
} }
@ -247,7 +251,7 @@ type queueState struct {
Seen []string `json:"seen"` 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() { func (q *QueueService) save() {
dataDir := q.config.GetDataDir() dataDir := q.config.GetDataDir()
if dataDir == "" { 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() { func (q *QueueService) load() {
dataDir := q.config.GetDataDir() dataDir := q.config.GetDataDir()
if dataDir == "" { if dataDir == "" {