Skip to content

Zero the vacated head slot in concurrent.Queue.Dequeue to release dead references - #598

Merged
Quim Muntal (qmuntal) merged 1 commit into
microsoft:mainfrom
PratikDhanaveFork:zero-vacated-queue-head-slot
Jul 23, 2026
Merged

Quim Muntal (qmuntal) merged 1 commit into
microsoft:mainfrom
PratikDhanaveFork:zero-vacated-queue-head-slot

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

Queue.Dequeue advanced the internal slice with q.items = q.items[1:] but never cleared the old head slot. The backing array therefore kept a live pointer to the dequeued value at the vacated index until the array eventually reallocated.

This change zeroes the head slot before re-slicing and drops the backing array once the queue empties:

item := q.items[0]
var zero T
q.items[0] = zero
q.items = q.items[1:]
if len(q.items) == 0 {
    q.items = nil
}
return item, true

Why

The queue is instantiated with reference element types that are long-lived and cycle steadily, e.g. Queue[*execution.MessageEnvelope] (workflow/inproc/runner.go), Queue[workflow.Event] (workflow/internal/execution/eventstream.go), and Queue[func(context.Context) error] (workflow/inproc/context.go). Because the head slot was never cleared, a dequeued value stayed GC-reachable through the backing array long after it was logically removed, retaining whatever object graph it pointed at until the slice happened to reallocate.

Clearing the vacated element mirrors Go's own slices.Delete semantics, which zero removed slots precisely so dead references can be collected promptly.

How it is tested

Two deterministic tests in internal/concurrent/queue_test.go:

  • TestQueue_DequeueZeroesVacatedSlot inspects the shared backing array through a full-capacity view and asserts the head slot is nil after Dequeue (while later elements keep the array alive).
  • TestQueue_DequeueDropsEmptyBackingArray asserts items is released to nil once the queue drains.

Both fail before the change and pass after. go build ./..., go vet ./internal/concurrent/..., and go test ./internal/concurrent/... all pass.

@PratikDhanave
PratikDhanave (PratikDhanave) requested a review from a team as a code owner July 23, 2026 02:23
Copilot AI review requested due to automatic review settings July 23, 2026 02:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes unintended memory retention in internal/concurrent.Queue by ensuring dequeued elements no longer remain GC-reachable via the slice’s backing array, and by releasing the backing array when the queue becomes empty.

Changes:

  • Zero the vacated head slot in Queue.Dequeue() before reslicing to avoid pinning dequeued references.
  • Set q.items = nil when the queue drains to drop the backing array entirely.
  • Add deterministic tests that validate head-slot zeroing and backing-array release on empty.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
internal/concurrent/queue.go Clears the dequeued slot and nils the slice when empty to prevent retaining dead references.
internal/concurrent/queue_test.go Adds tests verifying vacated-slot zeroing and backing-array release when the queue drains.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Dequeue advanced the slice with q.items = q.items[1:] without clearing
the old head slot, so the backing array kept the dequeued reference alive
until it reallocated. For long-lived reference-typed queues (message
envelopes, workflow events, deferred funcs) that cycle steadily, dead
values stayed GC-reachable far longer than necessary.

Zero the head before re-slicing and drop the backing array once the queue
empties, matching slices.Delete semantics so dead references are released
promptly. Add deterministic tests asserting the vacated slot is cleared
and the array is released when the queue drains.
@qmuntal
Quim Muntal (qmuntal) added this pull request to the merge queue Jul 23, 2026
Merged via the queue into microsoft:main with commit 855f0fb Jul 23, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants