Skip to content

fix(queue): resolve data races and shutdown issues in worker queue - #282

Merged
dhollinger merged 1 commit into
masterfrom
fix-queue-races
Oct 6, 2026
Merged

dhollinger merged 1 commit into
masterfrom
fix-queue-races

Conversation

@dhollinger

@dhollinger dhollinger commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

A race condition was found in queue.go handling of queue items with concurrency. The QueueItem could be accessed by each goroutine at the same time leading to issues where new channels and queues would be created orphaning old queues and losing data. This bug was found during the run of some local go test runs with the -race flag passed. Tests were updated to account for these changes.

This PR Does the following things to fix the issue:

  1. Use a Read/Write Mutex to guard the items slice and all QueueItem state mutations so the concurrent AddToQueue and GetQueueItems calls don't enter into a race with the worker's updates.
  2. Return detached snapshots of an item from AddToQueue and GetQueueItem so API handlers can safely marshal them while the worker processes jobs.
  3. Updated trimItems to drop the oldest item(s) instead of the newest ones and trim the queue-full path too so the history is bounded. May need some discussion on this.
  4. Updated the Work() function to make it idempotent so a second call no longer replaces the existing job channel and leaks the previous worker goroutine.
  5. Updated the Dispose() function so that it closes the job channel under a lock so in-flight queueJob sends can't panic. It waits for the worker to drain buffered/in-flight jobs. It also is made idemopotent so if it's called more than once, it's safe. AddToQueue after Dispose now returns a clean error instead of just panicking.

The code in this PR was built in conjunction with AI, but not exclusively by it. The PR is written entirely by hand.

- Guard the items slice and all QueueItem state mutations with a
  sync.RWMutex so concurrent AddToQueue/GetQueueItems calls and the
  worker's updates can't race
- Return detached item snapshots from AddToQueue and GetQueueItems so
  API handlers can safely marshal them while the worker processes jobs
- trimItems: drop the oldest items instead of the newest ones and trim
  on the queue-full path too so the history stays bounded
- Work: make idempotent so a second call no longer replaces the job
  channel and leaks the previous worker goroutine
- Dispose: close the job channel under a lock so in-flight queueJob
  sends can't panic, wait for the worker to drain buffered/in-flight
  jobs, and make it safe to call multiple times; AddToQueue after
  Dispose now returns a clean error instead of panicking

Add regression tests under lib/queue that fail on the old code with
-race and cover trimming, Work idempotency, Dispose draining and
post-Dispose queuing.
@dhollinger dhollinger self-assigned this Oct 5, 2026
@dhollinger dhollinger added the bug Something isn't working label Oct 5, 2026
@sebastianrakel

Copy link
Copy Markdown
Member

LGTM

@dhollinger
dhollinger merged commit e271c52 into master Oct 6, 2026
1 check passed
@dhollinger
dhollinger deleted the fix-queue-races branch October 6, 2026 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants