Skip to content

fix: always close response body#1276

Merged
ysmolski merged 9 commits into
masterfrom
yury/eng-7913-engine-always-close-response-body
Aug 18, 2025
Merged

fix: always close response body#1276
ysmolski merged 9 commits into
masterfrom
yury/eng-7913-engine-always-close-response-body

Conversation

@ysmolski

@ysmolski ysmolski commented Aug 14, 2025

Copy link
Copy Markdown
Contributor

Close properly body in the "newWSConnectionHandler" and "dial" functions.

Bonus part:

  • Fix leak in subscriptionClient.dial/newWSConnectionHandler.
  • Rename local "connectionInitMessage" var to avoid collision with global var.
  • Fixed 1st flaky test in TestClientToSubgraphPingPong and improved runtime of both tests

Summary by CodeRabbit

  • Bug Fixes

    • Improved WebSocket connection cleanup and upgrade handling to prevent resource leaks, drain upgrade responses for connection reuse, and make subscription initialization/acknowledgement more robust.
  • Tests

    • Added parameterized tests for many WebSocket upgrade failure scenarios and invalid accept headers.
    • Strengthened tests to explicitly close HTTP responses, validate ping/pong and payload timing, and tighten unsubscribe/server-completion checks.

@coderabbitai

coderabbitai Bot commented Aug 14, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Drains and closes HTTP upgrade response bodies and defers net.Conn closure on dial/init errors; consolidates connection-init message handling and error propagation; adds tests for WebSocket upgrade failures and invalid Sec-WebSocket-Accept; fixes test response-body leaks. No public API signature changes.

Changes

Cohort / File(s) Summary
Subscription client — dial & init cleanup
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go
Defer-close underlying net.Conn on initialization errors, use a local initMsg for connection-init payload mutations and write, unify err assignment for SetWriteDeadline/waitForAck flows, and drain+close upgrade response bodies on non-101 or invalid Sec-WebSocket-Accept before returning errors. No exported signature changes.
Subscription client tests — ping/pong & new failure tests
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go
Add encoding/base64 import; introduce synchronization channels and tighter timing/logging in TestClientToSubgraphPingPong; add TestWebSocketUpgradeFailures (various HTTP upgrade failure responses) and TestInvalidWebSocketAcceptKey (missing/malformed/invalid Sec-WebSocket-Accept) using httptest servers and asserting UpgradeRequestError details.
Datasource tests — response body cleanup
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go
Close resp.Body (with require.NoError) after httpClient.Do(req) in TestSubscriptionSource_Start and TestSubscription_GTWS_SubProtocol to prevent resource leaks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch yury/eng-7913-engine-always-close-response-body

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ysmolski ysmolski marked this pull request as draft August 14, 2025 15:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go (2)

8349-8354: Prefer t.Cleanup/defer to guarantee body closure even on assertion failures

Closing the body is correct. To ensure it’s always closed even if the preceding require fails (or future changes add early returns), register the close with t.Cleanup right after the Do() succeeds.

-        resp, err := httpClient.Do(req)
-        require.NoError(t, err)
-        require.Equal(t, http.StatusOK, resp.StatusCode)
-        require.NoError(t, resp.Body.Close())
+        resp, err := httpClient.Do(req)
+        require.NoError(t, err)
+        t.Cleanup(func() { require.NoError(t, resp.Body.Close()) })
+        require.Equal(t, http.StatusOK, resp.StatusCode)

8477-8482: Mirror the cleanup pattern for consistent, robust resource handling

Same comment as above: use t.Cleanup (or defer) so the response body is closed even if an assertion aborts the test.

-        resp, err := httpClient.Do(req)
-        require.NoError(t, err)
-        require.Equal(t, http.StatusOK, resp.StatusCode)
-        require.NoError(t, resp.Body.Close())
+        resp, err := httpClient.Do(req)
+        require.NoError(t, err)
+        t.Cleanup(func() { require.NoError(t, resp.Body.Close()) })
+        require.Equal(t, http.StatusOK, resp.StatusCode)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1020056 and 9fad3d5.

📒 Files selected for processing (2)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go (2 hunks)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (1 hunks)
⏰ 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.23 / windows-latest)
  • GitHub Check: Build and test (go 1.23 / ubuntu-latest)

Comment thread v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go Outdated
This prevents memory leaks.
@ysmolski ysmolski force-pushed the yury/eng-7913-engine-always-close-response-body branch from 9fad3d5 to 9b0acbc Compare August 14, 2025 15:14
@ysmolski ysmolski marked this pull request as ready for review August 14, 2025 15:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (1)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (1)

489-548: Fix deferred-close guard: connection can leak on invalid subprotocol return

The deferred close relies on the local variable err to be non-nil. However, the default branch in the subprotocol switch returns a new error literal without assigning it to err. In that path, the deferred close won't run and the TCP connection leaks.

Concretely, the default branch around Line 547 returns without touching err. Assign the error to err before returning so the defer fires.

Apply this diff to make the deferred close effective for that path:

 switch subProtocol {
 case ProtocolGraphQLWS:
   return newGQLWSConnectionHandler(requestContext, engineContext, conn, options, updater, c.log), nil
 case ProtocolGraphQLTWS:
   return newGQLTWSConnectionHandler(requestContext, engineContext, conn, options, updater, c.log), nil
 default:
-  return nil, NewInvalidWsSubprotocolError(subProtocol)
+  err = NewInvalidWsSubprotocolError(subProtocol)
+  return nil, err
 }

Optional (safer): Convert the function to use a named return error and check that in the defer to cover all return sites without manual assignments.

🧹 Nitpick comments (2)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (2)

591-597: Reconsider closing Transport-owned net.Conn on Do error

When httpClient.Do(req) returns an error, net/http’s Transport is responsible for connection lifecycle. Closing info.Conn yourself can fight the Transport (e.g., racing with its own teardown or preventing reuse in some edge cases). It’s generally safer to let Transport manage it.

Apply this diff to leave connection management to net/http:

 upgradeResponse, err := c.httpClient.Do(req)
 if err != nil {
-  if conn != nil {
-    conn.Close()
-  }
   return nil, "", err
 }

If you’ve observed a concrete resource leak without this manual Close, please share details; otherwise I recommend relying on Transport here.


599-606: Optionally drain non-101 response before closing to enable connection reuse

For non-upgrade responses, draining the body before Close helps net/http reuse the TCP connection. This is optional but can improve performance for repeated attempts.

Apply this diff:

 // On failed upgrade, we close the connection here without transferring ownership to the callee.
 if upgradeResponse.StatusCode != http.StatusSwitchingProtocols {
-  upgradeResponse.Body.Close()
+  // Drain to EOF to allow connection reuse by net/http.
+  _, _ = io.Copy(io.Discard, upgradeResponse.Body)
+  upgradeResponse.Body.Close()
   return nil, "", &UpgradeRequestError{
     URL:        u,
     StatusCode: upgradeResponse.StatusCode,
   }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9fad3d5 and 8ea26f4.

📒 Files selected for processing (2)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go (2 hunks)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (1)
pkg/execution/datasource_config.go (1)
  • Header (353-356)
⏰ 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). (3)
  • GitHub Check: Build and test (go 1.23 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.23 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.23 / windows-latest)
🔇 Additional comments (3)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (3)

509-527: Init message construction is correct and avoids mutating shared state

Using getConnectionInitMessage and then overlaying InitialPayload and Extensions on the local initMsg is clean and avoids mutating the global template. Good use of jsonparser.Set.


529-539: Error propagation style LGTM

Switching to explicit err assignment before returns keeps the defer-based cleanup reliable on failure paths. No issues here.


610-612: LGTM: close on invalid Sec-WebSocket-Accept

Closing the body on handshake validation failure correctly tears down the upgraded TCP connection and prevents leaks.

@ysmolski ysmolski force-pushed the yury/eng-7913-engine-always-close-response-body branch from ea888fa to fb7b556 Compare August 14, 2025 16:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (1)

509-527: initMsg rename avoids shadowing; add a copy to avoid aliasing the global buffer

getConnectionInitMessage can return the global connectionInitMessage slice. Mutating initMsg via jsonparser.Set could share the underlying array if capacity permits. Safer to copy before any Set calls.

Apply this minimal copy after retrieving initMsg:

 initMsg, err := c.getConnectionInitMessage(requestContext, options.URL, options.Header)
 if err != nil {
   return nil, err
 }
 
+// Ensure we don't accidentally mutate a shared/global backing array.
+initMsg = append([]byte(nil), initMsg...)
 
 if len(options.InitialPayload) > 0 {
   initMsg, err = jsonparser.Set(initMsg, options.InitialPayload, "payload")
   if err != nil {
     return nil, err
   }
 }
 
 if options.Body.Extensions != nil {
   initMsg, err = jsonparser.Set(initMsg, options.Body.Extensions, "payload", "extensions")
   if err != nil {
     return nil, err
   }
 }

Alternatively, modify getConnectionInitMessage to always return a fresh copy when no payload is supplied:

func (c *subscriptionClient) getConnectionInitMessage(ctx context.Context, url string, header http.Header) ([]byte, error) {
	if c.onWsConnectionInitCallback == nil {
		return append([]byte(nil), connectionInitMessage...), nil
	}
	// ... and likewise when len(payload) == 0
}
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (1)

2348-2461: Great coverage of upgrade failure matrix; consider asserting URL on error (nit)

Table-driven tests cover a wide range of non-101 responses and assert the custom error type. As a minor enhancement, also assert that UpgradeRequestError.URL is set to the HTTP URL used internally (http://...) to lock in behavior.

Example assertion (after ErrorAs):

  • require.Contains(t, upgradeErr.URL, "http://")
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ea888fa and bc2d0a7.

📒 Files selected for processing (2)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (4 hunks)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (2)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (2)
  • WithLogger (122-126)
  • UpgradeRequestError (479-482)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go (2)
  • GraphQLSubscriptionOptions (1928-1941)
  • GraphQLBody (1943-1948)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (3)
v2/pkg/engine/datasource/httpclient/httpclient.go (1)
  • URL (20-20)
pkg/execution/datasource_config.go (1)
  • Header (353-356)
v2/pkg/graphqlerrors/errors.go (1)
  • Extensions (141-143)
⏰ 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.23 / windows-latest)
  • GitHub Check: Build and test (go 1.23 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.23 / windows-latest)
  • GitHub Check: Build and test (go 1.23 / ubuntu-latest)
🔇 Additional comments (10)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (7)

489-491: Good defensive pattern: track err for deferred cleanup

Declaring a local err to coordinate deferred cleanup is correct and keeps control flow explicit.


501-508: Leak prevention on post-dial failures looks correct

Deferring conn.Close() only when err != nil ensures ownership is released on all init failures without impacting the success path.


528-536: Write deadline and error propagation are correct

Setting a write deadline before init and propagating errors via the shared err variable is consistent and robust.


537-539: Ack wait handling is correct

waitForAck usage and error handling look good and consistent with the read/write timeout model.


591-595: Accurate note on httptrace semantics

Comment correctly documents GotConn not being invoked on Do error, clarifying why conn might be nil.


597-603: Correct: drain and close body on non-101 to enable reuse

Draining to EOF and closing the body on failed upgrade avoids leaks and enables connection reuse by net/http Transport.


611-614: Correct: drain and close on invalid Sec-WebSocket-Accept

Ensures the Transport can reuse/tear down the connection properly on handshake validation failure.

v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (3)

5-5: Import addition is appropriate

encoding/base64 is needed for malformed accept-key scenarios; good addition.


2016-2018: Flakiness reduction after ping is sensible

Brief sleep after receiving ping gives the server time to push an additional payload; pragmatic fix for timing-sensitive test.


2463-2557: Good negative tests for Sec-WebSocket-Accept handling

Validates missing, malformed, and incorrect accept headers. The HIJACK+close ensures the client doesn’t hang on 101 paths. Looks solid.

@ysmolski ysmolski force-pushed the yury/eng-7913-engine-always-close-response-body branch from ecbe56f to e458547 Compare August 15, 2025 10:19
Comment thread v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go Outdated
Comment thread v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go Outdated
Comment thread v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go Outdated
Comment thread v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go Outdated
@ysmolski ysmolski requested a review from endigma August 15, 2025 14:36

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (1)

1903-1971: Avoid closing signaling channels; use buffered + non-blocking send to prevent panics on multiple pings

In the server loop, if the client sends more than one ping before the client’s complete arrives, closing pingReceived again will panic ("close of closed channel"). Same for payloadSend if multiple sends happen. Prefer a buffered chan with a non-blocking send (or sync.Once) instead of close.

Apply this diff to make signaling idempotent and safe:

-        pingReceived := make(chan struct{}) // closed when the server receives a ping
-        payloadSend := make(chan struct{})  // closed when the server sends a payload
+        // buffered channels + non-blocking send to avoid double-close panics if events repeat
+        pingReceived := make(chan struct{}, 1) // signaled when the server receives a ping
+        payloadSend := make(chan struct{}, 1)  // signaled when the server sends a payload

And replace closing with non-blocking sends when signaling:

-                    receivedPing = true
-                    close(pingReceived)
+                    receivedPing = true
+                    select {
+                    case pingReceived <- struct{}{}:
+                    default:
+                    }
...
-                    close(payloadSend)
+                    select {
+                    case payloadSend <- struct{}{}:
+                    default:
+                    }
♻️ Duplicate comments (1)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (1)

2021-2027: Nice: wait on payload signal instead of sleeping (addresses earlier flakiness)

Waiting for payloadSend before unsubscribing removes timing sensitivity and aligns with the previous review suggestion to replace sleeps with channels.

🧹 Nitpick comments (1)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (1)

2497-2503: "Correct length but wrong content" case isn't 20 bytes; craft a 20-byte value

Sec-WebSocket-Accept is a base64 of a 20-byte SHA-1. This case currently encodes a 27-byte string. It will still fail as invalid (so the test passes), but the case description doesn't match. Use a 20-byte value to reflect the intent.

Apply this diff:

-        {
-            name: "Correct length but wrong content",
-            acceptKeyHandler: func(challengeKey string) string {
-                return base64.StdEncoding.EncodeToString([]byte("wrong-content-here-20-bytes"))
-            },
-            expectError:   true,
-            errorContains: "invalid Sec-WebSocket-Accept",
-        },
+        {
+            name: "Correct length but wrong content",
+            acceptKeyHandler: func(challengeKey string) string {
+                // 20 bytes (not the SHA-1 of challengeKey+GUID), so length is correct but content is wrong
+                return base64.StdEncoding.EncodeToString([]byte("12345678901234567890"))
+            },
+            expectError:   true,
+            errorContains: "invalid Sec-WebSocket-Accept",
+        },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e458547 and 7e9f047.

📒 Files selected for processing (2)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (4 hunks)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (3)
v2/pkg/engine/resolve/context.go (2)
  • Request (148-151)
  • NewContext (153-160)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (3)
  • NewGraphQLSubscriptionClient (216-272)
  • WithLogger (122-126)
  • UpgradeRequestError (479-482)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go (2)
  • GraphQLSubscriptionOptions (1928-1941)
  • GraphQLBody (1943-1948)
⏰ 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). (3)
  • GitHub Check: Build and test (go 1.23 / windows-latest)
  • GitHub Check: Build and test (go 1.23 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.23 / windows-latest)
🔇 Additional comments (2)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (2)

5-5: Import looks necessary and scoped to new tests

The base64 import is used by TestInvalidWebSocketAcceptKey and is appropriate.


2149-2154: Good synchronization on pong reception

The explicit wait for pongReceived with timeout makes the test much less flaky.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (1)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (1)

2208-2211: Fix double-close of serverDone (panic risk)

serverDone is closed in a defer; closing it again in the error branch will panic. Remove the inner close.

-        defer func() {
-            _ = conn.Close(websocket.StatusNormalClosure, "server done")
-            close(serverDone)
-        }()
+        defer func() {
+            _ = conn.Close(websocket.StatusNormalClosure, "server done")
+            close(serverDone)
+        }()
@@
-                // Signal that the server is done (connection closed)
-                close(serverDone)
-                return // Exit handler goroutine
+                // Connection closed; defer will close serverDone
+                return // Exit handler goroutine

Also applies to: 2256-2257

♻️ Duplicate comments (2)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (2)

2433-2476: Capture tc per iteration to future-proof subtests

Range-var capture can bite if these subtests become parallel later. Shadow tc inside the loop.

-    for _, tc := range testCases {
-        t.Run(tc.name, func(t *testing.T) {
+    for _, tc := range testCases {
+        tc := tc
+        t.Run(tc.name, func(t *testing.T) {

2514-2570: Also capture tc per iteration here

Same reasoning as above; add a per-iteration capture.

-    for _, tc := range testCases {
-        t.Run(tc.name, func(t *testing.T) {
+    for _, tc := range testCases {
+        tc := tc
+        t.Run(tc.name, func(t *testing.T) {
🧹 Nitpick comments (1)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (1)

1947-1958: Avoid fixed-iteration read loop; rely on condition and context timeout instead

Limiting to 5 iterations risks missing a late message under load. Prefer looping until both conditions are met or the read context times out.

Apply this diff:

-            // Process up to 5 messages or until we see both a ping and a complete
-            for i := 0; i < 5; i++ {
-                if receivedPing && receivedComplete {
-                    break
-                }
-
-                _, data, err = conn.Read(readCtx)
+            // Process messages until we see both a ping and a complete or the read context times out
+            for !(receivedPing && receivedComplete) {
+                _, data, err = conn.Read(readCtx)
                 if err != nil {
                     // Connection closed or timeout
                     t.Logf("Connection read ended: %v", err)
                     break
                 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7e9f047 and 6da69f5.

📒 Files selected for processing (1)
  • v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (9 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (3)
v2/pkg/engine/resolve/context.go (3)
  • Context (16-35)
  • Request (148-151)
  • NewContext (153-160)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client.go (3)
  • NewGraphQLSubscriptionClient (216-272)
  • WithLogger (122-126)
  • UpgradeRequestError (479-482)
v2/pkg/engine/datasource/graphql_datasource/graphql_datasource.go (2)
  • GraphQLSubscriptionOptions (1928-1941)
  • GraphQLBody (1943-1948)
⏰ 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). (3)
  • GitHub Check: Build and test (go 1.23 / ubuntu-latest)
  • GitHub Check: Build and test (go 1.23 / windows-latest)
  • GitHub Check: Build and test (go 1.23 / windows-latest)
🔇 Additional comments (1)
v2/pkg/engine/datasource/graphql_datasource/graphql_subscription_client_test.go (1)

1902-1906: Event-driven sync over sleeps: nice improvement

Using pingReceived/payloadSend channels with non-blocking sends removes timing flakiness and avoids double-close/panic risks. This will make the test more deterministic and faster.

Also applies to: 1966-1977

@StarpTech StarpTech left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@ysmolski ysmolski merged commit 9069cc9 into master Aug 18, 2025
13 checks passed
@ysmolski ysmolski deleted the yury/eng-7913-engine-always-close-response-body branch August 18, 2025 16:17
ysmolski pushed a commit that referenced this pull request Aug 22, 2025
🤖 I have created a release *beep* *boop*
---


##
[2.0.0-rc.221](v2.0.0-rc.220...v2.0.0-rc.221)
(2025-08-21)


### Bug Fixes

* always close response body
([#1276](#1276))
([9069cc9](9069cc9))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- Chores
- Bumped v2 release candidate to 2.0.0-rc.221; other version fields
unchanged. No functional changes included in this PR.

- Documentation
- Updated the changelog with a new entry for 2.0.0-rc.221, noting a bug
fix: always close the response body. Included comparison details to the
previous release candidate for transparency on what changed.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@coderabbitai coderabbitai Bot mentioned this pull request Sep 5, 2025
3 tasks
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.

3 participants