Skip to content

KTOR-8938 Inherit server coroutine context in WebSocket session - #5426

Merged
Leonid Stashevsky (e5l) merged 11 commits into
release/3.xfrom
claude/KTOR-8938-ws-context-inheritance
Apr 22, 2026
Merged

KTOR-8938 Inherit server coroutine context in WebSocket session#5426
Leonid Stashevsky (e5l) merged 11 commits into
release/3.xfrom
claude/KTOR-8938-ws-context-inheritance

Conversation

@e5l

Copy link
Copy Markdown
Member

Summary

  • Fixes KTOR-8938
  • WebSocketUpgrade.upgrade() was building the WebSocket session's coroutine context from only the engine dispatcher, dropping custom CoroutineContext.Elements from the server/call scope
  • Changed to use call.coroutineContext as the base context so user-provided elements are preserved while the engine dispatcher still overrides the dispatcher

Test plan

  • Added engine-level test testWebSocketSessionInheritsServerCoroutineContext in WebSocketEngineSuite that passes a custom context element via parentCoroutineContext and verifies it's accessible inside the webSocket handler's coroutineContext
  • All existing CIO WebSocket tests continue to pass
  • All existing WebSocket plugin tests continue to pass

🤖 Generated with Claude Code

Leonid Stashevsky (e5l) and others added 2 commits March 5, 2026 11:11
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The WebSocket session's coroutineContext was built solely from
the engine dispatcher, dropping custom CoroutineContext elements
from the server/call scope. Use call.coroutineContext as the base
so user-provided elements are preserved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9e67759f-4979-4089-a534-275a7ebd0e26

📥 Commits

Reviewing files that changed from the base of the PR and between 7a3594c and 33a2124.

📒 Files selected for processing (1)
  • ktor-server/ktor-server-test-suites/common/src/io/ktor/server/testing/suites/WebSocketEngineSuite.kt

📝 Walkthrough

Walkthrough

Recomposes coroutine contexts used during WebSocket and servlet upgrade flows (reorders and optionally includes parent Job), adds @OptIn(InternalCoroutinesApi::class) where needed, ensures webConnection.close() runs on cancellation via invokeOnCompletion(onCancelling = true), and adds tests for context propagation and cancellation.

Changes

Cohort / File(s) Summary
WebSocket Context Composition
ktor-server/ktor-server-plugins/ktor-server-websockets/common/src/io/ktor/server/websocket/WebSocketUpgrade.kt
Swap context composition to use call.coroutineContext + engineContext for RawWebSocket sessions instead of engineContext + optional Job, altering which Job/context elements are present.
Servlet-Jakarta Upgrade Flow
ktor-server/ktor-server-servlet-jakarta/jvm/src/io/ktor/server/servlet/jakarta/AsyncServlet.kt, ktor-server/ktor-server-servlet-jakarta/jvm/src/io/ktor/server/servlet/jakarta/ServletUpgrade.kt
When performing upgrade, conditionally include parent servlet Job into engineContext and use that context for upgrade; annotate init with @OptIn(InternalCoroutinesApi::class) and call upgradeJob.invokeOnCompletion(onCancelling = true) to close connection on cancellation.
Servlet Upgrade Flow
ktor-server/ktor-server-servlet/jvm/src/io/ktor/server/servlet/AsyncServlet.kt, ktor-server/ktor-server-servlet/jvm/src/io/ktor/server/servlet/ServletUpgrade.kt
Same changes as Jakarta variant: derive parent servlet Job and combine with engineContext for upgrade; add @OptIn(InternalCoroutinesApi::class) and use invokeOnCompletion(onCancelling = true).
WebSocket Test Suite
ktor-server/ktor-server-test-suites/common/src/io/ktor/server/testing/suites/WebSocketEngineSuite.kt
Add tests: testWebSocketSessionInheritsServerCoroutineContext and testWebSocketSessionCancelledOnServerStop; introduce CustomTestElement key and import kotlin.coroutines.*.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

bug

Suggested reviewers

  • bjhham
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: inheriting server coroutine context in WebSocket sessions to fix KTOR-8938.
Description check ✅ Passed The description covers all required template sections: subsystem (Server WebSocket), motivation (references KTOR-8938 and explains the problem), and solution (describes context inheritance approach). Test plan section demonstrates verification strategy.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/KTOR-8938-ws-context-inheritance
📝 Coding Plan
  • Generate coding plan for human review comments

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

❤️ Share

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

Leonid Stashevsky (e5l) and others added 6 commits March 5, 2026 13:16
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Strip the call's Job before merging its context, and restore
the original Job from the engine's processing coroutine. This
ensures custom context elements are inherited without changing
the WebSocket session's Job parent-child relationship.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use call.coroutineContext directly (with its Job) so that the
WebSocket session is a proper child of the server's coroutine
scope. This ensures cancellation propagates correctly from
server to WebSocket sessions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tomcat's servlet-based WebSocket upgrade creates an upgradeJob
with no parent connection to the server scope, so server stop
does not propagate cancellation to WebSocket sessions. This is
a pre-existing Tomcat limitation unrelated to this fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pass the servlet's SupervisorJob as parent for the upgrade handler's Job,
ensuring cancellation propagates when the servlet is destroyed. Use
onCancelling=true for WebConnection cleanup to break the deadlock between
blocked IO reads and Job completion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@e5l
Leonid Stashevsky (e5l) enabled auto-merge (squash) March 18, 2026 08:59
server.stop() calls runBlocking internally via runBlockingBridge,
which throws on JS and WASM platforms. Skip the test on those
targets with a runtime platform check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@ktor-server/ktor-server-servlet/jvm/src/io/ktor/server/servlet/ServletUpgrade.kt`:
- Around line 77-79: The class-level `@OptIn`(InternalCoroutinesApi::class) on
ServletUpgradeHandler is too broad; remove it from the class declaration and
instead annotate only the init override that calls
job.invokeOnCompletion(onCancelling = true) with
`@OptIn`(InternalCoroutinesApi::class). Update the ServletUpgradeHandler
declaration to keep `@InternalAPI` only, then add `@OptIn` on the override fun
init(session: HttpSession) (or the init method that contains
job.invokeOnCompletion(onCancelling = true)) so the opt-in scope is narrowed to
that specific usage.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 94707925-b427-4e64-a9bf-9e2e76fd31bd

📥 Commits

Reviewing files that changed from the base of the PR and between c6c1a5c and f9c952a.

📒 Files selected for processing (6)
  • ktor-server/ktor-server-plugins/ktor-server-websockets/common/src/io/ktor/server/websocket/WebSocketUpgrade.kt
  • ktor-server/ktor-server-servlet-jakarta/jvm/src/io/ktor/server/servlet/jakarta/AsyncServlet.kt
  • ktor-server/ktor-server-servlet-jakarta/jvm/src/io/ktor/server/servlet/jakarta/ServletUpgrade.kt
  • ktor-server/ktor-server-servlet/jvm/src/io/ktor/server/servlet/AsyncServlet.kt
  • ktor-server/ktor-server-servlet/jvm/src/io/ktor/server/servlet/ServletUpgrade.kt
  • ktor-server/ktor-server-test-suites/common/src/io/ktor/server/testing/suites/WebSocketEngineSuite.kt

Comment thread ktor-server/ktor-server-servlet/jvm/src/io/ktor/server/servlet/ServletUpgrade.kt Outdated
@e5l

Copy link
Copy Markdown
Member Author

Addressed CI feedback (iteration 1):

  • Fixed: testWebSocketSessionCancelledOnServerStop failing on JS/WASM — added runtime platform check to skip the test since server.stop() calls runBlocking internally via runBlockingBridge, which is unsupported on JS and WASM platforms.

Remaining failures (pre-existing, not caused by this PR):

…ethod

Move the annotation from class-level to the specific method that uses
invokeOnCompletion(onCancelling = true), in both servlet and servlet-jakarta
upgrade handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@e5l

Copy link
Copy Markdown
Member Author

Addressed review feedback:

  • Narrowed @OptIn(InternalCoroutinesApi::class) scope from class-level to the init method in both ServletUpgradeHandler variants (servlet and servlet-jakarta), as suggested by CodeRabbit

CI status after push:

@e5l

Copy link
Copy Markdown
Member Author

PR Review Iteration Summary

Review Comments

All review feedback has been addressed:

  • CodeRabbit: Narrowed @OptIn(InternalCoroutinesApi::class) scope from class-level to init method — addressed in commit 7a3594c. Confirmed resolved by reviewer.

CI Failures Analysis

All 6 CI failures are pre-existing on the release/3.x base branch and unrelated to this PR's WebSocket/servlet changes:

Test Platform Base branch? Issue
CurlHttp2Test.test protocol version is HTTP 2 mingwX64 Yes (since #3936) #5458
RateLimitTest.testRemovesUnusedRateLimitersOnRefill mingwX64 Yes (since #4072) #5471
TestApplicationTest.testStreamingResponse mingwX64 Yes (build 412003) #5472
HttpTimeoutTest.testGetRequestTimeoutWithSeparateReceivePerRequestAttributes linuxX64 Yes (build 412249) #5473
JettyEngineHttp2SustainabilityTest.testBigFile jvm Intermittent #5474
ContentTest.testEmptyContent macosX64 Yes (build 412588) #5470

Status

  • ✅ All review feedback addressed
  • ✅ No code changes needed — CI failures are all pre-existing base branch issues
  • ✅ Issues filed for all undocumented flaky tests

- Replace server.stop() with server.stopSuspend() as suggested by reviewer
- Remove JS/WASM platform check since stopSuspend doesn't use runBlocking
- Remove open modifier from test function (no longer needed for overrides)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@e5l

Copy link
Copy Markdown
Member Author

Addressed review feedback (iteration 2):

  • Fixed: Replaced server.stop(0, 0) with server.stopSuspend(0, 0) in testWebSocketSessionCancelledOnServerStop as requested by Osip Fatkullin (@osipxd)
  • Removed JS/WASM platform check since stopSuspend doesn't use runBlocking internally
  • Removed open modifier from test (no longer needed without platform-specific overrides)

CI status after push:

  • All JVM tests (Java 8, 11, 17, 21): passing
  • JavaScript on Chrome/Node.js: passing
  • WasmJS on Chrome/Node.js: passing (test now runs on WASM too)
  • Native Linux X64, macOS X64: passing
  • Native macOS Arm64: flaky testProxyHeaders failure (unrelated to this PR)
  • Native Windows X64: pre-existing failures (CurlHttp2Test, TCPSocketTest, UDPSocketTest, DependencyInjectionTest)
  • Build All Core: composite, fails due to above

@osipxd

Copy link
Copy Markdown
Member

Could you also check if this PR fixes this issue as well?

  • KTOR-4110 Websockets: Websocket handler doesn't support structured concurrency

@bjhham Bruce Hamilton (bjhham) 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 👍

@e5l

Copy link
Copy Markdown
Member Author

Osip Fatkullin (@osipxd), I checked and problem is different

@e5l
Leonid Stashevsky (e5l) merged commit 3acb8ea into release/3.x Apr 22, 2026
18 of 21 checks passed
@e5l
Leonid Stashevsky (e5l) deleted the claude/KTOR-8938-ws-context-inheritance branch April 22, 2026 05:57
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