Skip to content

CAMEL-20199: Migrate TomcatReactiveExecutor from raw ThreadLocal to ContextValue - #25239

Open
gnodet wants to merge 1 commit into
apache:mainfrom
gnodet:CAMEL-20199/tomcat-reactive-contextvalue
Open

CAMEL-20199: Migrate TomcatReactiveExecutor from raw ThreadLocal to ContextValue#25239
gnodet wants to merge 1 commit into
apache:mainfrom
gnodet:CAMEL-20199/tomcat-reactive-contextvalue

Conversation

@gnodet

@gnodet gnodet commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Claude Code on behalf of @gnodet

Migrates TomcatReactiveExecutor from raw ThreadLocal with reflection-based cleanup to ContextValue.newThreadLocal(), achieving parity with DefaultReactiveExecutor which was already migrated.

What changed:

  • Replaced the TrackingThreadLocal inner class (which used fragile Thread.class.getDeclaredField("threadLocals") reflection) with ContextValue.newThreadLocal("CamelTomcatReactiveWorker", ...)
  • Removed ConcurrentHashMap<Thread, Field> thread tracking and the reflection-based clearWorkers() method
  • Shutdown now uses workers.remove() for clean cleanup
  • Refactored Worker class to use extracted helper methods (matching DefaultReactiveExecutor's structure)
  • Dropped public from test class/methods per JUnit 5 conventions

Why it matters for virtual threads:

  • The old reflection hack (Thread.threadLocals field access) does not work with virtual threads
  • With ContextValue, on JDK 25+ this automatically uses ScopedValue instead of ThreadLocal
  • Worker objects with ArrayDeque queues are on the routing hot path — with raw ThreadLocal and virtual threads, a new Worker was created per VT and never reused

Part of the umbrella issue CAMEL-20199 (Complete support of Virtual Threads).

Test plan

  • Existing SimpleMockTest passes (2 tests)
  • CI green

🤖 Generated with Claude Code

…ontextValue

Replace the reflection-based TrackingThreadLocal with ContextValue.newThreadLocal()
to match the pattern already used in DefaultReactiveExecutor. This removes the
fragile Thread.threadLocals reflection hack that breaks with virtual threads and
aligns the Tomcat reactive executor with the virtual thread compatibility work.

Changes:
- Replace TrackingThreadLocal with ContextValue.newThreadLocal()
- Remove ConcurrentHashMap thread tracking and reflection-based clearWorkers()
- Refactor Worker class to use extracted methods (matching DefaultReactiveExecutor)
- Use workers.remove() for clean shutdown instead of reflection
- Drop public from test class/methods per JUnit 5 conventions

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

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • components/camel-reactive-executor-tomcat

🔬 Scalpel shadow comparison — Scalpel: 9 tested, 29 compile-only — current: 9 all tested

Maveniverse Scalpel detected 38 affected modules (current approach: 9).

⚠️ Modules only in Scalpel (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

Skip-tests mode would test 9 modules (1 direct + 8 downstream), skip tests for 29 (generated code, meta-modules)

Modules Scalpel would test (9)
  • camel-jbang-mcp
  • camel-jbang-plugin-mcp
  • camel-jbang-plugin-route-parser
  • camel-jbang-plugin-tui
  • camel-jbang-plugin-validate
  • camel-launcher-container
  • camel-reactive-executor-tomcat
  • camel-yaml-dsl-validator
  • camel-yaml-dsl-validator-maven-plugin
Modules with tests skipped (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (38 modules)
  • Camel :: All Components Sync point
  • Camel :: Assembly
  • Camel :: Catalog :: CSimple Maven Plugin (deprecated)
  • Camel :: Catalog :: Camel Catalog
  • Camel :: Catalog :: Camel Report Maven Plugin
  • Camel :: Catalog :: Camel Route Parser
  • Camel :: Catalog :: Console
  • Camel :: Catalog :: Dummy Component
  • Camel :: Catalog :: Lucene (deprecated)
  • Camel :: Catalog :: Maven
  • Camel :: Catalog :: Suggest
  • Camel :: Component DSL
  • Camel :: Coverage
  • Camel :: Docs
  • Camel :: Endpoint DSL
  • Camel :: Endpoint DSL :: Support
  • Camel :: Integration Tests
  • Camel :: JBang :: Core
  • Camel :: JBang :: Integration tests
  • Camel :: JBang :: MCP
  • Camel :: JBang :: Main
  • Camel :: JBang :: Plugin :: Edit
  • Camel :: JBang :: Plugin :: Generate
  • Camel :: JBang :: Plugin :: Kubernetes
  • Camel :: JBang :: Plugin :: MCP
  • Camel :: JBang :: Plugin :: Route Parser
  • Camel :: JBang :: Plugin :: TUI
  • Camel :: JBang :: Plugin :: Testing
  • Camel :: JBang :: Plugin :: Validate
  • Camel :: Kamelet Main
  • Camel :: Launcher
  • Camel :: Launcher :: Container
  • Camel :: Reactive Executor :: Tomcat
  • Camel :: YAML DSL
  • Camel :: YAML DSL :: Deserializers
  • Camel :: YAML DSL :: Maven Plugins
  • Camel :: YAML DSL :: Validator
  • Camel :: YAML DSL :: Validator Maven Plugin

⚙️ View full build and test results

@gnodet
gnodet marked this pull request as ready for review July 30, 2026 09:37

@gnodet gnodet left a comment

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.

Looks good ✅

Clean, well-structured migration from fragile reflection-based ThreadLocal cleanup to ContextValue, achieving full parity with DefaultReactiveExecutor. The old reflection cleanup in clearWorkers() was actually broken (passed this instead of the ThreadLocal to ThreadLocalMap.remove), so this is both a modernization and a correctness improvement.

Notable behavioral fixes vs. the old code:

  • scheduleQueue() now correctly calls incrementPendingTasks(), fixing a stats accounting bug where pending counter could go negative
  • Statistics checking is now dynamic (rechecked per call) rather than cached at Worker construction time

One note on the PR description: it states "With ContextValue, on JDK 25+ this automatically uses ScopedValue instead of ThreadLocal." This is inaccurate for this specific usage — the code uses ContextValue.newThreadLocal(), which always uses ThreadLocal regardless of JDK version. Only ContextValue.newInstance() would use ScopedValue on JDK 25+. The newThreadLocal variant is necessary here because Workers need mutable state and remove() support, which ScopedValue does not provide.

CI passes on both JDK 17 and JDK 25.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code on behalf of Guillaume Nodet

@gnodet
gnodet requested review from davsclaus and orpiske July 31, 2026 05:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant