perf: skip watchdog goroutine and dedup for uncancellable contexts#29
Open
costela wants to merge 1 commit into
Open
perf: skip watchdog goroutine and dedup for uncancellable contexts#29costela wants to merge 1 commit into
costela wants to merge 1 commit into
Conversation
Every call spawned a watchdog goroutine plus a context.WithCancelCause allocation, and wrapped the observer in a dedup layer (a second allocation) to guard against that goroutine racing the deferred observe. The watchdog only exists to record a context cancellation/deadline promptly. When the incoming context can never be canceled (ctx.Done() == nil, e.g. context.Background()), it can never fire usefully, so skip both the goroutine and the context allocation. With no watchdog there is no second observer (normal return and panic go through the same single defer), so the dedup wrapper is unnecessary too and is moved inside the watchdog branch. For uncancellable-context callers this removes a goroutine and drops the per-call allocation count from 2 to 1. Callers passing a cancellable or deadline context are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR optimizes the Wrap hot path by avoiding work that cannot provide value when the caller’s context is uncancellable (e.g., context.Background()), reducing per-call allocations and eliminating an unnecessary goroutine.
Changes:
- Gate the watchdog goroutine and
context.WithCancelCauseallocation behindif ctx.Done() != nil. - Move the
dedupObservableCallwrapper inside the watchdog branch since only that path can result in concurrent observation.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
On the uncancellable-context fast path, skip the per-call watchdog goroutine, its
context.WithCancelCauseallocation, and the observer dedup wrapper.Why
Every call paid: a goroutine + a context allocation (watchdog) and a second allocation (dedup wrapper). The watchdog only serves to record a context cancellation/deadline promptly — when
ctx.Done() == nil(e.g.context.Background()) it can never fire usefully. And with no watchdog there is no second observer racing the deferred observe, so the dedup is redundant too.How
if ctx.Done() != nil { ... }gates the watchdog goroutine + context allocation.dedupObservableCallwrapper moves inside that branch (normal return and panic share one defer, so they're mutually exclusive — single observer).Results
benchstatofBenchmarkHoglet_Do_*(which call throughcontext.Background(), the uncancellable fast path),mainvs this branch,-count=10on an AMD Ryzen AI 9 HX 370:Latency drops ~75–80%, allocations 5 → 1/op, bytes 192 → 16 B/op — plus one fewer goroutine per call. Cancellable/deadline-context callers take the unchanged path and are unaffected.
Notes
Relies on breaker middleware observing synchronously (documented inline); an async middleware observer must dedup itself.
🤖 Generated with Claude Code