Skip to content

runtime: harden the queue turn record (idempotent release, State panics, retention test) #129

Closed

Description

@ThomasK33

An independent review of #124 (the fix for #123) found three small gaps in the queue turn record. None of them is reachable on a normal path today, so #124 did not change its reviewed head for them.

1. The queue release wrapper is not idempotent

admit returns an idempotent release (admission.go, the admit doc comment and its sync.OnceFunc). Under DispatchDeferred, dispatch in #124 wraps that release so it also calls leaveQueueScope, and the wrapper is not idempotent. A second call would decrement the scope's active count twice and trip the leaveQueueScope assert, which panics in production.

Today each queue delivery calls the wrapper exactly once: either in the resolved or errored branch of dispatchDeferred, or through defer work.releaseAdmission() in the detached tail. Under burst, leaveQueueScope does nothing.

Suggested fix: wrap the deferred release in sync.OnceFunc, or clear entry.scope in leaveQueueScope.

2. A panicking State call can leave a stale waiter behind the record

Traced by reading the code, not tested. Under DispatchSync, suppose State.AcquireLock panics while a queue waiter polls, and net/http recovers the panic. The waiter's pending entry stays. The deferred leaveQueueScope then finds active == 0 with a pending entry, and its assert fires. Later ignored, duplicate or errored deliveries for that scope fire the same assert until some delivery takes the turn or registers as the waiter. On main, the same panic only leaves the stale entry.

The fix for item 1 (clearing entry.scope) does not cover this. One option: clear the delivery's own pending entry in leaveQueueScope before the assert, or make queueForLock clear it in a defer.

3. No test pins the record's retention

No test reads queueTurns, so a missing delete in leaveQueueScope would pass every Go test (the TLA+ model's IdleScopeRetainsNothing catches it). Add a bounded-retention test with an export hook, as BurstScopeCount in export_test.go does for burst.

Docs

ConcurrencyQueue's GoDoc relies on Adapter.ValidateThreadID not blocking, but only the Observer interface states that rule. Add it to ValidateThreadID in types.go.

Owner and trigger

The coder/chat maintainer triages this. Fix it with the next change to queue coordination, or before the queue strategy is called production-grade, whichever comes first.


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: xhigh

Activity

  1. added
    needs-triageMaintainer needs to evaluate this issue
    bugSomething isn't working
    ready-for-agentFully specified, ready for an AFK agent
    and removed
    needs-triageMaintainer needs to evaluate this issue
    on Oct 29, 2026
  2. ThomasK33 commented on Oct 29, 2026

    @ThomasK33
    MemberAuthor

    Triage: ready-for-agent. Each item names its fix. Start this after #124 merges, because all four items change code that #124 adds.

    Order of work:

    1. Item 3 (retention test) first. It gives the later steps a red check.
    2. Item 1 (wrap the deferred release in sync.OnceFunc).
    3. Item 2 (clear the delivery's own pending entry in leaveQueueScope before the assert). It needs a test: a State whose AcquireLock panics while a waiter polls.
    4. The ValidateThreadID doc line.

    Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: medium

  3. added a commit that references this issue on Oct 29, 2026
    0e624ab
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingready-for-agentFully specified, ready for an AFK agent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions