Conversation
resolve.Context exposes SubgraphHeadersBuilder and ExecutionOptions, but
ExecutionEngine builds its resolve.Context internally and the functional
option type takes an unexported struct, so neither field can be reached
from outside the engine package.
That matters for correctness, not just convenience. The resolver folds the
hash returned by SubgraphHeadersBuilder into its subgraph request
deduplication key. Without a builder the hash is zero, so the key covers
only the data source ID and the rendered request body. A caller that
forwards per-client headers to subgraphs by another route - typically an
http.RoundTripper installed on the data source HTTP client - contributes
nothing to the key. Concurrent operations whose subgraph request bodies
are byte-identical, which is every operation without arguments, then
collapse into one fetch and all callers receive the response resolved for
one arbitrary client.
Add two options so the fields become reachable:
- WithSubgraphHeadersBuilder makes header forwarding go through the
engine, which puts it in the deduplication key while keeping
deduplication effective for callers that do share headers.
- WithExecutionOptions sets resolve.ExecutionOptions, which lets callers
turn deduplication off outright.
Both follow the shape of the existing WithAuthorizer and
WithPreFetchFieldAuthorizer options. No behaviour changes unless an option
is passed.
DisableSubgraphRequestDeduplication was documented as covering requests "within a single operation execution". A Resolver holds one SubgraphRequestSingleFlight for its whole lifetime and hands it to every resolve call, so deduplication spans concurrent operations from different clients. The distinction is easy to get wrong and the consequence is a cross-client data leak, so spell out the real scope, what the key covers, and that per-client headers have to reach subgraphs through SubgraphHeadersBuilder to be part of it.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughThe execution engine now accepts subgraph header builders and resolver execution options. Tests cover concurrent header propagation, request deduplication, and disabled deduplication. Resolver documentation defines the deduplication scope and key. ChangesExecution options and request deduplication
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to This additive change exposes execution options for subgraph headers and request deduplication without introducing an actionable merge-blocking risk; it is merge-ready after normal checks and review. Sequence Diagram(s)sequenceDiagram
participant Client
participant ExecutionEngine
participant ResolveContext
participant Resolver
participant Subgraph
Client->>ExecutionEngine: Execute operation with execution options
ExecutionEngine->>ResolveContext: configure headers and resolver options
ResolveContext->>Resolver: resolve operation
Resolver->>Subgraph: send rendered request with generated headers
Subgraph-->>Resolver: return response
Resolver-->>ExecutionEngine: return GraphQL result
ExecutionEngine-->>Client: return operation result
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Full details: Linked Issues checkExplanation The changes satisfy issue ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@execution/engine/execution_engine_deduplication_test.go`:
- Around line 141-168: Update the concurrent worker setup around the goroutine
in the deduplication test to add a per-worker readiness signal after creating
its request and result writer. Wait for all workers to report readiness before
closing start, ensuring every worker is blocked on the start receive before
execution begins; preserve the existing WaitGroup and result assertions.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 40c8dbf9-dfe5-44d0-b22e-7b090b7b8b24
📒 Files selected for processing (3)
execution/engine/execution_engine.goexecution/engine/execution_engine_deduplication_test.gov2/pkg/engine/resolve/context.go
executeConcurrently closed the start channel as soon as the loop that spawns the workers finished, so a worker that had not reached the receive yet would begin late. With a 50ms subgraph delay the margin is wide, but on a loaded runner a straggler could enter Execute after the leader's fetch had already completed, and the shared-header case would then be measuring scheduling rather than deduplication. Have each worker report once its setup is done and release the burst only after every report has arrived.
|
woo strange emoji in your PR title |
|
Fair — dropped it. I took it from the examples in the PR template ( While you are here: is there anything needed from my side to get CI running? No checks have been triggered on the branch, which I assume is the usual approval gate for a first-time contributor. Happy to rebase onto master too — the branch is a few days behind now. |
|
Sorry I am not a maintainer |
|
@devsergiy @pepol Help please |
golangci-lint's modernize analyzer flags the Add(1) / defer Done() pair now that Go 1.25 provides WaitGroup.Go, which does both itself.
Summary by CodeRabbit
New Features
Documentation
Fixes #1616.
What
Adds two execution options to
execution/engine:and corrects the documented scope of
resolve.ExecutionOptions.DisableSubgraphRequestDeduplication.Why
resolve.Contextalready carriesSubgraphHeadersBuilderandExecutionOptions,but
ExecutionEnginebuilds itsresolve.Contextinternally andtype ExecutionOptions func(ctx *internalExecutionContext)takes an unexportedstruct, so no function of that type can be declared outside the package. Both
fields are therefore unreachable for embedders.
That is a correctness gap rather than a missing convenience. The subgraph
deduplication key is
hash(DataSourceID + renderedInput + SubgraphHeadersBuilder hash). With nobuilder the hash is
0, so per-client headers forwarded by any other route — anhttp.RoundTripperon the data source client is the only optionexecution/engineleaves — are not part of the key. Concurrent operations withbyte-identical subgraph request bodies collapse into one fetch, and the followers
copy the leader's response without ever issuing a request of their own. Every
argument-free operation has an identical body across clients, so concurrent
clients receive each other's data.
The issue has a self-contained reproducer. On an idle machine, eight concurrent
clients produce one upstream fetch and seven responses carrying another client's
data.
With
WithSubgraphHeadersBuilderthe headers travel through the engine, becomepart of the key, and deduplication keeps working for callers that genuinely do
share headers — which is why this is preferable to just switching the feature
off.
WithExecutionOptionsis there for embedders who would rather disablededuplication outright.
Tests
New file
execution/engine/execution_engine_deduplication_test.go:TestWithSubgraphHeadersBuilder/sets the builder on the resolve contextTestWithSubgraphHeadersBuilder/concurrent identical operations resolve against their own headers—8 concurrent executions, 8 distinct header sets, each response matches its own
headers and the subgraph is hit exactly 8 times
TestWithSubgraphHeadersBuilder/identical headers are still deduplicated—8 concurrent executions sharing one header set collapse into fewer fetches, so
the option does not disable single flight
TestWithExecutionOptions/sets the execution options on the resolve contextTestWithExecutionOptions/disabling subgraph deduplication gives every execution its own fetchThe subgraph in these tests echoes the
Authorizationheader it received, so aresponse identifies which caller's headers the fetch was made with, and a fixed
delay keeps the single flight window open for the whole burst.
SubgraphHeadersBuilderhad no test coverage before this change.Verified locally:
go test ./engine/passes, andgo test -race ./engine/ -run 'TestWithSubgraphHeadersBuilder|TestWithExecutionOptions' -count=3passes with no races.
Compatibility
Additive. Nothing changes for callers that do not pass the new options; both
default to today's behaviour.
Note for implementers
A
SubgraphHeadersBuildermust return a copy of its header map fromHeadersForSubgraph.httpclient.makeHTTPRequestassigns the returned valuestraight into
http.Request.Headerand then addsAcceptandContent-Typetoit, so a shared map races across concurrent fetches. This is documented on the
new option.
Checklist
The first box is intentionally unchecked: I opened #1616 at the same time as this
PR rather than waiting for approval first, because the change is small and easier
to judge with the code in front of you. Happy to close this and continue in the
issue if you would prefer to settle the approach there.
Open Source AI Manifesto
I have read and verified every line of this change, and the reproducer and tests
back it up.