Skip to content

perf: skip watchdog goroutine and dedup for uncancellable contexts#29

Open
costela wants to merge 1 commit into
mainfrom
perf/skip-watchdog-uncancellable
Open

perf: skip watchdog goroutine and dedup for uncancellable contexts#29
costela wants to merge 1 commit into
mainfrom
perf/skip-watchdog-uncancellable

Conversation

@costela

@costela costela commented Jun 26, 2026

Copy link
Copy Markdown
Member

What

On the uncancellable-context fast path, skip the per-call watchdog goroutine, its context.WithCancelCause allocation, 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.
  • The dedupObservableCall wrapper moves inside that branch (normal return and panic share one defer, so they're mutually exclusive — single observer).

Results

benchstat of BenchmarkHoglet_Do_* (which call through context.Background(), the uncancellable fast path), main vs this branch, -count=10 on an AMD Ryzen AI 9 HX 370:

goos: linux
goarch: amd64
pkg: github.com/exaring/hoglet
cpu: AMD Ryzen AI 9 HX 370 w/ Radeon 890M
                           │   old.txt   │               new.txt               │
                           │   sec/op    │   sec/op     vs base                │
Hoglet_Do_EWMA-24            563.0n ± 1%   140.3n ± 5%  -75.08% (p=0.000 n=10)
Hoglet_Do_SlidingWindow-24   563.8n ± 1%   115.1n ± 1%  -79.58% (p=0.000 n=10)
geomean                      563.3n        127.1n       -77.44%

                           │   old.txt   │              new.txt               │
                           │    B/op     │    B/op     vs base                │
Hoglet_Do_EWMA-24            192.00 ± 2%   16.00 ± 0%  -91.67% (p=0.000 n=10)
Hoglet_Do_SlidingWindow-24   192.00 ± 0%   16.00 ± 0%  -91.67% (p=0.000 n=10)
geomean                       192.0        16.00       -91.67%

                           │  old.txt   │              new.txt               │
                           │ allocs/op  │ allocs/op   vs base                │
Hoglet_Do_EWMA-24            5.000 ± 0%   1.000 ± 0%  -80.00% (p=0.000 n=10)
Hoglet_Do_SlidingWindow-24   5.000 ± 0%   1.000 ± 0%  -80.00% (p=0.000 n=10)
geomean                      5.000        1.000       -80.00%

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

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>
@costela costela marked this pull request as ready for review June 26, 2026 19:02
@costela costela requested a review from Copilot June 26, 2026 19:02

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 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.WithCancelCause allocation behind if ctx.Done() != nil.
  • Move the dedupObservableCall wrapper 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.

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.

2 participants