Skip to content

fix(runtime): keep the newer event when the supersede log panics - #145

Merged
ThomasK33 merged 11 commits into
mainfrom
fix/supersede-log-panic-no-loss
Oct 30, 2026
Merged

ThomasK33 merged 11 commits into
mainfrom
fix/supersede-log-panic-no-loss

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Why

Every open follow-up issue in coder/chat gets one small, reviewed PR, and this one closes #143. The supersede log line ran on the newer delivery's own goroutine, after MarkEvent and before that delivery's run started. When the app's slog handler panicked on that line, net/http aborted the request, the platform's retry was deduped, and the newer event never ran. The triage asked to finish the handoff before the log line, but that alone cannot help, because the goroutine that panics is the only one that would run the newer event. A delivery whose prelude panicked also left its dispatch span open, so the loss did not show in tracing.

What changed

  • registerPending and takeQueueTurn finish the handoff under queueMu without calling the app logger. They return a supersession with the displaced event's adapter, ID, and thread ID, so the newer tail does not keep the displaced payload reachable. The dispatcher writes the same chat <strategy> superseded line once the newer run is secured. Under DispatchDeferred that is right after the tail starts. Under DispatchSync it is after the handler returns and the lock is released.
  • prelude now opens the span, calls acceptAndRoute (the old prelude body), and ends the span with the error outcome when acceptAndRoute panicked. ADR 0015 invariant 4 names this, and gains the triage rule that a slog handler panic cannot drop an event the strategy keeps.

Scope

  • Covers the debounce supersede, the queue turn, and the queue waiter supersede, in both dispatch modes.
  • The panic still reaches net/http. This PR adds no recover.
  • A panic in a detached tail's own log lines still ends the process, as on main.

Tradeoffs

  • The displaced waiter could log its own supersession, which keeps the sync log line next to the supersede moment. I prototyped it. Under DispatchDeferred the waiter runs on a detached tail, so its log panic crashed the test binary in every deferred logger-panic test. Under sync it failed the existing TestQueueSupersedeLoggerPanicReleasesItsThreadLock and left the waiter's span open.
  • With this choice, the supersede line under DispatchSync now appears after the newer handler ends.

Blast Radius

Only the queue and debounce supersede paths and the prelude's panic path change. Every supersession still writes one line with the same message and keys. The line can now come after the displaced waiter's or the newer tail's own log lines.

Verification

  • Each fix lands after its failing test, behind a real httptest.Server. Before the fixes, debounce runs the displaced event instead of the newest one, the queue's newer event never runs, the displaced queue waiter is never superseded, and the span never ends.
  • Wrong fixes fail the new tests. The triage reorder alone still loses the newer event, and its orphan waiter trips the leaveQueueScope assert. Logging before the run is secured also loses the event. A recover returns 200 instead of aborting the request. An unconditional span end ends spans twice. A span end tied to the unfinished acceptance entry misses a panic after MarkEvent.
  • A retention test holds weak pointers to the displaced event and its message while the newer handler runs. Both are collected.
  • The new tests and the Queue, Debounce, Deferred, Burst, Admission, Concurrent, and Panic tests pass 20 of 20 runs under -race. go vet and go test pass in every workspace module, and go test -race passes for the root package.

Closes #143


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

A MarkEvent panic in the prelude aborts the request, but the delivery's
dispatch span never ends, so tracing shows no terminal outcome for it.
A logger panic on the unrouted-message line comes after MarkEvent, so a
fix keyed to the unfinished acceptance entry would miss it.
The prelude owns its span until it ends it on a resolved outcome or
returns it in work. A panic does neither, so a deferred check on the
zero results ends the span with the error outcome after the other
releases run. The panic still reaches net/http. ADR 0015 invariant 4
gains the rule.
The supersede log runs on the displacing delivery's own goroutine before
that delivery's run starts. When the app's slog handler panics there,
the newer event never runs. Under debounce the displaced A runs instead
of B. Under queue, C never runs after it takes the free lock, and a
waiter displaced through registerPending is never superseded.
registerPending and takeQueueTurn now finish the handoff under queueMu
without calling the app logger, and return the displaced waiter's event.
The dispatcher writes the same supersede line once the newer delivery's
run is secured: under DispatchDeferred right after its tail starts, and
under DispatchSync after its handler returns. A panicking slog handler on
that line still aborts the request, but the newer event runs and the
displaced waiter ends superseded.
Signed-off-by: Thomas Kosiewski <tk@coder.com>
The prelude now opens the span, runs acceptAndRoute, and ends the span
with the error outcome when acceptAndRoute did not return. That replaces
the check on zero named results, which needed a comment to read.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T10:34:50.445391Z 07da16f Manual request
🔒 Security Review ✅ Completed 2026-10-30T10:37:16.138279Z 07da16f Manual request

Security findings

Advisory findings (1)

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 73dd13dc99

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛡️ Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: 73dd13dc99

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread runtime.go Resolved
Codex security review on #145: the newer delivery's tail copies
work.supersedes, a pointer to the displaced Event. After the displaced
tail frees its admission slot, its payload stays reachable until the
newer tail ends, so it no longer counts against MaxDetached.
A fix that copies the displaced Event by value would free the Event but
keep its Message and Raw payload reachable.
work.supersedes now holds a supersession with the displaced event's
adapter, ID, and thread ID instead of the *Event. The newer tail no
longer keeps the displaced payload reachable after the displaced tail
frees its admission slot, so ADR 0015 invariant 1 holds again.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 47f376216d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 47f376216d

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

The waiter comments said registerPending writes the superseded_by
record. The displacing delivery's dispatcher writes it now. ADR 0015
invariant 4 gains the triage rule that a slog handler panic cannot drop
an event the Concurrency Strategy keeps.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 07da16fc05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 07da16fc05

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33
ThomasK33 merged commit f437dee into main Oct 30, 2026
2 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.

runtime: a panicking slog handler on the supersede log loses queued events

1 participant