Skip to content

Conversation

@jensneuse
Copy link
Member

@jensneuse jensneuse commented Oct 25, 2025

  • memory management improved through arenas
  • moved request deduplication from transport layer to engine
  • UniqueRequestID is no longer needed, we pre-compute a hash for the headers outside of the Engine and can supply it via HeadersBuilder interface through the RequestContext to the engine

Summary by CodeRabbit

  • Performance Improvements

    • Added arena-based memory pooling and sharded single-flight deduplication for faster, more memory-efficient request handling.
  • Subscription Enhancements

    • HTTP headers now propagate through subscription lifecycle.
    • Subscription triggers include SourceName and SourceID metadata.
  • API Refinements

    • Data-source and HTTP client calls now return full response payloads as bytes and accept headers.
    • Removed hash-based request identification in favor of header-aware handling.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 25, 2025

Walkthrough

APIs and tests were refactored to pass HTTP headers and return response bytes instead of writing to buffers; arena-based allocation was centralized and pooled and threaded through resolver/loader; sharded single‑flight deduplication for inbound/subgraph requests was added; xxhash UniqueRequestID flows removed; subscription triggers now include SourceName/SourceID. Tests and mocks updated accordingly.

Changes

Cohort / File(s) Summary
Dependency updates
v2/go.mod
Replace pseudo-version of github.com/wundergraph/astjson with v1.0.0; add github.com/wundergraph/go-arena v1.0.0.
Datasource signatures (bulk)
v2/pkg/engine/datasource/...
v2/pkg/engine/datasource/.../graphql_datasource/*.go, .../grpc_datasource/*.go, .../introspection_datasource/*.go, .../staticdatasource/*.go, .../pubsub_datasource/*.go, .../httpclient/*
Change Load/LoadWithFiles to accept headers http.Header and return ([]byte, error) (instead of writing to buffers); propagate headers through HTTP/multipart code; remove xxhash UniqueRequestID hashing logic from subscription flows.
gRPC JSON builder
v2/pkg/engine/datasource/grpc_datasource/json_builder.go
Move arena into jsonBuilder.jsonArena; remove arena parameters from helper signatures and use instance arena for all astjson value construction.
HTTP client implementation & tests
v2/pkg/engine/datasource/httpclient/nethttpclient.go, .../httpclient_test.go
Add Content-Length header constant and size-hint context API; makeHTTPRequest, Do, DoMultipartForm now accept base headers and return []byte; remove writer-based outputs, body-hash logic and switch to context-buffer preallocation.
Resolve DataSource interface & mocks/tests
v2/pkg/engine/resolve/datasource.go, v2/pkg/engine/resolve/resolve_mock_test.go, many v2/pkg/engine/resolve/*_test.go
Update DataSource and subscription interfaces to accept headers and return []byte; remove UniqueRequestID; update generated mocks and tests to new signatures/return semantics.
Arena pooling
v2/pkg/engine/resolve/arena.go, v2/pkg/engine/resolve/arena_test.go
Add ArenaPool and ArenaPoolItem with per-id sizing heuristics, weak-ref pooling, Acquire/Release and comprehensive unit tests.
Single‑flight deduplication
v2/pkg/engine/resolve/inbound_request_singleflight.go, v2/pkg/engine/resolve/subgraph_request_singleflight.go
Add sharded InboundRequestSingleFlight and SubgraphRequestSingleFlight with Get/Finish semantics, sharding, size hints and lifecycle handling.
Resolver & loader arena integration
v2/pkg/engine/resolve/resolve.go, v2/pkg/engine/resolve/loader.go, v2/pkg/engine/resolve/resolvable.go, .../resolvable_*_test.go
Thread arena pools and single‑flight into resolver/loader flows; change NewResolvable signature to accept an arena/context; add ArenaResolveGraphQLResponse and pervasive arena-aware rendering/load paths; update tests.
Context / request metadata
v2/pkg/engine/resolve/context.go, v2/pkg/engine/resolve/response.go
Add VariablesHash, SubgraphHeadersBuilder and HeadersForSubgraphRequest; add deduplication flags; change Request.ID to uint64; add GraphQLSubscriptionTrigger.SourceName and SourceID; add GraphQLResponse.SingleFlightAllowed().
Template rendering abstraction
v2/pkg/engine/resolve/inputtemplate.go
Introduce InputTemplateWriter interface and replace *bytes.Buffer usages with it; add explicit write-error checks.
Fetch model simplification
v2/pkg/engine/resolve/fetch.go, v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go, v2/pkg/engine/resolve/fetchtree.go
Remove ParallelListItemFetch, FetchKindParallelListItem, RequiresParallelListItemFetch and related creation/trace/queryPlan branches.
Fast JSON utilities
v2/pkg/fastjsonext/fastjsonext.go, .../fastjsonext_test.go
Migrate APIs to accept generic arena.Arena; update value creation and Set calls to use arena parameter; adjust tests to pass nil/arena accordingly.
Variables parsing change
v2/pkg/variablesvalidation/variablesvalidation.go, v2/pkg/astnormalization/uploads/upload_finder.go, v2/pkg/engine/resolve/tainted_objects_test.go
Replace ParseBytesWithoutCache calls with ParseBytes.
Tests & fixtures
many v2/pkg/engine/.../*_test.go, v2/pkg/engine/datasource/introspection_datasource/fixtures/*.golden
Update tests and mocks for new signatures/returns, adapt fixtures to data-wrapped {"data":...} responses, and adjust EOF newlines.
Constants & small visitor changes
v2/pkg/engine/resolve/const.go, v2/pkg/astvisitor/visitor.go
Add internal byte-slice constants pipe and dot; add an arena field to Walker and use arena-backed slices in selection handling.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Resolver
    participant InboundSF as InboundSingleFlight
    participant SubgraphSF as SubgraphSingleFlight
    participant ArenaPool
    participant DataSource

    Client->>Resolver: send GraphQL request
    Resolver->>InboundSF: GetOrCreate(ctx, responseKey)
    alt leader
        InboundSF-->>Resolver: leader token
        Resolver->>ArenaPool: Acquire(triggerID)
        ArenaPool-->>Resolver: Arena
        Resolver->>SubgraphSF: GetOrCreateItem(fetchItem, input, extraKey)
        alt new subgraph item
            SubgraphSF-->>Resolver: leader Item
            Resolver->>DataSource: Load(ctx, headers, input)
            DataSource-->>Resolver: (data, err)
            Resolver->>SubgraphSF: Finish(sfKey, fetchKey, item)
        else shared/wait
            SubgraphSF-->>Resolver: wait for loaded
        end
        Resolver->>InboundSF: FinishOk(inflightReq, data)
        Resolver->>ArenaPool: Release(triggerID, Arena)
    else follower
        InboundSF-->>Resolver: existing inflight -> wait Done
    end
    Resolver-->>Client: return merged GraphQL response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • v2/pkg/engine/resolve/arena.go — weak-pointer pool correctness, concurrency and GC interactions.
  • v2/pkg/engine/resolve/subgraph_request_singleflight.go and inbound_request_singleflight.go — sharding, locking, key computation and lifecycle correctness.
  • v2/pkg/engine/resolve/loader.go and resolvable.go — pervasive arena threading, MergeValuesWithPath and error-path correctness.
  • System-wide API/signature shifts — ensure all call sites, generated mocks and tests consistently pass headers and consume returned []byte.
  • v2/pkg/engine/datasource/httpclient/nethttpclient.go — header propagation, Content-Length handling, multipart path and context-based buffer sizing.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "feat: improve memory management & request deduplication" directly aligns with the two primary themes of the changeset. The changes extensively introduce arena-based memory management throughout the codebase, including new ArenaPool types, arena-aware methods in various datasources, and optimizations in JSON building and response handling. Additionally, the PR implements request deduplication through new InboundRequestSingleFlight and SubgraphRequestSingleFlight types, while removing UniqueRequestID methods that relied on xxhash. The title is concise, specific, and clearly conveys the main improvements without unnecessary noise or vagueness.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/improve-memory-usage-with-arenas

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa789e0 and 789f125.

📒 Files selected for processing (4)
  • v2/pkg/astvisitor/visitor.go (5 hunks)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go (14 hunks)
  • v2/pkg/engine/plan/visitor.go (1 hunks)
  • v2/pkg/variablesvalidation/variablesvalidation.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • v2/pkg/variablesvalidation/variablesvalidation.go
  • v2/pkg/astvisitor/visitor.go
  • v2/pkg/engine/plan/visitor.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-19T14:50:19.528Z
Learnt from: endigma
PR: wundergraph/graphql-go-tools#1298
File: v2/pkg/engine/resolve/resolve.go:999-1011
Timestamp: 2025-09-19T14:50:19.528Z
Learning: In the graphql-go-tools resolver, when handling backpressure for async unsubscribe operations, the team prefers using individual goroutines over bounded deferrer implementations. The rationale is that bounded implementations can also overflow, and the preferred solution for overload is scaling the deployment rather than internal buffering. The current approach prevents deadlocks at the cost of goroutine creation, which is an acceptable tradeoff.

Applied to files:

  • v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go
🧬 Code graph analysis (1)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go (3)
v2/pkg/engine/resolve/context.go (1)
  • NewContext (190-197)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go (1)
  • SubscriptionSource (1953-1955)
v2/pkg/engine/datasource/httpclient/file.go (2)
  • FileUpload (3-7)
  • NewFileUpload (9-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / windows-latest)

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

Copy link
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.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
v2/pkg/engine/resolve/inputtemplate.go (3)

161-161: Fix unchecked error to resolve pipeline failure.

The errcheck linter flags this line because WriteString can return an error. With the interface abstraction, implementations might fail, unlike *bytes.Buffer which never returns errors.

Apply this diff to handle the error:

-	preparedInput.WriteString(value[0])
-	return nil
+	_, err := preparedInput.WriteString(value[0])
+	return err

168-168: Handle WriteString error for consistency.

Similar to line 161, this WriteString call ignores potential errors. Although not flagged by the pipeline (possibly because the linter stopped at the first error), it should be handled consistently.

Apply this diff to handle the error:

-		preparedInput.WriteString(value[j])
+		if _, err := preparedInput.WriteString(value[j]); err != nil {
+			return err
+		}

39-51: Update caller type compatibility in loader.go

The signature change to InputTemplateWriter introduces type mismatches at both call sites. The callers in loader.go still initialize preparedInput as *bytes.Buffer and pass it to SetInputUndefinedVariables, which now requires InputTemplateWriter:

  • Line 1340 & 1386: preparedInput := bytes.NewBuffer(nil) passed to function expecting InputTemplateWriter
  • Line 1428 & 1500: preparedInput := bytes.NewBuffer(make([]byte, 0, 64)) passed to function expecting InputTemplateWriter

Either revert the signature change to *bytes.Buffer or update the callers in loader.go to use InputTemplateWriter.

v2/pkg/engine/resolve/loader_test.go (1)

1029-1049: Fix benchmark assertion to match new response format.

The benchmark expects a response with an "errors" field, but the new response format no longer includes an empty errors array by default. The pipeline failure indicates this assertion is failing.

Update the expected output to match the actual response format:

-	expected := `{"errors":[],"data":{"topProducts":[{"name":"Table","__typename":"Product","upc":"1","reviews":[{"body":"Love Table!","author":{"__typename":"User","id":"1","name":"user-1"}},{"body":"Prefer other Table.","author":{"__typename":"User","id":"2","name":"user-2"}}],"stock":8},{"name":"Couch","__typename":"Product","upc":"2","reviews":[{"body":"Couch Too expensive.","author":{"__typename":"User","id":"1","name":"user-1"}}],"stock":2},{"name":"Chair","__typename":"Product","upc":"3","reviews":[{"body":"Chair Could be better.","author":{"__typename":"User","id":"2","name":"user-2"}}],"stock":5}]}}`
+	expected := `{"data":{"topProducts":[{"name":"Table","__typename":"Product","upc":"1","reviews":[{"body":"Love Table!","author":{"__typename":"User","id":"1","name":"user-1"}},{"body":"Prefer other Table.","author":{"__typename":"User","id":"2","name":"user-2"}}],"stock":8},{"name":"Couch","__typename":"Product","upc":"2","reviews":[{"body":"Couch Too expensive.","author":{"__typename":"User","id":"1","name":"user-1"}}],"stock":2},{"name":"Chair","__typename":"Product","upc":"3","reviews":[{"body":"Chair Could be better.","author":{"__typename":"User","id":"2","name":"user-2"}}],"stock":5}]}}`
v2/pkg/engine/resolve/resolvable.go (1)

109-121: Non-nil arena precondition across parse/merge/error paths

These sites assume r.astjsonArena is non-nil (ParseBytesWithArena, MergeValues, SetArrayItem, ArrayValue, AppendError*). This is fine if callers set the arena first; otherwise it’s crash-prone. The fix is in resolve.go (ensure arena is acquired and injected before Init), see my comment there.

Also applies to: 470-479, 862-863, 1198-1212, 1286-1295, 1298-1312

v2/pkg/engine/datasource/grpc_datasource/json_builder.go (1)

252-257: Avoid mutating message.Fields via slice aliasing.

validFields := message.Fields followed by append(...) can mutate the underlying slice, causing duplicated fields across calls.

Apply:

- validFields := message.Fields
+ validFields := append(RPCFields(nil), message.Fields...)
 if message.IsOneOf() {
-  validFields = append(validFields, message.FieldSelectionSet.SelectFieldsForTypes(
+  validFields = append(validFields, message.FieldSelectionSet.SelectFieldsForTypes(
      message.SelectValidTypes(string(data.Type().Descriptor().Name())))...)
 }

Replace RPCFields with the actual slice type alias (e.g., []RPCField) if different.

🧹 Nitpick comments (32)
v2/pkg/engine/resolve/inputtemplate.go (1)

47-47: Consider handling Write errors consistently.

These Write calls ignore potential errors. While not flagged by the pipeline, handling errors consistently would improve robustness, especially since the interface abstraction now allows for implementations that might fail.

Consider handling errors from all Write calls similar to the WriteString fixes. For example, at line 84:

-		_, _ = preparedInput.Write(segment.Data)
+		if _, err = preparedInput.Write(segment.Data); err != nil {
+			return err
+		}

Apply similar patterns to lines 47, 106, 123, 144, and 166 if error handling is desired.

Also applies to: 84-84, 106-106, 123-123, 144-144, 166-166

v2/pkg/engine/resolve/context.go (2)

39-48: Clarify SubgraphHeadersBuilder contract

Add a short doc that the returned uint64 is the dedup scope key (stable within a request, distinct across requests/users) and that headers must be treated as immutable.

-type SubgraphHeadersBuilder interface {
-    HeadersForSubgraph(subgraphName string) (http.Header, uint64)
-}
+// SubgraphHeadersBuilder provides per-subgraph request headers and a dedup scope key.
+// The returned header must be treated as immutable by callers.
+// The returned uint64 should uniquely scope deduplication (e.g., request ID),
+// so concurrent operations with different auth headers do not deduplicate together.
+type SubgraphHeadersBuilder interface {
+    HeadersForSubgraph(subgraphName string) (http.Header, uint64)
+}

Confirm all call sites of HeadersForSubgraphRequest use the returned uint64 as the SingleFlight extraKey.


211-224: Hygiene: reset Request.ID and SubgraphHeadersBuilder on Free()

Minor cleanup to avoid leaking state when Contexts are pooled/reused.

 func (c *Context) Free() {
   c.ctx = nil
   c.Variables = nil
   c.Files = nil
   c.Request.Header = nil
+  c.Request.ID = 0
   c.RenameTypeNames = nil
   c.RemapVariables = nil
   c.TracingOptions.DisableAll()
   c.Extensions = nil
   c.subgraphErrors = nil
   c.authorizer = nil
   c.LoaderHooks = nil
+  c.SubgraphHeadersBuilder = nil
 }
v2/pkg/engine/resolve/singleflight.go (3)

16-23: Remove unused cleanup channel

cleanup chan func() is never used; drop it to reduce noise.

 type SingleFlight struct {
     mu      *sync.RWMutex
     items   map[uint64]*SingleFlightItem
     sizes   map[uint64]*fetchSize
     xxPool  *sync.Pool
-    cleanup chan func()
 }
@@
 func NewSingleFlight() *SingleFlight {
     return &SingleFlight{
         items: make(map[uint64]*SingleFlightItem),
         sizes: make(map[uint64]*fetchSize),
         mu:    new(sync.RWMutex),
         xxPool: &sync.Pool{
             New: func() any {
                 return xxhash.New()
             },
         },
-        cleanup: make(chan func()),
     }
 }

Also applies to: 29-41


118-136: Size hint rollover: consider EMA for smoother adaptation (optional)

Current reset at 50 samples turns the moving average into ~50% weight on the last sample. An exponential moving average would be smoother under traffic variance.

If desired, replace the rollover with avg = alpha*avg + (1-alpha)*len(response) stored as fixed‑point to avoid floats.


75-83: Hash extraKey into the digest instead of adding it to reduce collision risk

Currently, sfKey is computed as h.Sum64() + extraKey, which can collide (e.g., 100+50 and 75+75 both yield 150). Since extraKey comes from request headers via HeadersForSubgraph, it should be mixed into the hash to ensure distinct headers produce distinct keys.

@@
-import (
-    "sync"
-
-    "github.com/cespare/xxhash/v2"
-)
+import (
+    "encoding/binary"
+    "sync"
+
+    "github.com/cespare/xxhash/v2"
+)
@@
-func (s *SingleFlight) sfKey(h *xxhash.Digest, fetchItem *FetchItem, input []byte, extraKey uint64) uint64 {
+func (s *SingleFlight) sfKey(h *xxhash.Digest, fetchItem *FetchItem, input []byte, extraKey uint64) uint64 {
     if fetchItem != nil && fetchItem.Fetch != nil {
         info := fetchItem.Fetch.FetchInfo()
         if info != nil {
             _, _ = h.WriteString(info.DataSourceID)
             _, _ = h.WriteString(":")
         }
     }
     _, _ = h.Write(input)
-    return h.Sum64() + extraKey
+    var ek [8]byte
+    binary.LittleEndian.PutUint64(ek[:], extraKey)
+    _, _ = h.Write(ek[:])
+    return h.Sum64()
 }

Also applies to: 85-96

v2/pkg/engine/datasource/introspection_datasource/source_test.go (1)

31-41: Return-based Load usage looks good; simplify newline trim.
The switch to returning responseData is correct. You can simplify the newline removal from json.Indent with TrimSuffix for clarity.

-// Trim the trailing newline that json.Indent adds
-responseBytes := actualResponse.Bytes()
-if len(responseBytes) > 0 && responseBytes[len(responseBytes)-1] == '\n' {
-  responseBytes = responseBytes[:len(responseBytes)-1]
-}
+// Trim the trailing newline that json.Indent adds
+responseBytes := bytes.TrimSuffix(actualResponse.Bytes(), []byte{'\n'})
v2/pkg/variablesvalidation/variablesvalidation.go (1)

101-101: Normalize empty/null variables to "{}" before parsing.
To match GraphQL semantics and mirror UploadFinder, guard nil/empty/"null" variables so ParseBytes never errors on absent variables.

 func (v *VariablesValidator) Validate(operation, definition *ast.Document, variables []byte) error {
   v.visitor.definition = definition
   v.visitor.operation = operation
-  v.visitor.variables, v.visitor.err = astjson.ParseBytes(variables)
+  // Normalize absent variables to {}
+  if variables == nil || bytes.Equal(variables, []byte("null")) || bytes.Equal(variables, []byte("")) {
+    variables = []byte("{}")
+  }
+  v.visitor.variables, v.visitor.err = astjson.ParseBytes(variables)
   if v.visitor.err != nil {
     return v.visitor.err
   }

Please confirm callers never pass nil/empty variables inadvertently. If they do, add/keep this guard to prevent regressions.

v2/pkg/engine/resolve/resolvable_test.go (1)

15-15: Constructor updates LGTM; consider a tiny test helper to DRY.
All call sites now pass the arena argument (nil) correctly. Optionally add a newTestResolvable(opts) helper to cut duplication.

func newTestResolvable(opts ResolvableOptions) *Resolvable { return NewResolvable(nil, opts) }

Also applies to: 87-87, 160-160, 234-234, 262-262, 337-337, 373-373, 443-443, 473-473, 504-504, 552-552, 626-626, 656-656, 690-690, 722-722, 758-758, 841-841, 926-926, 953-953, 976-976, 998-998, 1021-1021, 1049-1049, 1147-1147, 1245-1245, 1345-1345

v2/pkg/engine/resolve/variables_renderer.go (1)

350-356: Use sync.Pool.New to remove the nil-branch; ensure Reset clears arena.
Minor micro-optimization: initialize the pool with New to simplify getResolvable.

-var (
-  _graphQLVariableResolveRendererPool = &sync.Pool{}
-)
+var (
+  _graphQLVariableResolveRendererPool = &sync.Pool{
+    New: func() any { return NewResolvable(nil, ResolvableOptions{}) },
+  }
+)

 func (g *GraphQLVariableResolveRenderer) getResolvable() *Resolvable {
-  v := _graphQLVariableResolveRendererPool.Get()
-  if v == nil {
-    return NewResolvable(nil, ResolvableOptions{})
-  }
-  return v.(*Resolvable)
+  return _graphQLVariableResolveRendererPool.Get().(*Resolvable)
 }

Please confirm Resolvable.Reset releases any arena-backed state (if ever set on this path), so pooled instances don’t retain large allocations between uses.

v2/pkg/engine/datasource/staticdatasource/static_datasource.go (1)

78-80: Consider returning an error instead of panicking.

The panic for unimplemented functionality could cause runtime crashes if this method is accidentally called. Consider returning a descriptive error instead:

-func (Source) LoadWithFiles(ctx context.Context, headers http.Header, input []byte, files []*httpclient.FileUpload) (data []byte, err error) {
-	panic("not implemented")
-}
+func (Source) LoadWithFiles(ctx context.Context, headers http.Header, input []byte, files []*httpclient.FileUpload) (data []byte, err error) {
+	return nil, fmt.Errorf("static data source does not support file uploads")
+}

Note: You'll need to add "fmt" to the imports if you adopt this change.

v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go (1)

8455-8455: Updated Start signature: ensure nil-safe header handling

Calls now use Start(ctx, headers, options, updater) with headers=nil. Verify the implementation treats nil as empty and does not mutate the provided map. Consider adding one test that passes non-empty headers and asserts they reach the upstream.

Also applies to: 8461-8461, 8474-8474, 8486-8486, 8504-8504, 8527-8527, 8591-8591, 8611-8611, 8635-8635

v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go (2)

1957-1964: Avoid clobbering configured headers; prefer merge with precedence.

Overwriting options.Header loses statically configured subscription headers. Merge instead, letting passed headers override duplicates.

Apply:

 err := json.Unmarshal(input, &options)
 if err != nil {
   return err
 }
- options.Header = headers
+ // merge configured headers with passed headers; passed headers take precedence
+ if options.Header == nil {
+   options.Header = make(http.Header)
+ }
+ for k, v := range headers {
+   options.Header[k] = v
+ }

1977-1984: Same header-merge concern as AsyncStart.

Use the same merge strategy here to preserve configured headers.

- options.Header = headers
+ if options.Header == nil {
+   options.Header = make(http.Header)
+ }
+ for k, v := range headers {
+   options.Header[k] = v
+ }
v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go (1)

154-156: Do not panic; return a proper error for unsupported file uploads.

Panics in datasource paths can crash the process. Return an error or GraphQL error JSON.

-func (d *DataSource) LoadWithFiles(ctx context.Context, headers http.Header, input []byte, files []*httpclient.FileUpload) (data []byte, err error) {
-  panic("unimplemented")
-}
+func (d *DataSource) LoadWithFiles(ctx context.Context, headers http.Header, input []byte, files []*httpclient.FileUpload) (data []byte, err error) {
+  // gRPC transport does not support multipart uploads
+  return nil, fmt.Errorf("gRPC datasource: file uploads are not supported")
+}
v2/pkg/engine/datasource/grpc_datasource/grpc_datasource_test.go (1)

237-239: Remove leftover debug printing.

Avoid noisy stdout in tests.

- bytes := output
- fmt.Println(string(bytes))
+ bytes := output
v2/pkg/engine/datasource/pubsub_datasource/pubsub_kafka.go (2)

59-61: Avoid panic; return unsupported operation error.

Never panic in datasource code paths.

-func (s *KafkaPublishDataSource) LoadWithFiles(ctx context.Context, headers http.Header, input []byte, files []*httpclient.FileUpload) (data []byte, err error) {
-  panic("not implemented")
-}
+func (s *KafkaPublishDataSource) LoadWithFiles(ctx context.Context, headers http.Header, input []byte, files []*httpclient.FileUpload) (data []byte, err error) {
+  return nil, fmt.Errorf("kafka publish: file uploads are not supported")
+}

46-57: Choose one error strategy: return error or JSON status, not both.

Returning non-nil error and a JSON body can cause double-handling upstream. Prefer nil error with explicit JSON or return only error.

Option A (error-only):

- if err := s.pubSub.Publish(ctx, publishConfiguration); err != nil {
-   return []byte(`{"success": false}`), err
- }
- return []byte(`{"success": true}`), nil
+ if err := s.pubSub.Publish(ctx, publishConfiguration); err != nil {
+   return nil, err
+ }
+ return []byte(`{"success": true}`), nil

Option B (JSON-only, nil error):

- if err := s.pubSub.Publish(ctx, publishConfiguration); err != nil {
-   return []byte(`{"success": false}`), err
- }
+ if err := s.pubSub.Publish(ctx, publishConfiguration); err != nil {
+   return []byte(`{"success": false","error":"publish failed"}`), nil
+ }

Pick consistently across datasources.

Please confirm the engine’s expectation for datasource error handling so we align consistently.

v2/pkg/engine/resolve/resolvable.go (1)

81-100: Reset clears the arena; ensure it is set before next Init/Resolve

Reset sets r.astjsonArena = nil. Any subsequent Init/Resolve must set the arena first, or calls like astjson.ObjectValue/ParseBytesWithArena will likely panic or mis-allocate. Add a comment or a guard, and ensure all call sites set the arena before use.
Would you like me to add a SetArena(a arena.Arena) helper and guard Init when arena is nil?

v2/pkg/engine/resolve/resolve.go (3)

1089-1114: Set arena in subscription SkipLoader path for consistency

SkipLoader still constructs Resolvable with nil arena. It often works, but error/value-completion paths would allocate via arena. Acquire/assign an arena as in ResolveGraphQLResponse to avoid surprises.

Example:

- t := newTools(r.options, r.allowedErrorExtensionFields, r.allowedErrorFields, r.sf)
+ t := newTools(r.options, r.allowedErrorExtensionFields, r.allowedErrorFields, r.sf)
+ resolveArena := r.resolveArenaPool.Acquire(ctx.Request.ID)
+ t.loader.jsonArena = resolveArena.Arena
+ t.resolvable.astjsonArena = resolveArena.Arena
+ defer r.resolveArenaPool.Release(ctx.Request.ID, resolveArena)

1199-1224: Also set arena in Async subscription SkipLoader path

Same rationale as above; apply the same acquire/assign/release here.


1168-1174: Improve uniqueID mixing: write headersHash into hasher instead of adding

Adding 64-bit hashes can increase collisions. Prefer feeding headersHash bytes into xxhash and use Sum64().

Suggested change (add import "encoding/binary"):

+ import "encoding/binary"
@@
- _, _ = xxh.Write(input)
- // the hash for subgraph headers is pre-computed
- // we can just add it to the input hash to get a unique id
- uniqueID := xxh.Sum64() + headersHash
+ _, _ = xxh.Write(input)
+ var hb [8]byte
+ binary.LittleEndian.PutUint64(hb[:], headersHash)
+ _, _ = xxh.Write(hb[:])
+ uniqueID := xxh.Sum64()

Also applies to: 1226-1233

v2/pkg/engine/datasource/pubsub_datasource/pubsub_nats.go (2)

43-51: Headers currently unused

Start receives headers but the underlying NatsPubSub.Subscribe doesn’t. If headers are intentionally ignored for NATS, add a short comment; otherwise consider plumbing usage or dropping the param in this source.


79-93: LGTM: request path returns data slice and propagates error

Readability nit: rename subscriptionConfiguration -> requestConfiguration. Optional.

v2/pkg/engine/resolve/resolve_test.go (1)

112-136: Remove or use unused parameter enableSingleFlight.

The enableSingleFlight bool is unused in testFn; either wire it to options or remove it to avoid confusion.

v2/pkg/engine/datasource/httpclient/nethttpclient.go (4)

315-325: Confirm FileUpload lifetime; avoid deleting user files.

The defer removes every opened file (os.Remove). If FileUpload.Path() can point to non-temporary user files, this deletes user data.

Option: only delete when FileUpload is marked temporary, or move cleanup behind an explicit flag.

- if err = os.Remove(file.Name()); err != nil {
+ if fileShouldBeDeleted(file) { // e.g., a field or method on FileUpload
+   if err = os.Remove(file.Name()); err != nil {
       return
- }
+   }
+ }

Please confirm the contract of FileUpload.Path().


232-241: Close decompressor readers after use to free resources.

gzip/deflate readers implement io.ReadCloser. Close them after ReadFrom to release resources early.

 out := buffer(ctx)
-_, err = out.ReadFrom(respReader)
+_, err = out.ReadFrom(respReader)
 if err != nil {
   return nil, err
 }
+if rc, ok := respReader.(io.ReadCloser); ok {
+  _ = rc.Close()
+}

209-216: Prefer a single Accept-Encoding value.

Set “gzip, deflate” once instead of two separate headers; clearer and avoids duplicate values.

-request.Header.Set(AcceptEncodingHeader, EncodingGzip)
-request.Header.Add(AcceptEncodingHeader, EncodingDeflate)
+request.Header.Set(AcceptEncodingHeader, EncodingGzip+", "+EncodingDeflate)

238-265: Trace injection assumes JSON object; guard or fallback for non-objects.

jsonparser.Set will fail for non-object bodies (arrays, scalars). Consider detecting object first; otherwise return original data and put trace into ResponseContext only.

v2/pkg/engine/datasource/grpc_datasource/json_builder.go (2)

406-415: Clarify nesting bound check.

if level > md.NestingLevel likely intends >=; using >= avoids an unnecessary extra frame and is easier to reason about.

- if level > md.NestingLevel {
+ if level >= md.NestingLevel {
   return current, nil
 }

114-121: Consider exposing a Reset to reuse jsonBuilder arenas safely.

If jsonBuilder instances are reused, add a Reset method to re-init jsonArena (or allocate per call) to avoid unbounded growth.

v2/pkg/engine/resolve/loader.go (1)

432-446: Consider using arena for array creation.

The function now accepts an arena parameter and uses it for SetArrayItem, but the array itself is created with MustParseBytes (line 441) without the arena. For consistency and to fully leverage arena benefits, consider:

-	arr := astjson.MustParseBytes([]byte(`[]`))
+	arr := astjson.ArrayValue(a)
	for i, item := range items {
		arr.SetArrayItem(a, i, item)
	}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c4acc9 and 60b5c3b.

⛔ Files ignored due to path filters (2)
  • go.work.sum is excluded by !**/*.sum
  • v2/go.sum is excluded by !**/*.sum
📒 Files selected for processing (45)
  • v2/go.mod (1 hunks)
  • v2/pkg/astnormalization/uploads/upload_finder.go (1 hunks)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go (3 hunks)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go (14 hunks)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (0 hunks)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (0 hunks)
  • v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go (5 hunks)
  • v2/pkg/engine/datasource/grpc_datasource/grpc_datasource_test.go (25 hunks)
  • v2/pkg/engine/datasource/grpc_datasource/json_builder.go (15 hunks)
  • v2/pkg/engine/datasource/httpclient/httpclient_test.go (2 hunks)
  • v2/pkg/engine/datasource/httpclient/nethttpclient.go (9 hunks)
  • v2/pkg/engine/datasource/introspection_datasource/fixtures/schema_introspection.golden (1 hunks)
  • v2/pkg/engine/datasource/introspection_datasource/fixtures/schema_introspection_with_custom_root_operation_types.golden (1 hunks)
  • v2/pkg/engine/datasource/introspection_datasource/fixtures/type_introspection.golden (1 hunks)
  • v2/pkg/engine/datasource/introspection_datasource/source.go (3 hunks)
  • v2/pkg/engine/datasource/introspection_datasource/source_test.go (1 hunks)
  • v2/pkg/engine/datasource/pubsub_datasource/pubsub_datasource_test.go (4 hunks)
  • v2/pkg/engine/datasource/pubsub_datasource/pubsub_kafka.go (3 hunks)
  • v2/pkg/engine/datasource/pubsub_datasource/pubsub_nats.go (3 hunks)
  • v2/pkg/engine/datasource/staticdatasource/static_datasource.go (2 hunks)
  • v2/pkg/engine/plan/planner_test.go (2 hunks)
  • v2/pkg/engine/plan/visitor.go (1 hunks)
  • v2/pkg/engine/resolve/arena.go (1 hunks)
  • v2/pkg/engine/resolve/authorization_test.go (3 hunks)
  • v2/pkg/engine/resolve/context.go (2 hunks)
  • v2/pkg/engine/resolve/datasource.go (1 hunks)
  • v2/pkg/engine/resolve/event_loop_test.go (2 hunks)
  • v2/pkg/engine/resolve/inputtemplate.go (7 hunks)
  • v2/pkg/engine/resolve/loader.go (50 hunks)
  • v2/pkg/engine/resolve/loader_hooks_test.go (14 hunks)
  • v2/pkg/engine/resolve/loader_test.go (14 hunks)
  • v2/pkg/engine/resolve/resolvable.go (14 hunks)
  • v2/pkg/engine/resolve/resolvable_custom_field_renderer_test.go (2 hunks)
  • v2/pkg/engine/resolve/resolvable_test.go (26 hunks)
  • v2/pkg/engine/resolve/resolve.go (20 hunks)
  • v2/pkg/engine/resolve/resolve_federation_test.go (21 hunks)
  • v2/pkg/engine/resolve/resolve_mock_test.go (3 hunks)
  • v2/pkg/engine/resolve/resolve_test.go (29 hunks)
  • v2/pkg/engine/resolve/response.go (1 hunks)
  • v2/pkg/engine/resolve/singleflight.go (1 hunks)
  • v2/pkg/engine/resolve/tainted_objects_test.go (3 hunks)
  • v2/pkg/engine/resolve/variables_renderer.go (1 hunks)
  • v2/pkg/fastjsonext/fastjsonext.go (1 hunks)
  • v2/pkg/fastjsonext/fastjsonext_test.go (1 hunks)
  • v2/pkg/variablesvalidation/variablesvalidation.go (1 hunks)
💤 Files with no reviewable changes (2)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go
🧰 Additional context used
🧬 Code graph analysis (30)
v2/pkg/engine/resolve/variables_renderer.go (1)
v2/pkg/engine/resolve/resolvable.go (2)
  • NewResolvable (71-79)
  • ResolvableOptions (64-69)
v2/pkg/engine/resolve/context.go (1)
v2/pkg/engine/plan/planner.go (1)
  • IncludeQueryPlanInResponse (92-96)
v2/pkg/engine/resolve/resolvable_custom_field_renderer_test.go (1)
v2/pkg/engine/resolve/resolvable.go (2)
  • NewResolvable (71-79)
  • ResolvableOptions (64-69)
v2/pkg/engine/resolve/loader_test.go (1)
v2/pkg/engine/resolve/resolvable.go (2)
  • NewResolvable (71-79)
  • ResolvableOptions (64-69)
v2/pkg/engine/datasource/introspection_datasource/source.go (2)
v2/pkg/engine/datasource/staticdatasource/static_datasource.go (3)
  • Source (72-72)
  • Source (74-76)
  • Source (78-80)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
v2/pkg/engine/resolve/resolve_federation_test.go (2)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/resolve/resolve_mock_test.go (1)
  • NewMockDataSource (28-32)
v2/pkg/engine/resolve/response.go (4)
v2/pkg/engine/resolve/inputtemplate.go (1)
  • InputTemplate (31-37)
v2/pkg/engine/resolve/variables.go (1)
  • Variables (27-27)
v2/pkg/engine/resolve/datasource.go (1)
  • SubscriptionDataSource (15-19)
v2/pkg/engine/resolve/fetch.go (2)
  • PostProcessingConfiguration (116-132)
  • QueryPlan (251-254)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go (3)
v2/pkg/engine/datasource/staticdatasource/static_datasource.go (3)
  • Source (72-72)
  • Source (74-76)
  • Source (78-80)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (2)
  • DoMultipartForm (272-328)
  • Do (267-270)
v2/pkg/engine/resolve/resolve_mock_test.go (2)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
v2/pkg/engine/resolve/resolvable_test.go (1)
v2/pkg/engine/resolve/resolvable.go (2)
  • NewResolvable (71-79)
  • ResolvableOptions (64-69)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go (3)
v2/pkg/engine/resolve/context.go (1)
  • NewContext (168-175)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go (1)
  • SubscriptionSource (1953-1955)
v2/pkg/engine/datasource/httpclient/file.go (2)
  • FileUpload (3-7)
  • NewFileUpload (9-15)
v2/pkg/engine/resolve/resolvable.go (2)
v2/pkg/engine/resolve/resolve.go (1)
  • New (181-245)
v2/pkg/fastjsonext/fastjsonext.go (2)
  • AppendErrorToArray (8-15)
  • AppendErrorWithExtensionsCodeToArray (17-27)
v2/pkg/engine/plan/planner_test.go (2)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
v2/pkg/engine/resolve/resolve_test.go (10)
v2/pkg/engine/resolve/context.go (2)
  • Context (16-37)
  • ExecutionOptions (50-56)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)
  • Do (267-270)
v2/pkg/engine/resolve/resolve_mock_test.go (1)
  • NewMockDataSource (28-32)
v2/pkg/engine/resolve/response.go (2)
  • GraphQLResponse (35-43)
  • GraphQLResponseInfo (45-47)
v2/pkg/engine/resolve/fetchtree.go (6)
  • Single (59-67)
  • SingleWithPath (69-82)
  • Sequence (26-31)
  • Parallel (33-38)
  • ArrayPath (52-57)
  • ObjectPath (40-45)
v2/pkg/engine/resolve/fetch.go (4)
  • SingleFetch (91-99)
  • SingleFetch (153-155)
  • FetchConfiguration (270-302)
  • PostProcessingConfiguration (116-132)
v2/pkg/engine/resolve/datasource.go (1)
  • DataSource (10-13)
v2/pkg/engine/resolve/inputtemplate.go (5)
  • InputTemplate (31-37)
  • TemplateSegment (22-29)
  • SegmentType (15-15)
  • StaticSegmentType (18-18)
  • VariableSegmentType (19-19)
v2/pkg/engine/resolve/variables.go (4)
  • VariableKind (7-7)
  • ContextVariableKind (10-10)
  • Variables (27-27)
  • ResolvableObjectVariableKind (13-13)
v2/pkg/engine/resolve/subscription_filter.go (2)
  • SubscriptionFilter (16-21)
  • SubscriptionFieldFilter (23-26)
v2/pkg/engine/resolve/singleflight.go (2)
v2/pkg/engine/resolve/resolve.go (1)
  • New (181-245)
v2/pkg/engine/resolve/fetch.go (3)
  • FetchItem (29-34)
  • Fetch (20-27)
  • FetchInfo (376-397)
v2/pkg/engine/datasource/pubsub_datasource/pubsub_kafka.go (3)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/resolve/resolve.go (1)
  • SubscriptionUpdater (1383-1390)
v2/pkg/engine/datasource/pubsub_datasource/kafka_event_manager.go (1)
  • KafkaPublishEventConfiguration (17-21)
v2/pkg/engine/resolve/loader.go (5)
v2/pkg/engine/resolve/singleflight.go (1)
  • SingleFlight (16-22)
v2/pkg/engine/resolve/fetch.go (5)
  • FetchItemPathElement (78-82)
  • FetchItem (29-34)
  • DataSourceLoadTrace (405-419)
  • Fetch (20-27)
  • FetchInfo (376-397)
v2/pkg/engine/resolve/inputtemplate.go (1)
  • SetInputUndefinedVariables (39-51)
v2/pkg/pool/hash64.go (1)
  • Hash64 (10-16)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)
  • WithHTTPClientSizeHint (139-141)
v2/pkg/engine/resolve/inputtemplate.go (1)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/resolve/datasource.go (3)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
v2/pkg/engine/resolve/resolve.go (1)
  • SubscriptionUpdater (1383-1390)
v2/pkg/engine/resolve/event_loop_test.go (2)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/resolve/resolve.go (1)
  • SubscriptionUpdater (1383-1390)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (2)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
v2/pkg/engine/datasource/staticdatasource/static_datasource.go (4)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go (1)
  • Source (1829-1831)
v2/pkg/engine/datasource/introspection_datasource/source.go (1)
  • Source (18-20)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
v2/pkg/fastjsonext/fastjsonext_test.go (1)
v2/pkg/fastjsonext/fastjsonext.go (3)
  • AppendErrorToArray (8-15)
  • PathElement (29-32)
  • CreateErrorObjectWithPath (34-50)
v2/pkg/engine/resolve/resolve.go (6)
v2/pkg/engine/resolve/arena.go (2)
  • ArenaPool (13-17)
  • NewArenaPool (30-34)
v2/pkg/engine/resolve/singleflight.go (2)
  • SingleFlight (16-22)
  • NewSingleFlight (29-41)
v2/pkg/engine/resolve/resolvable.go (2)
  • NewResolvable (71-79)
  • ResolvableOptions (64-69)
v2/pkg/engine/resolve/context.go (4)
  • Context (16-37)
  • Request (163-166)
  • ExecutionOptions (50-56)
  • SubgraphHeadersBuilder (39-41)
v2/pkg/engine/resolve/response.go (3)
  • GraphQLResponse (35-43)
  • GraphQLSubscription (12-16)
  • SubscriptionResponseWriter (68-74)
v2/pkg/pool/hash64.go (1)
  • Hash64 (10-16)
v2/pkg/engine/datasource/grpc_datasource/json_builder.go (1)
v2/pkg/engine/datasource/grpc_datasource/execution_plan.go (3)
  • RPCMessage (71-85)
  • ListMetadata (190-195)
  • LevelInfo (198-201)
v2/pkg/engine/resolve/loader_hooks_test.go (1)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/datasource/pubsub_datasource/pubsub_nats.go (4)
v2/pkg/engine/resolve/context.go (2)
  • Context (16-37)
  • Request (163-166)
v2/pkg/engine/resolve/resolve.go (1)
  • SubscriptionUpdater (1383-1390)
v2/pkg/engine/datasource/pubsub_datasource/nats_event_manager.go (1)
  • NatsPublishAndRequestEventConfiguration (31-35)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
v2/pkg/engine/resolve/authorization_test.go (2)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/resolve/resolve_mock_test.go (1)
  • NewMockDataSource (28-32)
v2/pkg/engine/datasource/httpclient/httpclient_test.go (1)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)
  • Do (267-270)
v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go (2)
v2/pkg/engine/resolve/datasource.go (1)
  • DataSource (10-13)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
🪛 GitHub Actions: v2-ci
v2/pkg/engine/resolve/loader_test.go

[error] 1046-1046: BenchmarkLoader_LoadGraphQLResponseData failed: expected a response containing an errors field, but got a response without 'errors' field. Deep comparison mismatch detected in test.

v2/pkg/engine/resolve/inputtemplate.go

[error] 161-161: golangci-lint: Error return value of preparedInput.WriteString is not checked (errcheck).

🪛 GitHub Check: Linters (1.25)
v2/pkg/engine/resolve/resolvable.go

[failure] 14-14:
File is not properly formatted (gci)

v2/pkg/engine/resolve/loader.go

[failure] 20-20:
File is not properly formatted (gci)

v2/pkg/engine/resolve/resolve.go

[failure] 15-15:
File is not properly formatted (gci)

🔇 Additional comments (52)
v2/pkg/engine/datasource/introspection_datasource/fixtures/schema_introspection_with_custom_root_operation_types.golden (1)

514-514: LGTM - Formatting-only change.

The trailing newline adjustment has no functional impact on the fixture data.

v2/pkg/engine/datasource/introspection_datasource/fixtures/schema_introspection.golden (1)

366-366: LGTM - Formatting-only change.

The trailing newline adjustment has no functional impact on the fixture data.

v2/pkg/engine/datasource/introspection_datasource/fixtures/type_introspection.golden (1)

59-59: LGTM - Formatting-only change.

The trailing newline adjustment has no functional impact on the fixture data.

v2/pkg/engine/resolve/inputtemplate.go (3)

7-7: LGTM!

The io import is necessary for the InputTemplateWriter interface definition.


58-63: LGTM! Clean abstraction for arena-based memory management.

The interface is well-designed and appropriately scoped, including only the methods needed for the rendering flow. This abstraction enables the arena-based memory management improvements mentioned in the PR objectives while maintaining compatibility with *bytes.Buffer.


65-65: All callers are compatible with the interface change.

The exported methods Render and RenderAndCollectUndefinedVariables have been correctly updated to accept InputTemplateWriter. Since bytes.Buffer implements the required interface (io.Writer, io.StringWriter, Reset(), Bytes()), all existing callers—in loader.go, resolve.go, subscription_filter.go, and tests—pass compatible types without requiring updates.

v2/pkg/engine/resolve/tainted_objects_test.go (1)

73-75: LGTM: switched to astjson.ParseBytes

The change is straightforward; parsing behavior in tests remains correct.

Also applies to: 97-99, 323-327

v2/pkg/engine/plan/visitor.go (1)

1293-1296: Populate SourceName/ID on subscription trigger

Good addition; aligns tests and surfaces subgraph identity on triggers.

Confirm objectFetchConfiguration always sets both sourceName and sourceID for subscription fetches; if either can be empty, consider defaulting SourceName to sourceID (and vice versa) for consistency.

v2/pkg/engine/resolve/response.go (1)

19-26: LGTM: GraphQLSubscriptionTrigger includes SourceName/SourceID

Public surface extended as intended; no behavioral changes.

v2/pkg/engine/datasource/pubsub_datasource/pubsub_datasource_test.go (1)

427-429: Tests updated for SourceName/SourceID

Expectations match the new trigger fields and datasource id/name.

Also applies to: 492-494, 539-541, 592-594

v2/pkg/engine/resolve/resolve_federation_test.go (1)

19-28: Mocks updated for new Load signature and response []byte

Good updates: include headers param and return []byte. Assertions validate inputs properly.

Also applies to: 23-27

v2/pkg/engine/resolve/context.go (1)

164-166: Request.ID type change verified — all consumers compatible

The type change from prior type to uint64 is verified as safe:

  • Pooling/dedup: Both ArenaPool.Acquire() and ArenaPool.Release() have signatures expecting uint64; all call sites pass ctx.Request.ID directly—fully compatible.
  • Logging: No code logs Request.ID.
  • Tracing: RequestData (the struct included in trace output) contains no ID field; Request.ID is not part of trace output.
  • JSON marshaling: No JSON marshaling of Request or its ID field.
v2/go.mod (1)

31-33: All concerns verified—no issues found.

The repository correctly uses Go 1.25 across both go.mod directive and CI workflows (matrix values in v2.yml and execution.yml both specify go: [ "1.25" ]). The setup-go@v5 action is compatible with this version. The astjson v1.0.0 and go-arena v1.0.0 dependencies are properly listed in the require section, go.sum is present and synchronized (226 entries), and no deprecated astjson APIs are in use. The module is tidy.

v2/pkg/astnormalization/uploads/upload_finder.go (1)

77-77: ParseBytes migration verified—behavior parity and concurrency characteristics confirmed.

Edge case handling (nil, "", "null" → "{}") is explicit at lines 74–76, ensuring normalized input regardless of ParseBytes implementation. Variables are accessed only within the same FindUploads invocation, with no shared Parser instance—each call is independent. The migration is safe and complete (no ParseBytesWithoutCache calls remain in the codebase).

v2/pkg/engine/resolve/authorization_test.go (2)

512-519: LGTM! Mock signature correctly updated.

The mock expectation properly reflects the new DataSource Load signature with HTTP headers and direct byte slice return. The DoAndReturn handler is correctly structured to return ([]byte, error).


817-824: LGTM! Consistent mock pattern.

All mock data source setups in this test file follow the same pattern with the updated Load signature. The test data continues to return valid GraphQL responses wrapped in the expected format.

v2/pkg/engine/resolve/resolvable_custom_field_renderer_test.go (1)

443-443: LGTM! Constructor signature updated correctly.

The NewResolvable call now includes the arena.Arena parameter (nil) as the first argument, aligning with the updated constructor signature in the broader PR.

v2/pkg/fastjsonext/fastjsonext_test.go (1)

24-29: LGTM! Arena parameter migration applied consistently.

The test correctly passes nil for the arena parameter to AppendErrorToArray, aligning with the PR's migration from *astjson.Arena to arena.Arena interface.

v2/pkg/engine/resolve/event_loop_test.go (1)

74-85: LGTM! Subscription Start signature updated.

The Start method now correctly accepts HTTP headers as the second parameter, consistent with the PR's objective to propagate headers through the data flow. The method implementation remains functionally correct.

v2/pkg/engine/resolve/loader_test.go (3)

20-34: LGTM! Mock responses correctly structured.

The mock data sources now return responses wrapped under a top-level "data" field, consistent with the GraphQL response format and the broader changes in this PR.


290-300: LGTM! Test setup correctly updated.

Both the NewResolvable constructor call and the expected output assertion have been updated to match the new patterns introduced in the PR.


1524-1530: LGTM! Error path rewriting correctly updated.

The rewriteErrorPaths function calls now pass nil as the first parameter (arena), consistent with the arena-based memory management changes in this PR.

v2/pkg/engine/datasource/httpclient/httpclient_test.go (1)

82-85: LGTM! Test correctly updated for new Do signature.

The test now properly handles the new Do signature that returns ([]byte, error) directly instead of writing to a buffer. The assertion compares the returned bytes with the expected output.

v2/pkg/engine/resolve/loader_hooks_test.go (1)

52-56: LGTM! Mock expectations correctly updated.

The mock Load method now expects three parameters (context, headers, input) and the DoAndReturn handler correctly returns ([]byte, error), aligning with the updated DataSource interface.

v2/pkg/engine/datasource/introspection_datasource/source.go (2)

22-33: LGTM! Load method signature correctly updated.

The Load method now:

  • Accepts headers http.Header as the second parameter
  • Returns (data []byte, err error) instead of writing to a buffer
  • Uses the new singleTypeBytes helper for type-specific responses

All error paths correctly return (nil, err) on failure.


61-68: LGTM! Helper method properly implements byte-returning pattern.

The new singleTypeBytes helper correctly mirrors the previous singleType logic but returns bytes directly instead of writing to an io.Writer. The nil type handling properly returns the null byte slice.

v2/pkg/engine/plan/planner_test.go (3)

7-7: LGTM - Import added for new API.

The net/http import is correctly added to support the new http.Header parameter in the updated DataSource method signatures.


1078-1080: LGTM - Test mock updated to new API.

The Load method signature correctly reflects the new DataSource API that accepts HTTP headers and returns data directly instead of writing to a buffer. Returning nil, nil is appropriate for a test fake.


1082-1084: LGTM - Test mock updated consistently.

The LoadWithFiles method signature correctly mirrors the Load method changes with HTTP headers and direct return values. The test fake implementation is appropriate.

v2/pkg/engine/datasource/staticdatasource/static_datasource.go (2)

5-5: LGTM - Import added for new API.

The net/http import correctly supports the updated method signatures.


74-76: LGTM - Static source correctly returns input.

The implementation appropriately returns the input bytes directly, which is the expected behavior for a static data source.

v2/pkg/engine/resolve/resolve_mock_test.go (1)

1-67: LGTM - Auto-generated mock updated correctly.

This is an auto-generated mock file (by MockGen) that has been correctly regenerated to match the new DataSource interface signatures with HTTP headers and direct return values. No manual review concerns.

v2/pkg/fastjsonext/fastjsonext.go (5)

5-5: LGTM - Arena dependency added for memory pooling.

The go-arena import supports the PR's objective of improving memory management through arena-based pooling.


8-15: LGTM - Arena-based error appending refactored correctly.

The function signature and implementation correctly adopt the new arena-centric API, passing the arena context through value creation and mutation calls.


17-27: LGTM - Consistent arena usage for error with extensions.

The function correctly uses arena-based value construction throughout, maintaining consistency with the new API pattern.


34-50: LGTM - Error object creation properly arena-aware.

The function correctly constructs error objects using arena-based value creation, ensuring memory efficiency and proper lifecycle management.


52-59: No issues found - nil arena usage is intentional and correct.

After thorough verification:

  • The PrintGraphQLResponse function uses out.Set(nil, ...) to set fields on a value created by astjson.MustParse
  • This nil arena pattern is consistent with the codebase: the codebase explicitly uses ParseBytesWithArena(nil, data) in loader_test.go, demonstrating nil arena is an established, intentional pattern
  • Tests pass successfully across multiple scenarios (loader_test.go lines 297, 759, 1044, 1431), confirming the function works correctly
  • The function serves as a terminal operation that composes pre-constructed values and serializes them—it does not manage arena lifetimes like the building functions (AppendErrorToArray, CreateErrorObjectWithPath)
  • The design is appropriate: building functions receive and manage explicit arena parameters, while terminal functions like PrintGraphQLResponse use nil arena for simpler composition and serialization
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go (2)

4023-4025: Include of SourceName/SourceID on subscription trigger looks right

Good addition for tracing/observability and source attribution. Please just confirm the values match the configured DataSource ID in the surrounding plan to avoid misleading logs/metrics.

Also applies to: 4066-4068


8773-8776: Load now returns bytes — assertions look good

The switch to capturing data from Load and asserting on the exact JSON payload is correct and matches the new API.

Also applies to: 8795-8798

v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go (1)

1909-1917: Good: headers plumbed and return-bytes API adhered to.

The methods correctly compact variables, forward headers to httpclient, and return data bytes.

v2/pkg/engine/datasource/grpc_datasource/grpc_datasource_test.go (1)

404-409: Assertion aligns with error-as-data policy; keep consistent with Load behavior.

No change required; just ensure datasource consistently returns nil error in all error paths (see datasource comment).

If you adopt the merge-error change, re-run this test to confirm it still passes.

v2/pkg/engine/datasource/grpc_datasource/grpc_datasource.go (1)

101-129: ****

The repository declares Go 1.25 in its go.mod files. Since Go 1.22+, loop variables are scoped per-iteration by default, automatically preventing the closure capture issue the review comment warns about. The suggested rebinding refactor is unnecessary—the original code is correct and does not have a closure capture bug. No changes are required.

Likely an incorrect or invalid review comment.

v2/pkg/engine/resolve/resolve.go (2)

5-20: Fix import ordering to satisfy gci

Linters report “File is not properly formatted (gci)”. Run gci/fmt over this file (and module-wide) to fix grouping/order.

Example:

  • standard lib
  • third-party
  • project-local

306-349: ArenaResolveGraphQLResponse looks solid

Acquire/assign/release of arenas and buffered write via arena buffer are correct.

v2/pkg/engine/resolve/loader.go (8)

360-373: Good use of arena allocation for items.

The arena-based allocation in selectItemsForPath properly uses the loader's arena for memory management, which aligns with the PR's memory management improvements.


547-549: Proper arena allocation before parsing.

The code correctly allocates a slice copy in the arena before parsing, ensuring the parsed data is arena-managed throughout its lifecycle.


1576-1592: Operation type context propagation looks good.

The new context key pattern for operation type propagation is implemented correctly with a safe default fallback.


1594-1603: Headers extraction method looks correct.

The headersForSubgraphRequest method properly handles nil checks and extracts headers for subgraph requests, with the extraKey used for request deduplication.


1656-1666: Direct load implementation is correct.

The loadByContextDirect method properly calls the updated DataSource interface methods with headers, aligning with the PR's API changes.


1806-1814: Execution now properly uses loadByContext.

The refactored execution flow correctly delegates to loadByContext, which encapsulates SingleFlight deduplication logic while respecting loader hooks.


791-791: Consistent arena usage for error object creation.

All error object creation now uses ParseWithArena, ensuring arena-based memory management is applied consistently throughout the error handling paths.

Also applies to: 1018-1028, 1065-1065, 1079-1079, 1091-1091, 1109-1109, 1144-1144, 1150-1150, 1160-1160, 1166-1166, 1186-1186, 1191-1191, 1198-1198, 1203-1203, 1210-1210, 1214-1214


185-186: ****

The initialization concern is unfounded. Both jsonArena and sf are properly initialized before use:

  • sf is assigned in newTools() (line 265 in resolve.go) from the Resolver's sf field, which is created with NewSingleFlight() at Resolver initialization.
  • jsonArena is assigned from the arena pool in resolve.go (lines 319, 485) immediately before the loader is used, after which LoadGraphQLResponseData is called.

The fields don't need cleanup in Free() because they're lifecycle-managed by their respective owners (Resolver for sf, arena pool for jsonArena), not by the Loader.

Likely an incorrect or invalid review comment.

Copy link
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.

Actionable comments posted: 0

♻️ Duplicate comments (6)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (3)

163-165: Avoid mutating/capturing caller's headers; clone before use.

Direct assignment request.Header = baseHeaders aliases the map, allowing subsequent Add/Set calls to mutate the caller's map, which can cause race conditions across concurrent requests.

Apply this diff:

 if baseHeaders != nil {
-   request.Header = baseHeaders
+   request.Header = baseHeaders.Clone()
 }

219-223: Set Request.ContentLength field instead of the header.

Manually setting the Content-Length header is ignored or overwritten by net/http. The request.ContentLength field should be set instead.

Apply this diff:

 if contentLength > 0 {
-   // always set the Content-Length Header so that chunking can be avoided
-   // and other parties can more efficiently parse
-   request.Header.Set(ContentLengthHeader, fmt.Sprintf("%d", contentLength))
+   request.ContentLength = int64(contentLength)
 }

Note: Remove the header manipulation; net/http will populate the header from the field. For streaming bodies with unknown length, leave ContentLength at its default (-1).


314-319: Check os.Open error before appending; prevent nil deref in cleanup.

temporaryFile is appended to tempFiles before checking the error from os.Open. If the open fails, tempFiles will contain a nil entry, causing the defer cleanup at lines 329-339 to panic when attempting to close or remove the file.

Apply this diff:

 temporaryFile, err := os.Open(file.Path())
-tempFiles = append(tempFiles, temporaryFile)
 if err != nil {
    return nil, err
 }
+tempFiles = append(tempFiles, temporaryFile)
 formValues[key] = bufio.NewReader(temporaryFile)
v2/pkg/engine/resolve/loader.go (1)

20-20: Fix import ordering per static analysis.

The gci linter indicates this file is not properly formatted. Ensure imports are grouped correctly (standard library, external, internal).

Run the following to fix:

#!/bin/bash
# Format the file with gci
gci write --skip-generated -s standard -s default -s "prefix(github.com/wundergraph/graphql-go-tools)" v2/pkg/engine/resolve/loader.go
v2/pkg/engine/resolve/resolvable.go (1)

14-14: Fix import ordering per static analysis.

The gci linter indicates this file is not properly formatted. Ensure imports are grouped correctly (standard library, external, internal).

Run the following to fix:

#!/bin/bash
# Format the file with gci
gci write --skip-generated -s standard -s default -s "prefix(github.com/wundergraph/graphql-go-tools)" v2/pkg/engine/resolve/resolvable.go
v2/pkg/engine/resolve/resolve.go (1)

15-15: Fix import ordering per static analysis.

The gci linter indicates this file is not properly formatted. Ensure imports are grouped correctly (standard library, external, internal).

Run the following to fix:

#!/bin/bash
# Format the file with gci
gci write --skip-generated -s standard -s default -s "prefix(github.com/wundergraph/graphql-go-tools)" v2/pkg/engine/resolve/resolve.go
🧹 Nitpick comments (3)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (1)

2440-2440: LGTM - discarding return values is acceptable here.

Explicitly discarding both return values from fmt.Fprintf is a common pattern in test mock handlers. Since this test focuses on WebSocket upgrade failure handling rather than the mock server's write success, this is acceptable.

If you want to be more defensive, consider checking the error:

-				_, _ = fmt.Fprintf(w, `{"error": "WebSocket upgrade failed", "status": %d}`, tc.statusCode)
+				_, err := fmt.Fprintf(w, `{"error": "WebSocket upgrade failed", "status": %d}`, tc.statusCode)
+				require.NoError(t, err)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)

329-339: Consider completing cleanup even on error.

The early return on Close() or Remove() errors prevents cleanup of remaining files, potentially leaking file descriptors. While minor compared to the nil entry issue at lines 314-319, consider logging errors and continuing the cleanup loop.

Example approach:

 defer func() {
    multipartBody.Close()
    for _, file := range tempFiles {
-       if err := file.Close(); err != nil {
-           return
-       }
-       if err = os.Remove(file.Name()); err != nil {
-           return
-       }
+       if closeErr := file.Close(); closeErr != nil {
+           // log closeErr but continue cleanup
+       }
+       if removeErr := os.Remove(file.Name()); removeErr != nil {
+           // log removeErr but continue cleanup
+       }
    }
 }()
v2/pkg/engine/resolve/loader.go (1)

1636-1686: Consider defensive copy when caching single-flight responses.

Line 1684 stores res.out directly into the shared cache without copying. While mergeResult (lines 556-558) makes a defensive copy before parsing, the cached item.response slice could be problematic if:

  • The DataSource.Load() implementation reuses the backing array across calls, or
  • Concurrent readers access the cached response while the backing array is modified elsewhere.

A defensive copy at line 1684 would eliminate this risk:

-	item.response = res.out
+	item.response = append([]byte(nil), res.out...)

This aligns with the defensive copy pattern already used at line 547-548 (in earlier code) and lines 556-558.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 60b5c3b and 3fb0272.

📒 Files selected for processing (6)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (1 hunks)
  • v2/pkg/engine/datasource/httpclient/nethttpclient.go (9 hunks)
  • v2/pkg/engine/resolve/context.go (2 hunks)
  • v2/pkg/engine/resolve/loader.go (50 hunks)
  • v2/pkg/engine/resolve/resolvable.go (14 hunks)
  • v2/pkg/engine/resolve/resolve.go (20 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • v2/pkg/engine/resolve/context.go
🧰 Additional context used
🧬 Code graph analysis (4)
v2/pkg/engine/resolve/resolve.go (7)
v2/pkg/engine/resolve/arena.go (2)
  • ArenaPool (13-17)
  • NewArenaPool (30-34)
v2/pkg/engine/resolve/singleflight.go (2)
  • SingleFlight (16-22)
  • NewSingleFlight (29-41)
v2/pkg/engine/resolve/resolvable.go (2)
  • NewResolvable (72-80)
  • ResolvableOptions (65-70)
v2/pkg/engine/resolve/context.go (4)
  • Context (16-37)
  • Request (170-173)
  • ExecutionOptions (57-63)
  • SubgraphHeadersBuilder (43-47)
v2/pkg/engine/resolve/response.go (3)
  • GraphQLResponse (35-43)
  • GraphQLSubscription (12-16)
  • SubscriptionResponseWriter (68-74)
v2/pkg/engine/datasource/staticdatasource/static_datasource.go (3)
  • Source (72-72)
  • Source (74-76)
  • Source (78-80)
v2/pkg/pool/hash64.go (1)
  • Hash64 (10-16)
v2/pkg/engine/resolve/loader.go (5)
v2/pkg/engine/resolve/singleflight.go (1)
  • SingleFlight (16-22)
v2/pkg/engine/resolve/fetch.go (5)
  • FetchItemPathElement (78-82)
  • FetchItem (29-34)
  • DataSourceLoadTrace (405-419)
  • Fetch (20-27)
  • FetchInfo (376-397)
v2/pkg/engine/resolve/context.go (3)
  • RateLimitOptions (129-142)
  • Context (16-37)
  • ExecutionOptions (57-63)
v2/pkg/engine/resolve/inputtemplate.go (1)
  • SetInputUndefinedVariables (39-51)
v2/pkg/pool/hash64.go (1)
  • Hash64 (10-16)
v2/pkg/engine/resolve/resolvable.go (2)
v2/pkg/engine/resolve/resolve.go (1)
  • New (187-251)
v2/pkg/fastjsonext/fastjsonext.go (2)
  • AppendErrorToArray (8-15)
  • AppendErrorWithExtensionsCodeToArray (17-27)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (2)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
🪛 GitHub Check: Linters (1.25)
v2/pkg/engine/resolve/resolve.go

[failure] 15-15:
File is not properly formatted (gci)

v2/pkg/engine/resolve/loader.go

[failure] 20-20:
File is not properly formatted (gci)

v2/pkg/engine/resolve/resolvable.go

[failure] 14-14:
File is not properly formatted (gci)

🔇 Additional comments (19)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (6)

30-30: LGTM: constant addition.

The ContentLengthHeader constant is properly defined and aligns with other header constants in the file.


133-154: LGTM: well-designed size hint mechanism.

The context-based size hint with a custom key type prevents collisions, and the buffer sizing logic balances memory efficiency with allocation overhead.


240-250: LGTM: thoughtful buffer lifecycle management.

The comment explaining the GC-based approach over pooling is valuable documentation. The buffer creation leverages the size hint mechanism to reduce allocations efficiently.


252-278: LGTM: clean trace extension handling.

The conditional trace embedding is well-structured, and error propagation follows the consistent (nil, err) pattern throughout.


281-284: LGTM: signature change aligns with PR objectives.

The updated signature to return []byte directly and accept baseHeaders matches the PR's goal of improving memory management and moving request deduplication into the engine.


341-341: LGTM: appropriate contentLength for multipart.

Passing contentLength=0 for the multipart body is correct since the body is a pipe reader with unknown length at request creation time.

v2/pkg/engine/resolve/loader.go (5)

186-196: Arena and single-flight integration looks good.

The documentation clearly explains the arena's thread-safety constraints and lifecycle. The warning about tying parsed JSON's underlying bytes to the arena lifecycle is crucial for avoiding segfaults.


369-383: Arena-aware helper functions are well-structured.

The consistent pattern of accepting arena.Arena as the first parameter and using arena-backed allocations throughout selectItemsForPath, selectItems, and itemsData ensures memory is managed correctly.

Also applies to: 401-439, 441-455


556-558: Defensive copy before parsing is essential.

This pattern correctly ties the lifecycle of the parsed JSON to the arena-allocated slice, preventing segfaults as noted in the comments at lines 190-193.


752-831: Arena-aware error handling is consistently implemented.

The lazy initialization via l.resolvable.ensureErrorsInitialized() (lines 788, 828, etc.) is a good optimization, and arena-backed object creation throughout error rendering paths is correct.

Also applies to: 873-1044


1606-1624: New context helper looks good.

GetOperationTypeFromContext provides a clean way to access the operation type from context, useful for the transport layer to make decisions (e.g., disabling single-flight for mutations).

v2/pkg/engine/resolve/resolvable.go (3)

72-80: Constructor signature updated for arena support.

Accepting arena.Arena as a parameter is correct. Callers must ensure they provide a valid arena before calling methods that use it (like Init), or rely on the new ArenaResolveGraphQLResponse path in resolve.go.


240-245: Lazy error initialization is an excellent optimization.

This pattern avoids allocating the errors array upfront, which can significantly reduce memory usage when most operations complete without errors. The consistent use of ensureErrorsInitialized() throughout the file (lines 773, 1215, 1286, 1292, 1298) ensures correctness.


110-128: Arena integration in Init methods looks correct.

Both Init and InitSubscription properly use arena-backed object creation and parsing. The explicit comments about not initializing errors (lines 115-116, 134-135) helpfully document the lazy initialization strategy.

Also applies to: 130-166

v2/pkg/engine/resolve/resolve.go (5)

75-85: Arena pools and single-flight initialization look excellent.

The separation of resolveArenaPool and responseBufferPool is well-reasoned (as explained in the comments), and both pools along with the single-flight cache are properly initialized.

Also applies to: 239-241


313-356: ArenaResolveGraphQLResponse has excellent arena lifecycle management.

This new method demonstrates proper arena handling:

  • Acquires arenas before use (lines 325, 343)
  • Sets arenas on both loader and resolvable (lines 326-327)
  • Releases arenas on all error paths (lines 331, 338, 347-349)
  • Releases arenas after successful completion (lines 352, 354)

The use of arena-backed buffers (line 344) for response writing is also correct.


488-530: Subscription update arena handling is correct.

The pattern of acquiring the arena (line 490), setting it on tools (lines 491-492), and releasing on all exit paths (lines 495, 507, 519, 530) is consistent and safe.


1079-1084: Header propagation helper is clean and straightforward.

The triggerHeaders method correctly delegates to SubgraphHeadersBuilder when available and provides sensible defaults.


253-275: Clarify the misleading "we set the arena manually" comment and verify nil arena handling in ResolveGraphQLResponse.

The code shows both ResolveGraphQLResponse and ArenaResolveGraphQLResponse exist in parallel, but the comment at line 255 is inaccurate. ResolveGraphQLResponse never sets the arena before calling Init (line 293)—only ArenaResolveGraphQLResponse does. The data parameter difference between them (ResolveGraphQLResponse passes data bytes, ArenaResolveGraphQLResponse passes nil) may influence arena behavior, but without examining the astjson library's nil-arena handling, it's unclear if this is an intentional design or a latent issue.

Either:

  • Update the comment to clarify that newTools intentionally creates Resolvable with nil arena for the non-arena code path, and verify tests cover this path
  • Or verify that astjson functions (ObjectValue, ParseBytesWithArena, MergeValues) safely handle nil arenas

Copy link
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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)

314-318: Critical: os.Open error still checked after append - can cause nil dereference.

The past review comment on this issue was not addressed. When os.Open fails, temporaryFile is nil but still appended to tempFiles. The defer cleanup at line 332 will then panic when calling file.Close() on the nil entry.

Apply this diff to fix:

  temporaryFile, err := os.Open(file.Path())
- tempFiles = append(tempFiles, temporaryFile)
  if err != nil {
      return nil, err
  }
+ tempFiles = append(tempFiles, temporaryFile)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fb0272 and ce83a7b.

📒 Files selected for processing (5)
  • v2/pkg/engine/datasource/httpclient/nethttpclient.go (9 hunks)
  • v2/pkg/engine/resolve/inputtemplate.go (8 hunks)
  • v2/pkg/engine/resolve/loader.go (50 hunks)
  • v2/pkg/engine/resolve/loader_test.go (14 hunks)
  • v2/pkg/engine/resolve/resolvable.go (14 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
v2/pkg/engine/resolve/resolvable.go (2)
v2/pkg/engine/resolve/resolve.go (1)
  • New (187-251)
v2/pkg/fastjsonext/fastjsonext.go (2)
  • AppendErrorToArray (8-15)
  • AppendErrorWithExtensionsCodeToArray (17-27)
v2/pkg/engine/resolve/inputtemplate.go (2)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/lexer/literal/literal.go (1)
  • COMMA (23-23)
v2/pkg/engine/resolve/loader_test.go (2)
v2/pkg/engine/resolve/resolvable.go (2)
  • NewResolvable (72-80)
  • ResolvableOptions (65-70)
v2/pkg/engine/resolve/loader.go (1)
  • Loader (159-196)
v2/pkg/engine/resolve/loader.go (5)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (2)
  • ResponseContext (72-76)
  • WithHTTPClientSizeHint (142-144)
v2/pkg/engine/resolve/fetch.go (11)
  • SingleFetch (91-99)
  • SingleFetch (153-155)
  • BatchEntityFetch (160-169)
  • BatchEntityFetch (194-196)
  • EntityFetch (200-209)
  • EntityFetch (226-228)
  • FetchItemPathElement (78-82)
  • FetchItem (29-34)
  • DataSourceLoadTrace (405-419)
  • Fetch (20-27)
  • FetchInfo (376-397)
v2/pkg/engine/resolve/context.go (3)
  • RateLimitOptions (129-142)
  • Context (16-37)
  • ExecutionOptions (57-63)
v2/pkg/engine/resolve/inputtemplate.go (1)
  • SetInputUndefinedVariables (39-51)
v2/pkg/pool/hash64.go (1)
  • Hash64 (10-16)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (2)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
🪛 GitHub Actions: v2-ci
v2/pkg/engine/resolve/loader.go

[error] 23-23: golangci-lint: File is not properly formatted (gci).

🪛 GitHub Check: Linters (1.25)
v2/pkg/engine/resolve/resolvable.go

[failure] 16-16:
File is not properly formatted (gci)

v2/pkg/engine/resolve/loader.go

[failure] 23-23:
File is not properly formatted (gci)

🔇 Additional comments (12)
v2/pkg/engine/resolve/resolvable.go (3)

239-244: LGTM: Well-designed lazy initialization helper.

The ensureErrorsInitialized() helper cleanly implements the lazy initialization pattern for the errors array, supporting the memory optimization goal mentioned in the PR objectives. The consistent usage throughout the file (lines 772, 1214, 1285, 1291, 1297) ensures errors are only allocated when needed.


113-115: Effective memory optimization through lazy initialization.

Setting r.errors = nil instead of eager initialization is a good optimization that defers allocation until errors actually occur. The inline comments clearly document this design decision, and the ensureErrorsInitialized() helper ensures safe usage throughout the codebase.

Also applies to: 133-134, 172-173


71-79: Intentional API change for arena integration.

The constructor signature change from NewResolvable(context.Context, ResolvableOptions) to NewResolvable(arena.Arena, ResolvableOptions) is a breaking change that aligns with the PR's arena-based memory management objectives. All test call sites have been correctly updated to pass nil or an appropriate arena instance.

v2/pkg/engine/resolve/loader_test.go (1)

290-290: Test updates correctly reflect API changes.

All test instantiations of NewResolvable have been properly updated to pass nil as the first arena parameter, consistent with the new constructor signature. This approach is appropriate for tests that don't require specific arena lifecycle management.

Also applies to: 379-379, 470-470, 752-752, 1027-1027, 1128-1128, 1424-1424

v2/pkg/engine/resolve/inputtemplate.go (2)

58-63: Well-designed abstraction with InputTemplateWriter interface.

The new InputTemplateWriter interface cleanly abstracts buffer operations (io.Writer, io.StringWriter, Reset(), Bytes()) and enables controlled reuse across the template rendering pipeline. This design improves testability and flexibility.


161-174: Error handling improvement for write operations.

Previously, write errors in renderHeaderVariable were silently ignored using _, _ = preparedInput.Write(...). The updated code now properly checks and propagates these errors (lines 161-163, 168-174), which improves robustness and makes potential issues visible to callers.

v2/pkg/engine/resolve/loader.go (3)

185-195: Excellent documentation of arena thread-safety constraints.

The detailed comments on jsonArena clearly explain critical constraints:

  • Not thread-safe (use only from main goroutine)
  • Lifecycle managed by Resolver (don't Reset or Release)
  • Critical requirement: underlying bytes must be arena-allocated to tie lifecycles

This documentation will help prevent common arena misuse patterns that lead to segfaults.


552-557: Critical arena lifecycle management pattern.

Lines 555-557 implement a crucial pattern: allocating a new slice on the arena and copying res.out before parsing with ParseBytesWithArena. This ensures the parsed AST and the underlying bytes share the same arena lifecycle, preventing segfaults when the arena is released. As noted in the arena comments (line 191), this pattern is essential for correct arena usage.


1635-1685: SingleFlight integration properly handles mutations and context.

The loadByContext function correctly:

  • Disables deduplication for mutations (line 1644) to avoid side-effect issues
  • Respects the DisableRequestDeduplication flag (line 1645)
  • Handles context cancellation via select with item.loaded and ctx.Done() (lines 1657-1661)
  • Provides size hints to the HTTP client (line 1672)
  • Properly stores shared responses (line 1667, 1683)

The integration aligns with the PR's request deduplication objectives.

v2/pkg/engine/datasource/httpclient/nethttpclient.go (3)

133-154: LGTM: Buffer size hint mechanism is well-designed.

The context-based size hint and the buffer function provide an efficient way to pre-allocate buffers based on expected response sizes, reducing allocations and copies. The 64-byte default for cases without a hint is a reasonable middle ground.


240-278: LGTM: Response buffering and trace handling are well-implemented.

The buffer allocation strategy is clearly documented, and the trace injection using jsonparser.Set is correct. Error handling is consistent throughout.


281-283: LGTM: Do function correctly updated to new signature.

The function now properly accepts baseHeaders and returns data directly, aligning with the PR objectives. Passing len(body) as contentLength is correct for regular HTTP requests.

Copy link
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.

Actionable comments posted: 0

♻️ Duplicate comments (1)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)

311-319: Critical: nil file appended to tempFiles causes panic in cleanup.

Line 315 appends temporaryFile before checking the error on line 316. If os.Open fails, temporaryFile is nil, and the cleanup loop (lines 331-337) will panic when calling file.Close() on the nil pointer.

Apply this diff to fix the error handling order:

 		key := fmt.Sprintf("%d", i)
 		temporaryFile, err := os.Open(file.Path())
-		tempFiles = append(tempFiles, temporaryFile)
 		if err != nil {
 			return nil, err
 		}
+		tempFiles = append(tempFiles, temporaryFile)
 		formValues[key] = bufio.NewReader(temporaryFile)
🧹 Nitpick comments (1)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)

219-223: Update comment to reflect that field (not header) is set.

The code correctly sets request.ContentLength field, but the comment on line 220 says "always set the Content-Length Header" which is misleading. The field and header are different—net/http will populate the header from the field.

Consider updating for clarity:

 	if contentLength > 0 {
-		// always set the Content-Length Header so that chunking can be avoided
+		// always set the ContentLength field so that chunking can be avoided
 		// and other parties can more efficiently parse
 		request.ContentLength = int64(contentLength)
 	}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce83a7b and 5cfd72d.

📒 Files selected for processing (4)
  • v2/pkg/engine/datasource/httpclient/nethttpclient.go (9 hunks)
  • v2/pkg/engine/resolve/loader.go (50 hunks)
  • v2/pkg/engine/resolve/resolvable.go (14 hunks)
  • v2/pkg/engine/resolve/resolve.go (20 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
v2/pkg/engine/resolve/loader.go (6)
v2/pkg/engine/resolve/singleflight.go (1)
  • SingleFlight (16-22)
v2/pkg/engine/resolve/fetch.go (5)
  • FetchItemPathElement (78-82)
  • FetchItem (29-34)
  • DataSourceLoadTrace (405-419)
  • Fetch (20-27)
  • FetchInfo (376-397)
v2/pkg/engine/resolve/context.go (3)
  • RateLimitOptions (129-142)
  • Context (16-37)
  • ExecutionOptions (57-63)
v2/pkg/engine/resolve/inputtemplate.go (1)
  • SetInputUndefinedVariables (39-51)
v2/pkg/pool/hash64.go (1)
  • Hash64 (10-16)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)
  • WithHTTPClientSizeHint (142-144)
v2/pkg/engine/resolve/resolvable.go (2)
v2/pkg/engine/resolve/resolve.go (1)
  • New (188-252)
v2/pkg/fastjsonext/fastjsonext.go (2)
  • AppendErrorToArray (8-15)
  • AppendErrorWithExtensionsCodeToArray (17-27)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
v2/pkg/engine/resolve/resolve.go (6)
v2/pkg/engine/resolve/arena.go (2)
  • ArenaPool (13-17)
  • NewArenaPool (30-34)
v2/pkg/engine/resolve/singleflight.go (2)
  • SingleFlight (16-22)
  • NewSingleFlight (29-41)
v2/pkg/engine/resolve/resolvable.go (2)
  • NewResolvable (72-80)
  • ResolvableOptions (65-70)
v2/pkg/engine/resolve/context.go (4)
  • Context (16-37)
  • Request (170-173)
  • ExecutionOptions (57-63)
  • SubgraphHeadersBuilder (43-47)
v2/pkg/engine/datasource/staticdatasource/static_datasource.go (3)
  • Source (72-72)
  • Source (74-76)
  • Source (78-80)
v2/pkg/pool/hash64.go (1)
  • Hash64 (10-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
🔇 Additional comments (6)
v2/pkg/engine/resolve/resolvable.go (1)

72-79: LGTM: Arena integration and lazy error initialization.

The changes properly integrate arena-based memory management:

  • NewResolvable signature correctly accepts arena.Arena as first parameter
  • Lazy error initialization via ensureErrorsInitialized() avoids allocating the errors array when not needed, improving memory efficiency
  • Arena usage is consistent throughout parsing and merging operations

The dual-path design (nil arena for legacy data-bytes path, explicit arena for optimized path) is intentional and correctly implemented.

Also applies to: 240-245

v2/pkg/engine/resolve/resolve.go (2)

314-357: LGTM: Arena lifecycle is correctly managed.

The ArenaResolveGraphQLResponse function properly handles arena acquisition and release:

  • resolveArena is acquired early (line 326) and released on all exit paths (lines 332, 339, 348, 353)
  • responseArena is acquired after resolution (line 344) and released after writing (line 355)
  • All error paths correctly release resources before returning

No resource leaks detected.


1080-1085: LGTM: Header-based subscription deduplication.

The triggerHeaders helper and its usage in subscription deduplication are well-designed:

  • Headers and hash are obtained via SubgraphHeadersBuilder.HeadersForSubgraph (line 1082)
  • The pre-computed headersHash is combined with the input hash to create a unique subscription ID (line 1130)
  • This ensures subscriptions with different headers are not deduplicated

The pattern is consistent across both sync (lines 1124-1131) and async (lines 1234-1241) subscription paths.

Also applies to: 1124-1131

v2/pkg/engine/resolve/loader.go (3)

369-455: LGTM: Arena-backed collection operations are well-implemented.

The arena integration for collection operations is correct:

  • selectItemsForPath and selectItems consistently allocate slices on the arena (lines 371, 423)
  • Arena appends use the correct arena.SliceAppend API (lines 433, 436)
  • itemsData properly passes arena to SetArrayItem (line 452)

The comments at lines 186-195 appropriately document arena thread-safety constraints and lifecycle management.


556-558: LGTM: Correct arena vs non-arena parsing patterns.

The code correctly handles two different parsing scenarios:

  1. Arena parsing (lines 556-558): Allocates bytes on the arena before calling ParseBytesWithArena. This ensures the parsed JSON nodes and their underlying byte slices share the same lifecycle.

  2. Non-arena parsing (lines 1886-1894): Uses ParseBytes when the data is not on the arena. The comment explicitly warns about the segfault risk when mixing arena-parsed values with non-arena bytes.

This distinction is critical for correctness and is properly documented.

Also applies to: 1886-1894


1636-1698: LGTM: Single-flight deduplication is correctly implemented.

The request deduplication logic is well-designed:

  • Excludes mutations (line 1645) to prevent sharing requests with side effects
  • Context-aware: Respects cancellation while waiting for shared responses (lines 1660-1661)
  • Thread-safe: Shared readers wait on item.loaded channel (line 1659) before accessing item.response (line 1668)
  • Size hints: Propagates previous response sizes to optimize buffer allocation (line 1673)

The loadByContextDirect helper cleanly separates the actual loading logic from deduplication concerns.

Copy link
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.

Actionable comments posted: 0

♻️ Duplicate comments (2)
v2/pkg/engine/resolve/resolve_test.go (1)

351-356: Use DoAndReturn instead of Do().Return on gomock expectations.

Gomock ignores Do’s returns; chain Return is misleading. Replace with a single DoAndReturn to set outputs and side effects together.

- mockDataSource.EXPECT().
-   Load(gomock.Any(), gomock.Any(), []byte(`{"id":1}`)).
-   Do(func(ctx context.Context, headers http.Header, input []byte) ([]byte, error) {
-     return []byte(`{"name":"Jens"}`), nil
-   }).
-   Return([]byte(`{"name":"Jens"}`), nil)
+ mockDataSource.EXPECT().
+   Load(gomock.Any(), gomock.Any(), []byte(`{"id":1}`)).
+   DoAndReturn(func(ctx context.Context, headers http.Header, input []byte) ([]byte, error) {
+     return []byte(`{"name":"Jens"}`), nil
+   })
v2/pkg/engine/resolve/loader.go (1)

1596-1638: SingleFlight cached response should be copied before sharing.

item.response = res.out stores the same backing slice; downstream reuse or pooling can corrupt shared readers. Make a defensive copy.

- item.response = res.out
+ item.response = append([]byte(nil), res.out...)

To ensure no callers mutate shared bytes, optionally scan for suspicious writes to SingleFlight items:

#!/bin/bash
rg -nP -C2 'item\.response' v2/pkg/engine/resolve -g '!**/*_test.go'
🧹 Nitpick comments (3)
v2/pkg/engine/resolve/inputtemplate.go (1)

86-86: Consider checking Write errors consistently.

While the improved error handling in renderHeaderVariable is good, several Write calls still ignore errors (lines 86, 108, 125, 146). Since InputTemplateWriter is now an interface, underlying implementations could fail for various reasons.

For consistency and robustness, consider checking errors from all Write operations:

 case StaticSegmentType:
-  _, _ = preparedInput.Write(segment.Data)
+  if _, err := preparedInput.Write(segment.Data); err != nil {
+    return err
+  }

Apply similar changes to lines 108, 125, and 146 where literal.NULL is written.

Also applies to: 108-108, 125-125, 146-146

v2/pkg/engine/resolve/resolve_test.go (1)

111-136: Unused parameter in test helper.

enableSingleFlight is unused in this helper; either wire it (e.g., toggle DisableRequestDeduplication via ctx) or drop it to reduce noise.

v2/pkg/engine/resolve/loader.go (1)

1603-1610: Trace flags: record “used” even when not shared.

SingleFlightUsed currently mirrors shared; on the first call it’s false although singleflight is engaged. Set used = true whenever GetOrCreateItem runs; keep shared to indicate fan‑out.

- if res.singleFlightStats != nil {
-   res.singleFlightStats.used = shared
-   res.singleFlightStats.shared = shared
- }
+ if res.singleFlightStats != nil {
+   res.singleFlightStats.used = true
+   res.singleFlightStats.shared = shared
+ }

Also applies to: 1803-1808

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cfd72d and 6cbfed0.

📒 Files selected for processing (12)
  • v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go (0 hunks)
  • v2/pkg/engine/resolve/arena.go (1 hunks)
  • v2/pkg/engine/resolve/context.go (2 hunks)
  • v2/pkg/engine/resolve/fetch.go (0 hunks)
  • v2/pkg/engine/resolve/fetchtree.go (0 hunks)
  • v2/pkg/engine/resolve/inputtemplate.go (8 hunks)
  • v2/pkg/engine/resolve/loader.go (48 hunks)
  • v2/pkg/engine/resolve/loader_hooks_test.go (13 hunks)
  • v2/pkg/engine/resolve/resolve.go (21 hunks)
  • v2/pkg/engine/resolve/resolve_test.go (29 hunks)
  • v2/pkg/engine/resolve/response.go (1 hunks)
  • v2/pkg/engine/resolve/singleflight.go (1 hunks)
💤 Files with no reviewable changes (3)
  • v2/pkg/engine/postprocess/create_concrete_single_fetch_types.go
  • v2/pkg/engine/resolve/fetchtree.go
  • v2/pkg/engine/resolve/fetch.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • v2/pkg/engine/resolve/arena.go
  • v2/pkg/engine/resolve/response.go
🧰 Additional context used
🧬 Code graph analysis (7)
v2/pkg/engine/resolve/context.go (1)
v2/pkg/engine/plan/planner.go (1)
  • IncludeQueryPlanInResponse (92-96)
v2/pkg/engine/resolve/loader_hooks_test.go (1)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/engine/resolve/loader.go (7)
v2/pkg/engine/resolve/singleflight.go (1)
  • SingleFlight (9-15)
v2/pkg/engine/resolve/fetch.go (5)
  • FetchItemPathElement (77-81)
  • FetchItem (28-33)
  • DataSourceLoadTrace (374-388)
  • Fetch (19-26)
  • FetchInfo (345-366)
v2/pkg/engine/resolve/context.go (3)
  • RateLimitOptions (135-148)
  • Context (16-37)
  • ExecutionOptions (57-69)
v2/pkg/engine/resolve/inputtemplate.go (1)
  • SetInputUndefinedVariables (39-51)
v2/pkg/pool/hash64.go (1)
  • Hash64 (10-16)
v2/pkg/ast/ast_operation_definition.go (3)
  • OperationType (11-11)
  • OperationTypeQuery (15-15)
  • OperationTypeMutation (16-16)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)
  • WithHTTPClientSizeHint (142-144)
v2/pkg/engine/resolve/singleflight.go (2)
v2/pkg/engine/resolve/resolve.go (1)
  • New (188-252)
v2/pkg/engine/resolve/fetch.go (3)
  • FetchItem (28-33)
  • Fetch (19-26)
  • FetchInfo (345-366)
v2/pkg/engine/resolve/inputtemplate.go (2)
v2/pkg/engine/resolve/context.go (1)
  • Context (16-37)
v2/pkg/lexer/literal/literal.go (1)
  • COMMA (23-23)
v2/pkg/engine/resolve/resolve.go (7)
v2/pkg/engine/resolve/arena.go (2)
  • ArenaPool (19-24)
  • NewArenaPool (38-42)
v2/pkg/engine/resolve/singleflight.go (2)
  • SingleFlight (9-15)
  • NewSingleFlight (40-52)
v2/pkg/engine/resolve/resolvable.go (2)
  • NewResolvable (72-80)
  • ResolvableOptions (65-70)
v2/pkg/engine/resolve/context.go (3)
  • Context (16-37)
  • Request (176-179)
  • SubgraphHeadersBuilder (43-47)
v2/pkg/engine/resolve/response.go (3)
  • GraphQLResponse (36-44)
  • GraphQLSubscription (12-16)
  • SubscriptionResponseWriter (69-75)
v2/pkg/engine/datasource/staticdatasource/static_datasource.go (3)
  • Source (72-72)
  • Source (74-76)
  • Source (78-80)
v2/pkg/pool/hash64.go (1)
  • Hash64 (10-16)
v2/pkg/engine/resolve/resolve_test.go (10)
v2/pkg/engine/resolve/context.go (3)
  • Context (16-37)
  • ExecutionOptions (57-69)
  • NewContext (181-188)
v2/pkg/engine/datasource/httpclient/file.go (1)
  • FileUpload (3-7)
v2/pkg/engine/resolve/response.go (2)
  • GraphQLResponse (36-44)
  • GraphQLResponseInfo (46-48)
v2/pkg/engine/resolve/fetchtree.go (6)
  • Single (59-67)
  • SingleWithPath (69-82)
  • Sequence (26-31)
  • Parallel (33-38)
  • ArrayPath (52-57)
  • ObjectPath (40-45)
v2/pkg/engine/resolve/fetch.go (7)
  • SingleFetch (90-98)
  • SingleFetch (152-154)
  • FetchConfiguration (248-274)
  • PostProcessingConfiguration (115-131)
  • BatchEntityFetch (159-168)
  • BatchEntityFetch (193-195)
  • BatchInput (178-191)
v2/pkg/engine/resolve/datasource.go (1)
  • DataSource (10-13)
v2/pkg/engine/resolve/inputtemplate.go (5)
  • InputTemplate (31-37)
  • TemplateSegment (22-29)
  • SegmentType (15-15)
  • StaticSegmentType (18-18)
  • VariableSegmentType (19-19)
v2/pkg/engine/resolve/variables.go (4)
  • VariableKind (7-7)
  • ContextVariableKind (10-10)
  • Variables (27-27)
  • ResolvableObjectVariableKind (13-13)
v2/pkg/engine/resolve/variables_renderer.go (2)
  • NewPlainVariableRenderer (57-61)
  • NewGraphQLVariableResolveRenderer (335-340)
v2/pkg/engine/resolve/subscription_filter.go (2)
  • SubscriptionFilter (16-21)
  • SubscriptionFieldFilter (23-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
🔇 Additional comments (23)
v2/pkg/engine/resolve/inputtemplate.go (4)

7-7: LGTM: Import change aligns with interface abstraction.

The replacement of bytes with io is appropriate given the new InputTemplateWriter interface uses io.Writer and io.StringWriter.


58-65: Excellent interface design for memory management flexibility.

The InputTemplateWriter interface cleanly abstracts buffer behavior, enabling the arena-backed memory pooling mentioned in the PR objectives. Since bytes.Buffer naturally satisfies this interface, existing callers remain compatible.


39-39: LGTM: Function signatures consistently updated to use the new interface.

All eight functions have been properly updated to accept InputTemplateWriter instead of *bytes.Buffer, maintaining a consistent API throughout the rendering pipeline.

Also applies to: 67-67, 77-77, 82-82, 119-119, 131-131, 135-135, 154-154


163-176: Good improvement: Error handling now enforced for header writes.

The addition of error checks for WriteString and Write operations makes this function more robust, especially since header values originate from external input.

v2/pkg/engine/resolve/loader_hooks_test.go (2)

6-6: LGTM: Import addition aligns with new Load signature.

The net/http import is necessary to support the updated Load signature that now accepts http.Header as a parameter.


53-565: LGTM: Mock signatures consistently updated across all test cases.

All mock expectations now correctly implement the new Load signature:

  • Accept http.Header parameter
  • Return ([]byte, error) instead of writing to io.Writer

The changes are uniform and properly simulate both success and error scenarios with JSON payloads.

v2/pkg/engine/resolve/context.go (4)

36-47: LGTM: Well-designed interface for header provisioning.

The SubgraphHeadersBuilder interface cleanly abstracts per-subgraph header provisioning and pre-computed hash generation for request deduplication. The documentation clearly explains the purpose and contract.


49-55: LGTM: Safe delegation with proper nil handling.

The method correctly guards against a nil builder and returns appropriate zero values, preventing nil pointer dereferences.


57-69: LGTM: Clear option for controlling deduplication.

The DisableRequestDeduplication field is well-named and documented. The comment clarifies that it applies within a single operation execution, which is the appropriate scope.


176-179: LGTM: Type change improves efficiency for arena keying.

Changing Request.ID from string to uint64 is more efficient for use as a map key in the arena pools. Note that this is a breaking change if Request is part of the public API and external code accesses this field directly.

v2/pkg/engine/resolve/resolve.go (6)

76-87: LGTM: Well-designed arena pool separation.

The use of two separate arena pools (resolveArenaPool for loading/resolving and responseBufferPool for response buffering) is a thoughtful design decision. The comments clearly explain the rationale: these workloads have different memory characteristics, making separate pools more efficient than a single shared pool.


254-276: LGTM: Flexible newTools signature supports both resolution paths.

The updated signature accepts arena and SingleFlight parameters, enabling the new arena-backed path while maintaining compatibility with the legacy data-bytes path (where nil arena is valid).


314-364: LGTM: Excellent arena lifecycle management.

The ArenaResolveGraphQLResponse method demonstrates careful resource management:

  • Acquires resolveArena for loading/resolving
  • Acquires responseArena only after loading completes
  • Releases resolveArena before the potentially slow I/O write operation
  • Releases responseArena after writing to the client
  • All error paths properly release acquired arenas

The staged release of resolveArena before I/O is particularly well-optimized.


1082-1095: LGTM: Clean trigger ID derivation with proper hashing.

The prepareTrigger method correctly:

  • Retrieves per-subgraph headers and their pre-computed hash
  • Combines input hash and header hash to create a unique trigger ID
  • Uses pooled xxhash for efficiency
  • Returns safe zero values when builder is absent

496-535: LGTM: Consistent arena management in subscription updates.

The method properly acquires and releases the arena on all code paths (lines 500, 512, 524, 535), preventing resource leaks during subscription updates.


737-739: LGTM: Headers properly propagated through subscription lifecycle.

The changes consistently thread headers through the subscription initialization:

  • prepareTrigger derives headers and trigger ID
  • addSubscription carries headers and source name
  • Start/AsyncStart receive headers for trigger creation

The pattern is consistent across both synchronous and asynchronous subscription paths.

Also applies to: 1134-1161, 1238-1259, 1365-1374

v2/pkg/engine/resolve/singleflight.go (5)

9-52: LGTM: Well-structured single-flight implementation.

The type definitions are clean and purposeful:

  • RWMutex enables concurrent reads for the fast path
  • loaded channel idiomatically signals completion to followers
  • sizeHint enables buffer pre-allocation based on historical data
  • fetchSize tracks rolling averages to optimize memory allocation

The constructor properly initializes all fields including the pooled xxhash digest.


61-92: LGTM: Correct double-checked locking pattern.

The implementation properly uses double-checked locking:

  1. Fast path with read lock (lines 64-70)
  2. Slow path acquires write lock (line 73)
  3. Double-checks under write lock (lines 74-79) to handle races
  4. Creates item only if still absent (lines 81-91)

The sizeHint initialization from historical fetchSize data enables efficient buffer pre-allocation for leaders.


94-102: LGTM: Efficient key generation with proper pooling.

The method correctly:

  • Acquires a digest from the pool
  • Generates both keys with resets between
  • Returns the digest to the pool

This minimizes allocations while ensuring clean state between hash computations.


106-141: LGTM: Clever two-key system balances uniqueness and grouping.

The design uses two keys with different purposes:

  • sfKey (lines 106-116): 100% unique by including DataSourceID, full input, and header hash (via extraKey). Prevents false deduplication.
  • fetchKey (lines 122-141): Intentionally less unique, grouping by DataSourceID and root fields only. Enables buffer size estimation for similar fetches.

The comments clearly explain this tradeoff, and the implementation correctly reflects the design intent.


146-164: LGTM: Proper completion signaling with size tracking.

The Finish method correctly:

  • Closes the loaded channel to signal all followers (line 147)
  • Removes the completed item from the cache (line 149)
  • Maintains rolling size estimates with a 50-entry rollover (lines 151-154) to prevent unbounded growth

The rollover logic resets to the average, maintaining a useful size hint without accumulating stale data.

v2/pkg/engine/resolve/loader.go (2)

492-501: Good: arena-safe JSON parsing.

Copying res.out into arena-allocated slice before ParseBytesWithArena correctly ties lifetimes and avoids segfaults.


407-421: Confirm SetArrayItem nil-arena semantics with astjson v1.0.0 source.

The code deliberately uses MustParseBytes and SetArrayItem(nil, ...) to avoid arena-based concurrency races in itemsData. However, available documentation does not clearly confirm whether passing nil as the arena parameter is officially supported in astjson v1.0.0, or whether the resulting array can safely hold references to values allocated from other arenas during concurrent read-only access.

Verify against the astjson v1.0.0 source or repository documentation that this nil-arena pattern is supported and that cross-arena value composition is safe for your concurrent usage pattern.

Copy link
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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
v2/pkg/engine/resolve/loader.go (1)

1388-1397: Consider simplifying the keyGen lifecycle.

The defer-with-nil-guard pattern (lines 1391-1397, 1459) works correctly but adds cognitive complexity. Since the cleanup happens at a single exit point (line 1457), you could remove the defer and just call pool.Hash64.Put(keyGen) directly there.

Alternative pattern

Remove the defer block and set keyGen = nil after the Put at line 1457:

-	keyGen := pool.Hash64.Get()
-	defer func() {
-		if keyGen == nil {
-			return
-		}
-		pool.Hash64.Put(keyGen)
-	}()
+	keyGen := pool.Hash64.Get()

Then at line 1457-1459:

-	// not used anymore
 	pool.Hash64.Put(keyGen)
-	// setting to nil so that the defer func doesn't return it twice
-	keyGen = nil
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6cbfed0 and daa18e8.

📒 Files selected for processing (1)
  • v2/pkg/engine/resolve/loader.go (46 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
v2/pkg/engine/resolve/loader.go (6)
v2/pkg/engine/resolve/fetch.go (3)
  • PostProcessingConfiguration (115-131)
  • Fetch (19-26)
  • FetchInfo (345-366)
v2/pkg/engine/resolve/singleflight.go (1)
  • SingleFlight (9-15)
v2/pkg/engine/resolve/context.go (3)
  • RateLimitOptions (135-148)
  • Context (16-37)
  • ExecutionOptions (57-69)
v2/pkg/engine/resolve/inputtemplate.go (1)
  • SetInputUndefinedVariables (39-51)
v2/pkg/pool/hash64.go (1)
  • Hash64 (10-16)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)
  • WithHTTPClientSizeHint (142-144)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
🔇 Additional comments (7)
v2/pkg/engine/resolve/loader.go (7)

184-196: Excellent documentation on arena safety.

The warnings about thread-safety and lifecycle management are critical for preventing segfaults. The note about tying byte lifecycles to the arena (line 188-190) is especially important.


333-347: Arena allocation pattern correctly applied.

The initial slice allocation (lines 335-336) and propagation to selectItems demonstrates proper arena-aware refactoring.


493-498: Critical arena safety pattern correctly implemented.

This pattern (allocate bytes on arena, then parse with arena) is essential for preventing segfaults by tying lifecycles together, as documented in the field comments. Well done.


1582-1632: Single-flight deduplication is well-implemented.

The request deduplication logic properly:

  • Excludes mutations to avoid side-effect issues (lines 1590-1592)
  • Handles context cancellation (lines 1604-1608)
  • Manages shared response lifecycle (lines 1603-1616)
  • Tracks statistics for observability (lines 1598-1601)

597-620: Batch deduplication merge logic is correct.

The refactored batchStats structure (per-unique-batch-item targets) correctly handles deduplication: each unique response item (line 604) is merged into all its target locations (lines 605-617). This matches the new structure defined at lines 94-112.


1825-1841: Good defensive documentation about arena exclusion.

The comment (lines 1832-1834) explicitly documents why arena allocation isn't used here, preventing future attempts to "optimize" this code in ways that would cause segfaults.


1559-1569: Function implementation is correct; however, verify this API is part of complete functionality.

The new GetOperationTypeFromContext function has no callers in the codebase. While the context value is correctly set at line 1585, the retrieval function isn't used anywhere. The function comment indicates it's intended for the transport layer, but no transport-related changes or usages were found.

The function implementation itself is sound (proper nil handling and type assertion), but confirm this PR includes all necessary integration points or document if this is intentionally forward-looking infrastructure.

Copy link
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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
v2/pkg/engine/resolve/loader.go (1)

1415-1534: LGTM: Sophisticated batch deduplication with arena optimization.

The batch entity loading correctly:

  • Uses arena-backed buffers for temporary allocations (lines 1429-1431)
  • Implements hash-based deduplication (lines 1465-1470)
  • Critically copies *astjson.Value pointers off the arena into persistent storage (lines 1510-1516) before the arena is reset

Optional: Remove ineffectual assignment at line 1517.

The static analysis correctly identifies that batchStats = nil at line 1517 has no effect since batchStats is not used afterward. While harmless, you can remove it for cleaner code:

 	for i := range batchStats {
 		res.batchStats[i] = make([]*astjson.Value, len(batchStats[i]))
 		copy(res.batchStats[i], batchStats[i])
-		batchStats[i] = nil
 	}
-	batchStats = nil
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between daa18e8 and 0c0e1ce.

📒 Files selected for processing (1)
  • v2/pkg/engine/resolve/loader.go (48 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
v2/pkg/engine/resolve/loader.go (6)
v2/pkg/engine/resolve/fetch.go (8)
  • PostProcessingConfiguration (115-131)
  • FetchItemPathElement (77-81)
  • FetchItem (28-33)
  • DataSourceLoadTrace (374-388)
  • BatchEntityFetch (159-168)
  • BatchEntityFetch (193-195)
  • Fetch (19-26)
  • FetchInfo (345-366)
v2/pkg/engine/resolve/singleflight.go (1)
  • SingleFlight (9-15)
v2/pkg/engine/resolve/context.go (3)
  • RateLimitOptions (135-148)
  • Context (16-37)
  • ExecutionOptions (57-69)
v2/pkg/engine/resolve/inputtemplate.go (1)
  • SetInputUndefinedVariables (39-51)
v2/pkg/engine/resolve/resolve.go (1)
  • New (188-252)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)
  • WithHTTPClientSizeHint (142-144)
🪛 GitHub Check: Linters (1.25)
v2/pkg/engine/resolve/loader.go

[failure] 1517-1517:
ineffectual assignment to batchStats (ineffassign)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
🔇 Additional comments (23)
v2/pkg/engine/resolve/loader.go (23)

60-65: LGTM: Clean API boundary for response body access.

The transition from *bytes.Buffer to []byte storage with a public getter method provides a clear API boundary and aligns with the arena-based memory management approach.


95-132: LGTM: Well-documented struct enhancements.

The batchStats documentation clearly explains the deduplication mapping, and the new fields (singleFlightStats, tools) are appropriately added to support the single-flight and arena-based batch processing features.


175-186: LGTM: Excellent documentation on arena usage constraints.

The detailed comments on jsonArena clearly communicate critical safety constraints (thread safety, lifecycle management, byte allocation requirements). This documentation will help prevent segfaults and misuse.


225-230: LGTM: Proper pool resource cleanup.

The deferred cleanup ensures batchEntityTools are returned to the pool, with graceful nil handling.


293-329: LGTM: Consistent resource initialization and cleanup.

The pattern of result initialization with deferred pool cleanup (line 305) is applied consistently across all fetch types.


331-345: LGTM: Arena-based allocation for path selection.

The initial items slice is properly allocated on the arena (line 333) and the arena is consistently propagated to selectItems (line 342).


363-401: LGTM: Complete arena integration in item selection.

All slice allocations within selectItems now use arena-aware methods (AllocateSlice, SliceAppend), ensuring memory is tied to the arena lifecycle.


403-417: LGTM: Thread-safe array creation without arena.

The comment (lines 410-411) correctly explains why arena allocation isn't used here—itemsData can be called concurrently. The fresh MustParseBytes allocation avoids race conditions at the cost of efficiency.


488-503: LGTM: Critical arena lifecycle pattern correctly implemented.

Lines 491-495 demonstrate the essential pattern: allocating bytes on the arena before parsing them with ParseBytesWithArena. This ties lifecycles together and prevents segfaults, as warned in the jsonArena field documentation.


577-636: LGTM: Arena-aware merge operations.

All MergeValuesWithPath calls now correctly pass the arena as the first parameter (lines 577, 603, 624), and the batchStats iteration (lines 596-617) properly handles the new per-unique-item target structure.


644-664: LGTM: Clean refactor to return []byte.

The method now returns []byte (line 663) instead of modifying a buffer, providing a cleaner API that aligns with the arena-based architecture.


720-726: LGTM: Lazy initialization pattern with clear documentation.

The ensureErrorsInitialized call before appending (lines 720-723) is well-documented as a performance optimization. Similar comments throughout (lines 760-763, 1017-1020, etc.) maintain consistency.


737-767: LGTM: Comprehensive arena integration in error wrapping.

All JSON parsing and value manipulation in the wrap-mode error handling uses arena-aware methods (ParseWithArena, SetValue), maintaining memory safety.


809-821: LGTM: Arena-aware extension code injection.

The extension code setting correctly uses arena methods for creating string values (line 809) and objects (lines 812-814, 817-819).


899-949: LGTM: Arena-aware path rewriting.

The rewriteErrorPaths function signature correctly accepts an arena (line 899) and uses it for parsing the new path (line 941) and setting it (line 945).


1253-1292: LGTM: Single fetch loading with proper error handling.

The input rendering uses a regular buffer (line 1255) which is appropriate for temporary input construction. The error path (lines 1279-1281) correctly sets the error response and returns nil since the error is captured in the result.


1294-1371: LGTM: Entity fetch input preparation.

The entity fetch correctly uses temporary buffers (lines 1304-1305) for input construction, which is appropriate since the input is passed to the DataSource and not stored in the arena.


1373-1413: LGTM: Efficient batch processing tool pooling.

The batchEntityTools pooling mechanism properly encapsulates the xxhash digest, deduplication map, and arena (lines 1373-1377), with correct reset logic (lines 1379-1385) to prepare for reuse.


1590-1607: LGTM: Well-designed context key pattern.

The GetOperationTypeFromContext helper provides a clean way for the transport layer to check operation types, with sensible defaults (Query) and proper nil handling.


1609-1618: LGTM: Clean header extraction helper.

The helper properly handles nil checks and encapsulates the logic for retrieving headers and their hash for single-flight deduplication.


1620-1682: LGTM: Single-flight deduplication correctly implemented.

The implementation properly:

  • Excludes mutations to avoid side-effect issues (line 1629)
  • Handles context cancellation (lines 1642-1646)
  • Manages shared response lifecycle (lines 1641-1669)
  • Delegates to loadByContextDirect for actual I/O (lines 1672-1682)

The header hash (extraKey) at line 1626 ensures requests with different headers are not deduplicated.


1709-1861: LGTM: Tracing properly integrated with single-flight.

The execution flow consistently routes through loadByContext (lines 1822, 1824, 1829), and tracing correctly captures single-flight statistics (lines 1836-1839) and output from the []byte response (lines 1840-1841).


1863-1879: LGTM: Appropriate non-arena usage with clear rationale.

The comments (lines 1870-1872) correctly explain why arena allocation isn't used in compactJSON—it's not a hot path and would require the parsed content to also be on the arena. The trade-off is reasonable for this tracing helper.

Copy link
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.

Actionable comments posted: 0

🧹 Nitpick comments (2)
v2/pkg/engine/resolve/loader.go (2)

1278-1281: Clarify error handling intent.

When input rendering fails, this assigns an error response to res.out and returns nil rather than propagating the error. While this appears intentional (encoding errors as GraphQL error responses rather than Go errors), the pattern is subtle.

Consider whether callers expect to see an error return value for this failure case, or if encoding it in the response body is the desired behavior for all scenarios.


720-726: Document ensureErrorsInitialized pattern.

The ensureErrorsInitialized() calls before every error append are critical (as the comments note, it's measurable for performance). However, the pattern is repeated ~8 times across error handling paths.

Consider extracting a helper like appendError(value *astjson.Value) that handles initialization and append, reducing duplication and making the pattern more maintainable.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c0e1ce and 8e3d0df.

📒 Files selected for processing (1)
  • v2/pkg/engine/resolve/loader.go (48 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
v2/pkg/engine/resolve/loader.go (6)
v2/pkg/engine/resolve/fetch.go (8)
  • PostProcessingConfiguration (115-131)
  • FetchItemPathElement (77-81)
  • FetchItem (28-33)
  • DataSourceLoadTrace (374-388)
  • BatchEntityFetch (159-168)
  • BatchEntityFetch (193-195)
  • Fetch (19-26)
  • FetchInfo (345-366)
v2/pkg/engine/resolve/singleflight.go (1)
  • SingleFlight (9-15)
v2/pkg/engine/resolve/context.go (2)
  • Context (16-37)
  • ExecutionOptions (57-69)
v2/pkg/engine/resolve/inputtemplate.go (1)
  • SetInputUndefinedVariables (39-51)
v2/pkg/engine/resolve/resolve.go (1)
  • New (188-252)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)
  • WithHTTPClientSizeHint (142-144)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
🔇 Additional comments (11)
v2/pkg/engine/resolve/loader.go (11)

175-186: LGTM – Excellent arena lifecycle documentation.

The warnings about thread-safety, lifecycle management, and the requirement to allocate parsed JSON on the same arena are critical for preventing segfaults. This documentation will help future maintainers avoid subtle memory bugs.


491-496: LGTM – Correct arena lifecycle pattern.

The explicit allocation and copy before parsing ensures the byte slice and parsed JSON share the same lifecycle, preventing the segfaults mentioned in the comments.


403-417: Verify nil arena usage in concurrent context.

Line 414 calls SetArrayItem(nil, i, item) without an arena. While this appears intentional (the comment notes concurrent calls, and arr is created via MustParseBytes rather than on the loader's arena), it differs from the arena-first pattern used elsewhere.

Confirm this exception is safe and consider adding a comment explaining why this particular allocation doesn't use the arena.


1373-1413: LGTM – Well-structured pool with proper lifecycle management.

The batchEntityTools pool correctly manages arena lifecycle: reset() clears both the arena and map, and the pool pattern ensures resources are reused efficiently. The arena scoping to batch operations is appropriate.


1428-1441: LGTM – Defensive cleanup prevents use-after-free.

Explicitly clearing batchStats pointers in the defer prevents memory corruption when the arena is reset after the function returns. This is a critical safety measure given the comment on line 1434.


1604-1615: LGTM – Well-designed public helper for operation type.

GetOperationTypeFromContext provides a clean API for the transport layer to check operation types, with sensible defaults (returning Query when context is nil or value is missing).


1636-1641: LGTM – Correct single-flight exclusion logic.

Properly excludes mutations from deduplication (avoiding side-effect issues) and respects the DisableRequestDeduplication flag. This addresses the critical concerns about request deduplication safety.


1878-1881: LGTM – Appropriate arena exclusion with clear rationale.

The explicit comment about not using arena here is valuable. Since compactJSON operates on trace/debug data (not hot path) and the input bytes aren't arena-allocated, this exclusion prevents segfaults while keeping the code simple.


1475-1495: LGTM – Efficient batch deduplication with correct arena usage.

The hash-based deduplication correctly identifies unique representations and accumulates merge targets. Using arena allocations during processing, then copying off the arena (lines 1521-1524) prevents memory corruption while maintaining efficiency.


577-624: LGTM – Consistent arena-aware merge operations.

All MergeValuesWithPath calls properly pass l.jsonArena, ensuring merged values are allocated on the same arena. The three merge paths (single item, batch stats, items) follow the same pattern.


1429-1430: LGTM – Arena buffers appropriate for batch processing.

Using arena.NewArenaBuffer for batch entity processing is appropriate given the complex deduplication logic and multiple passes over the data. The simpler single and entity fetches correctly use standard bytes.Buffer for transient rendering.

Copy link
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.

Actionable comments posted: 4

♻️ Duplicate comments (1)
v2/pkg/engine/resolve/loader.go (1)

1676-1677: Optional: defensive copy when caching shared response.

Storing res.out directly in item.response assumes immutability and unique backing arrays from DataSource. If that ever changes, followers may observe mutated data. Consider:

item.response = append([]byte(nil), res.out...)

Tagging as already discussed previously.

🧹 Nitpick comments (5)
v2/pkg/engine/resolve/inbound_request_singleflight.go (2)

17-20: Consider RWMutex for read-mostly path.

Followers only read the shard map before waiting. Switching to sync.RWMutex and using RLock for the lookup can reduce contention under load (mirrors subgraph single-flight). Optional.

Also applies to: 77-92


121-131: Minor: set Err before unlocking (clarity).

Setting req.Err under the lock (or immediately before close) avoids any doubt about visibility; channel close already provides the happens‑before. Optional.

v2/pkg/engine/resolve/subgraph_request_singleflight.go (1)

12-16: Remove unused cleanup channel.

cleanup is unused; drop the field and its initialization to reduce noise.

@@
 type SubgraphRequestSingleFlight struct {
 	shards  []singleFlightShard
 	xxPool  *sync.Pool
-	cleanup chan func()
 }
@@
 	s := &SubgraphRequestSingleFlight{
 		shards: make([]singleFlightShard, shardCount),
 		xxPool: &sync.Pool{
 			New: func() any {
 				return xxhash.New()
 			},
 		},
-		cleanup: make(chan func()),
 	}

Also applies to: 53-61

v2/pkg/engine/resolve/resolve.go (1)

85-89: Comment nit: fix description of inboundRequestSingleFlight.

It deduplicates inbound GraphQL requests (not subgraph requests).

v2/pkg/engine/resolve/loader.go (1)

1848-1853: Consider redacting sensitive headers in trace output (if present).

Input redaction is handled; Output may still include upstream headers under extensions.trace.response.headers. Consider stripping or masking standard sensitive headers before exposing traces.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e3d0df and f3f2a8e.

📒 Files selected for processing (6)
  • v2/pkg/engine/resolve/const.go (1 hunks)
  • v2/pkg/engine/resolve/context.go (3 hunks)
  • v2/pkg/engine/resolve/inbound_request_singleflight.go (1 hunks)
  • v2/pkg/engine/resolve/loader.go (48 hunks)
  • v2/pkg/engine/resolve/resolve.go (21 hunks)
  • v2/pkg/engine/resolve/subgraph_request_singleflight.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
v2/pkg/engine/resolve/context.go (1)
v2/pkg/engine/plan/planner.go (1)
  • IncludeQueryPlanInResponse (92-96)
v2/pkg/engine/resolve/loader.go (6)
v2/pkg/engine/resolve/fetch.go (8)
  • PostProcessingConfiguration (115-131)
  • FetchItemPathElement (77-81)
  • FetchItem (28-33)
  • DataSourceLoadTrace (374-388)
  • BatchEntityFetch (159-168)
  • BatchEntityFetch (193-195)
  • Fetch (19-26)
  • FetchInfo (345-366)
v2/pkg/engine/resolve/context.go (2)
  • Context (16-38)
  • ExecutionOptions (60-72)
v2/pkg/engine/resolve/inputtemplate.go (1)
  • SetInputUndefinedVariables (39-51)
v2/pkg/engine/resolve/resolve.go (1)
  • New (190-255)
v2/pkg/engine/resolve/datasource.go (1)
  • DataSource (10-13)
v2/pkg/engine/datasource/httpclient/nethttpclient.go (1)
  • WithHTTPClientSizeHint (142-144)
v2/pkg/engine/resolve/resolve.go (8)
v2/pkg/engine/resolve/arena.go (2)
  • ArenaPool (19-24)
  • NewArenaPool (38-42)
v2/pkg/engine/resolve/subgraph_request_singleflight.go (2)
  • SubgraphRequestSingleFlight (12-16)
  • NewSingleFlight (49-69)
v2/pkg/engine/resolve/inbound_request_singleflight.go (2)
  • InboundRequestSingleFlight (13-15)
  • NewRequestSingleFlight (26-39)
v2/pkg/engine/resolve/resolvable.go (2)
  • NewResolvable (72-80)
  • ResolvableOptions (65-70)
v2/pkg/engine/resolve/context.go (4)
  • Context (16-38)
  • Request (179-182)
  • ExecutionOptions (60-72)
  • SubgraphHeadersBuilder (44-50)
v2/pkg/engine/resolve/response.go (3)
  • GraphQLResponse (36-44)
  • GraphQLSubscription (12-16)
  • SubscriptionResponseWriter (69-75)
v2/pkg/engine/datasource/staticdatasource/static_datasource.go (3)
  • Source (72-72)
  • Source (74-76)
  • Source (78-80)
v2/pkg/pool/hash64.go (1)
  • Hash64 (10-16)
v2/pkg/engine/resolve/subgraph_request_singleflight.go (2)
v2/pkg/engine/resolve/resolve.go (1)
  • New (190-255)
v2/pkg/engine/resolve/fetch.go (3)
  • FetchItem (28-33)
  • Fetch (19-26)
  • FetchInfo (345-366)
v2/pkg/engine/resolve/inbound_request_singleflight.go (3)
v2/pkg/engine/resolve/context.go (4)
  • Context (16-38)
  • ExecutionOptions (60-72)
  • Request (179-182)
  • SubgraphHeadersBuilder (44-50)
v2/pkg/engine/resolve/response.go (1)
  • GraphQLResponse (36-44)
v2/pkg/ast/ast_operation_definition.go (2)
  • OperationType (11-11)
  • OperationTypeMutation (16-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
🔇 Additional comments (2)
v2/pkg/engine/resolve/const.go (1)

11-12: LGTM! Constants are properly used.

Both pipe and dot constants are actively used in v2/pkg/engine/resolve/subgraph_request_singleflight.go (lines 152 and 158 respectively) for hash computation during request deduplication. The additions follow the established pattern and are correctly integrated.

v2/pkg/engine/resolve/context.go (1)

37-50: LGTM: header hashing API + VariablesHash + Request.ID(uint64).

Interfaces and fields align with new dedup paths; helper HeadersForSubgraphRequest is clear.

Ensure all call sites updated for Request.ID type switch to uint64.

Also applies to: 52-58, 60-72, 179-182

Copy link
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.

Actionable comments posted: 1

🧹 Nitpick comments (3)
v2/pkg/engine/resolve/arena_test.go (3)

71-125: Document the specific bug being fixed.

The test name and comments reference a bug fix, but the actual bug and its root cause aren't documented. Consider adding a comment explaining:

  • What the original bug was (e.g., "items were skipped during pool traversal when weak pointers were collected")
  • Why the safeguard processed < numItems*2 is set to 2x
  • What symptoms users would have seen

This will help future maintainers understand why this test exists and what regression it prevents.


127-157: Consider making peak value assertions more flexible.

Line 138 asserts peak1 == 5, which assumes the arena allocates exactly the string length with no overhead. This assertion is fragile—if the underlying go-arena implementation changes its allocation strategy or adds padding, the test will break even though the ArenaPool logic is correct.

Consider either:

  • Removing the exact peak assertion (the test already validates size tracking)
  • Using a range assertion like assert.GreaterOrEqual(t, peak1, 5) to allow for allocation overhead

214-261: Consider reducing coupling to implementation details.

This test validates very specific internal behavior (window size of 50, exact division logic, count reset). While thorough, it's tightly coupled to the implementation, making it fragile:

  • Changes to the window size constant would require test updates
  • The exact arithmetic (lines 244-248) could break with algorithm tweaks

Consider:

  • Testing observable behavior instead (e.g., "size estimates converge after many operations")
  • Making magic values like 50 discoverable from the ArenaPool API if they're part of the contract
  • Adding a comment explaining why this specific window behavior is critical to test

That said, if the window size and reset logic are part of the performance contract, this level of detail may be justified.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57e688c and aa789e0.

📒 Files selected for processing (3)
  • v2/pkg/astvisitor/visitor.go (5 hunks)
  • v2/pkg/engine/resolve/arena.go (1 hunks)
  • v2/pkg/engine/resolve/arena_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • v2/pkg/engine/resolve/arena.go
🧰 Additional context used
🧬 Code graph analysis (1)
v2/pkg/engine/resolve/arena_test.go (1)
v2/pkg/engine/resolve/arena.go (2)
  • NewArenaPool (38-42)
  • ArenaPoolItem (33-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
  • GitHub Check: Build and test (go 1.25 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.25 / windows-latest)
🔇 Additional comments (10)
v2/pkg/engine/resolve/arena_test.go (5)

13-19: LGTM!

The initialization test correctly verifies that a new ArenaPool is created with empty internal structures.


21-35: LGTM!

The test correctly verifies that acquiring from an empty pool creates a new usable arena and doesn't leave artifacts in the pool.


37-69: LGTM!

The test effectively validates the release-acquire cycle, including verification that arenas are properly reset between uses.


159-177: LGTM!

The test appropriately validates both default and usage-based size calculations with reasonable flexibility on line 176.


179-212: LGTM!

The test correctly validates that multiple items can be pooled and subsequently re-acquired.

v2/pkg/astvisitor/visitor.go (5)

8-9: LGTM: Arena package import added.

The import is properly positioned and necessary for the arena-based memory management integration.


99-100: LGTM: Arena field added to Walker struct.

The arena field is properly defined as an unexported field for internal memory management. The placement at the end of the struct is appropriate.


131-134: LGTM: Arena properly reset in Release.

The nil check is defensive and correct since the arena is lazily initialized. Resetting the arena before returning the Walker to the pool ensures proper memory management across reuses.


1837-1837: LGTM: Arena-based slice allocation in walkSelectionSet.

The use of arena.SliceAppend to create a snapshot of selection references is an effective optimization. This reduces heap allocations in the hot path of AST traversal while maintaining correct semantics for detecting selection set changes during iteration.


1380-1384: No changes needed — buffer sizing is appropriate for the AST visitor workload.

The 64-byte minimum buffer size is a deliberate design choice for the visitor's allocation patterns. Unlike the larger 1024-byte buffers used for heavier workloads (like batch loading), the AST visitor handles many small allocations (slices of selection references). The monotonic arena grows on demand and retains the larger size across subsequent Reset() calls, making the small initial size efficient without incurring repeated resizing overhead.

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