Skip to content

KTOR-9334 Fix unconfined dispatcher in handler context#5364

Merged
bjhham merged 3 commits intorelease/3.xfrom
bjhham/unconfined-user-context-netty-fix
Feb 18, 2026
Merged

KTOR-9334 Fix unconfined dispatcher in handler context#5364
bjhham merged 3 commits intorelease/3.xfrom
bjhham/unconfined-user-context-netty-fix

Conversation

@bjhham
Copy link
Copy Markdown
Contributor

@bjhham bjhham commented Feb 13, 2026

Subsystem
Server, Netty

Motivation
KTOR-9334 Coroutines in route handlers are dispatched with Dispatchers.Unconfined since 3.2.0

Solution
Returned the netty dispatcher to the user context. This was removed in 3.2.0 to allow for some flexibility with overriding via the application's coroutine context, but when unassigned, this would lead to the engine's dispatcher taking over. The engine's dispatcher is unconfined, so you can get some unpredictability in the thread usage.

Addendum
I created a separate ticket to introduce thread affinity for Jetty, because it's always behaved this way and would require some more invasive surgery:
KTOR-9345 Jetty engine call context is unconfined

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 13, 2026

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.

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bjhham/unconfined-user-context-netty-fix

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.

@bjhham bjhham force-pushed the bjhham/unconfined-user-context-netty-fix branch from 0217e48 to 6c10640 Compare February 13, 2026 12:53
@bjhham bjhham force-pushed the bjhham/unconfined-user-context-netty-fix branch from eca498d to d530325 Compare February 18, 2026 07:58
@bjhham bjhham requested a review from e5l February 18, 2026 07:59
@bjhham bjhham marked this pull request as ready for review February 18, 2026 08:02
Copy link
Copy Markdown
Member

@e5l e5l left a comment

Choose a reason for hiding this comment

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

please check the edge case before merging

LOG.error("Failed to dispatch", result.exceptionOrNull())
} catch (cause: Throwable) {
LOG.warn("Failed to dispatch; falling back to caller thread", cause)
block.run()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Retrying the execution block a second time can lead to unintended consequences (if there were no suspension points and the response/request was partially consumed/produced). Should we respond with an internal server error in this case and reject execution?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I noticed that this was the approach taken in the Jetty engine, and it seems to solve a few problems with structured concurrency according to the newer sustainability tests. The main problem is that the netty executor quits before the application jobs join, so we need some kinda fallback for the shutdown sequence. If I recall correctly, the executor is shut down early because it's tied to accepting new requests. I'll try playing around with the fallback logic here to see if a secondary dispatcher would make more sense, or if we can avoid dropping the netty executor early.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It just occurred to me that we can explicitly check if the executor is shutting down and delegate to the IO dispatcher in this case. I've updated the dispatcher to do this instead, and restored the previous behavior of logging the exception for more mysterious cases. This should avoid any blocking or problems with the shutdown sequence.

@bjhham bjhham enabled auto-merge (squash) February 18, 2026 16:03
@bjhham bjhham merged commit f148565 into release/3.x Feb 18, 2026
16 of 19 checks passed
@bjhham bjhham deleted the bjhham/unconfined-user-context-netty-fix branch February 18, 2026 16:11
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