Skip to content

fix(router): keep websockets + resolver alive for reused graph muxes - #2838

Merged
dkorittki merged 5 commits into
feat/split-configsfrom
dominik/eng-9520-router-preserve-websocket-connections-on-graph-server-swap
May 7, 2026
Merged

fix(router): keep websockets + resolver alive for reused graph muxes#2838
dkorittki merged 5 commits into
feat/split-configsfrom
dominik/eng-9520-router-preserve-websocket-connections-on-graph-server-swap

Conversation

@dkorittki

@dkorittki dkorittki commented May 7, 2026

Copy link
Copy Markdown
Contributor

Since we are now able to reuse feature flag and base graph muxes if their config parts haven't changed there is no reason to kill their websocket connections during a graph server swap. Unfortunately their contexts, which are used to actually close them, is tied to the graph server. So whenever the graph server context is canceled during graph server swap, all websocket connections across all muxes get closed regardless of wether their mux does not get cancelled.

The fix is to inherit the graph mux context from the router instead of the graph server. What that does is that websocket connections only get closed on graph mux shutdown, not on graph server shutdown. When a graph server shuts down it shuts down only those muxes, which actually need to be closed. This in turn means that only those websocket connections get closed, which need to get closed.

One important note: This pull request should not have an impact on customers not using the split config feature in the token. But it does have changes on their code path, since I changed the context inheritance. Just something to keep in mind during review.

Summary by CodeRabbit

  • Tests
    • Added integration test to validate WebSocket connection behavior during incremental configuration updates affecting specific server components
  • Refactor
    • Improved context lifecycle management across server components to enable proper resource cleanup and graceful shutdown during dynamic updates

Checklist

Open Source AI Manifesto

This project follows the principles of the Open Source AI Manifesto. Please ensure your contribution aligns with its principles.

Each graphMux now owns its context derived from the router context rather
than the graph server context. The executor (resolver event loop), websocket
middleware, subscription clients, and cache warmup closures all use the
per-mux context. Graph-server-scoped resources (connector, pubsub providers)
continue to use the graph server context, because they are graph server
resources.

On graph server shutdown, reused muxes are skipped entirely. Their context
stays alive so existing websocket connections and active subscriptions are
not interrupted. Recreated muxes have their context cancelled in
graphMux.Shutdown(), which terminates the resolver, websocket
handler, and upstream subscription connections.
@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b957b6c3-fca0-4c65-900a-fd23351f57c6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR refactors the graph server's context management from using shared borrowed contexts to a hierarchical model with server-scoped and per-mux contexts. The refactoring enables mux-level context cancellation during hot-reload. A new integration test validates that updating a single feature-flag mux correctly closes only its connections while preserving others.

Changes

Context Hierarchy Refactoring and Hot-Reload Test

Layer / File(s) Summary
Context Structure and Data Shape
router/core/graph_server.go
graphServer stores dedicated graphServerCtx and graphServerCancel; graphMux gains ctx and cancel fields to hold mux-scoped context pairs.
Mux Build Signatures and Creation
router/core/graph_server.go
buildGraphMux removes the external ctx parameter and creates graphMuxCtx from routerCtx internally. buildMultiGraphHandler signature no longer accepts ctx. Base graph mux creation uses the new server-scoped context.
Mux Context Usage in Operations
router/core/graph_server.go
Plugin host setup, executor build, cache warmup, persisted operation warmup, and WebSocket middleware are updated to use graphMuxCtx instead of external shared contexts.
Shutdown and Cancellation
router/core/graph_server.go
graphMux.Shutdown defers cancellation of its mux context before closing resources. graphServer.Shutdown defers cancellation of server context.
Integration Test for Hot-Reload Behavior
router-tests/protocol/config_hot_reload_test.go
New test TestConfigHotReloadGraphServerSwap validates that updating only the myff mux closes its WebSocket subscriptions while base graph and myff2 subscriptions remain open. Imports updated to include net/http for WebSocket header construction.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the main change: keeping websockets and resolvers alive for reused graph muxes during config hot-reload, which aligns with the PR's core objective of preserving websocket connections across graph server swaps.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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


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

@github-actions github-actions Bot added the router label May 7, 2026
@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-90074a3591edecc0a4c51cc27cdec4f44bfbc58f

@codecov

codecov Bot commented May 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.42857% with 1 line in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (feat/split-configs@6bf713b). Learn more about missing BASE report.

Files with missing lines Patch % Lines
router/core/graph_server.go 96.42% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@                  Coverage Diff                  @@
##             feat/split-configs    #2838   +/-   ##
=====================================================
  Coverage                      ?   65.80%           
=====================================================
  Files                         ?      256           
  Lines                         ?    26859           
  Branches                      ?        0           
=====================================================
  Hits                          ?    17674           
  Misses                        ?     7757           
  Partials                      ?     1428           
Files with missing lines Coverage Δ
router/core/graph_server.go 83.92% <96.42%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dkorittki

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@dkorittki dkorittki changed the title fix: keep websocket connections alive for reused graph muxes fix(router): keep websocket connections alive for reused graph muxes May 7, 2026
@dkorittki dkorittki changed the title fix(router): keep websocket connections alive for reused graph muxes fix(router): keep websockets + resolver alive for reused graph muxes May 7, 2026

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@router-tests/protocol/config_hot_reload_test.go`:
- Around line 625-630: Replace the direct websocket write call
conn.WriteJSON(...) used for the subscription handshake with the test helper
testenv.WSWriteJSON so the test benefits from the built-in retry/timeout
behavior; locate the call that constructs a testenv.WebSocketMessage (ID "1",
Type "subscribe", Payload ...) and change the invocation to
testenv.WSWriteJSON(t, conn, &testenv.WebSocketMessage{...}) while keeping the
same message fields and error assertions intact (remove the manual
require.NoError on conn.WriteJSON since WSWriteJSON already handles errors).

In `@router/core/graph_server.go`:
- Around line 185-189: The constructors currently create contexts and partial
resources (graphMuxCtx/graphMuxCancel in buildGraphMux and
graphServerCtx/graphServerCancel in newGraphServer) but return on error without
cleaning them up; add deferred rollback logic in both functions that, on non-nil
error return, cancels the corresponding context(s) and invokes any partial
resource cleanup (e.g., call mux.Shutdown()/ShutdownMux, close caches, metric
stores, pub/sub providers, connectors) so no mux-scoped work lingers; implement
the defer immediately after creating graphMuxCtx/graphMuxCancel and
graphServerCtx/graphServerCancel (and after any resource that needs explicit
Close) that checks if err != nil and then cancels and closes those resources
before returning.
- Around line 877-879: The Shutdown method on graphMux currently defers
s.cancel(), which delays cancelling the mux context until after
cache/metric/exporter shutdown; change it to call s.cancel() immediately at the
start of graphMux.Shutdown (i.e., invoke s.cancel() before shutting down caches,
metrics, exporters and websocket/resolver goroutines) so that subscriptions and
goroutines tied to the mux context are stopped as soon as shutdown begins.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bcbbfb2b-66bc-40fb-92dc-1f2deeb05fb1

📥 Commits

Reviewing files that changed from the base of the PR and between 6bf713b and 1aca0f1.

📒 Files selected for processing (2)
  • router-tests/protocol/config_hot_reload_test.go
  • router/core/graph_server.go

Comment on lines +625 to +630
err := conn.WriteJSON(&testenv.WebSocketMessage{
ID: "1",
Type: "subscribe",
Payload: []byte(`{"query":"subscription { currentTime { unixTime timeStamp }}"}`),
})
require.NoError(t, err)

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use testenv.WSWriteJSON for the subscribe handshake.

This is still the success path, so the new hot-reload test should use the websocket helper rather than conn.WriteJSON directly. That keeps the test aligned with the repo’s retry/deadline behavior and reduces flakes.

As per coding guidelines: "Use testenv.WSReadJSON and testenv.WSWriteJSON for WebSocket reads and writes in tests instead of conn.ReadJSON and conn.WriteJSON, as these helpers include retry logic with 2-second deadlines and exponential backoff."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router-tests/protocol/config_hot_reload_test.go` around lines 625 - 630,
Replace the direct websocket write call conn.WriteJSON(...) used for the
subscription handshake with the test helper testenv.WSWriteJSON so the test
benefits from the built-in retry/timeout behavior; locate the call that
constructs a testenv.WebSocketMessage (ID "1", Type "subscribe", Payload ...)
and change the invocation to testenv.WSWriteJSON(t, conn,
&testenv.WebSocketMessage{...}) while keeping the same message fields and error
assertions intact (remove the manual require.NoError on conn.WriteJSON since
WSWriteJSON already handles errors).

Comment thread router/core/graph_server.go
Comment thread router/core/graph_server.go Outdated
@dkorittki
dkorittki marked this pull request as ready for review May 7, 2026 13:17

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@Noroth Noroth 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

})
}

// TestConfigHotReloadReusedFeatureFlagMuxKeepsWebSocketsAlive verifies that after a

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.

Name doesn't match

@dkorittki
dkorittki merged commit 44be5ab into feat/split-configs May 7, 2026
34 checks passed
@dkorittki
dkorittki deleted the dominik/eng-9520-router-preserve-websocket-connections-on-graph-server-swap branch May 7, 2026 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants