Skip to content

perf: cache interceptor chain in DoHTTPtoGRPC#22

Merged
ankurs merged 2 commits intomainfrom
perf/cache-http-to-grpc-interceptor
Mar 25, 2026
Merged

perf: cache interceptor chain in DoHTTPtoGRPC#22
ankurs merged 2 commits intomainfrom
perf/cache-http-to-grpc-interceptor

Conversation

@ankurs
Copy link
Copy Markdown
Member

@ankurs ankurs commented Mar 25, 2026

Summary

  • Cache the chained interceptor in DoHTTPtoGRPC via sync.Once instead of rebuilding it on every call
  • The interceptor chain is deterministic after init — no need to reconstruct per request

Test plan

  • make build passes
  • make test passes (go test -race ./...)
  • make lint passes (golangci-lint 0 issues + govulncheck 0 vulnerabilities)

Summary by CodeRabbit

  • Refactor
    • Improved gRPC gateway initialization to avoid rebuilding interceptor chains on each request, reducing runtime overhead and improving responsiveness for HTTP→gRPC traffic.
  • Documentation
    • Updated README anchors and examples to reflect the new initialization behavior and current usage guidance.

DoHTTPtoGRPC was rebuilding the entire interceptor chain on every call
via ChainUnaryServer(DefaultInterceptors()...). Since the chain is
deterministic after init, cache it with sync.Once to avoid per-request
slice allocations and closure creation.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

Introduces a package-level, lazily initialized cached gRPC unary interceptor (using sync.Once) and updates DoHTTPtoGRPC to use the cached accessor. README anchors and an example snippet for DoHTTPtoGRPC were updated/removed.

Changes

Cohort / File(s) Summary
Interceptor caching and runtime change
interceptors.go
Added package-level sync.Once and cached grpc.UnaryServerInterceptor; introduced getHTTPtoGRPCInterceptor accessor; changed DoHTTPtoGRPC to fetch the cached interceptor instead of rebuilding the chain on each call.
Documentation updates
README.md
Updated GitHub source line anchors for multiple exported functions and FilterFunc (incremented line numbers); removed an example snippet demonstrating DoHTTPtoGRPC usage with grpc-gateway handler registration.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰
I hopped in code with eager cheer,
Cached once, now calls are clear and near.
No more rebuilding every hop,
One interceptor — and we stop.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'perf: cache interceptor chain in DoHTTPtoGRPC' directly and clearly describes the main optimization change: caching the interceptor chain to avoid reconstructing it on every call.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/cache-http-to-grpc-interceptor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 DoHTTPtoGRPC path by avoiding per-call reconstruction of the gRPC unary interceptor chain, under the assumption that interceptor configuration is fixed after initialization.

Changes:

  • Add a sync.Once-backed cache for the chained unary server interceptor used by DoHTTPtoGRPC
  • Introduce getHTTPtoGRPCInterceptor() to lazily initialize and reuse the chain
  • Update DoHTTPtoGRPC to use the cached interceptor chain

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

Comment thread interceptors.go
Document that interceptor configuration must be finalized before the
first DoHTTPtoGRPC call, consistent with the init-only contract on
all other configuration functions in this package.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
interceptors.go (1)

135-145: Consider fail-fast enforcement after cache freeze.

Once the chain is initialized, later calls to config mutators (AddUnaryServerInterceptor, SetFilterFunc, etc.) are silently ignored for this path. A guard would make misconfiguration obvious at runtime.

Proposed refactor (fail-fast freeze guard)
 import (
 	"context"
 	stdError "errors"
 	"fmt"
 	"net/http"
 	"runtime/debug"
 	"strings"
 	"sync"
+	"sync/atomic"
 	"time"
@@
 var (
 	httpToGRPCOnce        sync.Once
 	httpToGRPCInterceptor grpc.UnaryServerInterceptor
+	httpToGRPCFrozen      atomic.Bool
 )
 
 func getHTTPtoGRPCInterceptor() grpc.UnaryServerInterceptor {
 	httpToGRPCOnce.Do(func() {
+		httpToGRPCFrozen.Store(true)
 		httpToGRPCInterceptor = grpc_middleware.ChainUnaryServer(DefaultInterceptors()...)
 	})
 	return httpToGRPCInterceptor
 }
+
+func assertHTTPtoGRPCMutable(caller string) {
+	if httpToGRPCFrozen.Load() {
+		panic(caller + " called after first DoHTTPtoGRPC invocation")
+	}
+}

Then call assertHTTPtoGRPCMutable(...) at the top of mutators (SetFilterFunc, AddUnaryServerInterceptor, etc.).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interceptors.go` around lines 135 - 145, The HTTP->gRPC interceptor chain is
lazily initialized via httpToGRPCOnce in getHTTPtoGRPCInterceptor, so subsequent
mutators like AddUnaryServerInterceptor and SetFilterFunc silently no-op; add a
fail-fast guard by introducing a freeze flag (e.g., an atomic.Bool
httpToGRPCFrozen) and a helper assertHTTPtoGRPCMutable() that checks the flag
and panics/logs a clear error if frozen, set httpToGRPCFrozen = true inside the
httpToGRPCOnce.Do closure (in getHTTPtoGRPCInterceptor) after initializing
httpToGRPCInterceptor, and call assertHTTPtoGRPCMutable() at the start of
mutators (AddUnaryServerInterceptor, SetFilterFunc, etc.) to make
misconfiguration obvious at runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@interceptors.go`:
- Around line 135-145: The HTTP->gRPC interceptor chain is lazily initialized
via httpToGRPCOnce in getHTTPtoGRPCInterceptor, so subsequent mutators like
AddUnaryServerInterceptor and SetFilterFunc silently no-op; add a fail-fast
guard by introducing a freeze flag (e.g., an atomic.Bool httpToGRPCFrozen) and a
helper assertHTTPtoGRPCMutable() that checks the flag and panics/logs a clear
error if frozen, set httpToGRPCFrozen = true inside the httpToGRPCOnce.Do
closure (in getHTTPtoGRPCInterceptor) after initializing httpToGRPCInterceptor,
and call assertHTTPtoGRPCMutable() at the start of mutators
(AddUnaryServerInterceptor, SetFilterFunc, etc.) to make misconfiguration
obvious at runtime.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92e8ef23-a56f-47c4-ac09-f5de60faac1d

📥 Commits

Reviewing files that changed from the base of the PR and between e4e2942 and 1d8b9d5.

📒 Files selected for processing (2)
  • README.md
  • interceptors.go
✅ Files skipped from review due to trivial changes (1)
  • README.md

@ankurs ankurs merged commit 610274d into main Mar 25, 2026
8 checks passed
@ankurs ankurs deleted the perf/cache-http-to-grpc-interceptor branch March 25, 2026 05:08
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