From e93773b5b3e3cbd992c5170b905614b738160a96 Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Mon, 18 May 2026 20:23:31 +0200 Subject: [PATCH 1/2] CAMEL-23479: Fix camel.main.virtualThreadsEnabled not working Two bugs fixed: 1. Race condition: autoconfigure() set the system property after camelContext.build() could already have triggered ThreadType.current() and permanently cached PLATFORM via double-checked locking. 2. Programmatic case broken: main.configure().withVirtualThreadsEnabled(true) set mainConfigurationProperties but never set the system property, so ThreadType.current() always returned PLATFORM. Fix: add configureVirtualThreadsEarly() called before camelContext.build() that checks all config sources (programmatic, initialProperties, PropertiesComponent, ENV, JVM system props) and sets the system property early. Also adds ThreadType.enable() to directly set the cached value, overriding any value already cached before this point. Co-Authored-By: Claude Sonnet 4.6 --- .../apache/camel/main/BaseMainSupport.java | 55 ++++++++++++++++ .../camel/main/MainVirtualThreadsTest.java | 62 +++++++++++++++++++ .../camel/util/concurrent/ThreadType.java | 13 ++++ 3 files changed, 130 insertions(+) create mode 100644 core/camel-main/src/test/java/org/apache/camel/main/MainVirtualThreadsTest.java diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index edec9b123a514..06d44a4583a09 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -119,6 +119,7 @@ import org.apache.camel.util.SecurityUtils; import org.apache.camel.util.SecurityViolation; import org.apache.camel.util.StringHelper; +import org.apache.camel.util.concurrent.ThreadType; import org.apache.camel.vault.VaultConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -795,6 +796,58 @@ protected void configureMainListener(CamelContext camelContext) throws Exception } } + private void configureVirtualThreadsEarly(CamelContext camelContext) { + // Check programmatic configuration first (main.configure().withVirtualThreadsEnabled(true)) + boolean enabled = mainConfigurationProperties.isVirtualThreadsEnabled(); + + if (!enabled && mainConfigurationProperties.isAutoConfigurationEnabled()) { + // Check initial properties (main.addInitialProperty / main.setInitialProperties) + if (initialProperties != null) { + String val = initialProperties.getProperty(optionKey("camel.main.virtualThreadsEnabled")); + if (val != null) { + enabled = Boolean.parseBoolean(val); + } + } + + if (!enabled) { + // Load from PropertiesComponent (application.properties, etc.) + OrderedLocationProperties props = (OrderedLocationProperties) camelContext.getPropertiesComponent() + .loadProperties(name -> name.startsWith("camel."), MainHelper::optionKey); + String val = props.getProperty(optionKey("camel.main.virtualThreadsEnabled")); + if (val != null) { + enabled = Boolean.parseBoolean(val); + } + } + + if (!enabled && mainConfigurationProperties.isAutoConfigurationEnvironmentVariablesEnabled()) { + Properties envProps = MainHelper.loadEnvironmentVariablesAsProperties(new String[] { "camel.main." }); + String val = envProps.getProperty(optionKey("camel.main.virtualThreadsEnabled")); + if (val != null) { + enabled = Boolean.parseBoolean(val); + } + } + + if (!enabled && mainConfigurationProperties.isAutoConfigurationSystemPropertiesEnabled()) { + Properties sysProps = MainHelper.loadJvmSystemPropertiesAsProperties(new String[] { "camel.main." }); + String val = sysProps.getProperty("camel.main.virtualThreadsEnabled"); + if (val != null) { + enabled = Boolean.parseBoolean(val); + } + } + + if (enabled) { + mainConfigurationProperties.setVirtualThreadsEnabled(true); + } + } + + if (enabled) { + System.setProperty("camel.threads.virtual.enabled", "true"); + // Directly set the cached value so even if ThreadType.current() was already called + // and cached PLATFORM before this point, it is overridden to VIRTUAL + ThreadType.enable(); + } + } + protected void autoConfigurationStartupConditions( CamelContext camelContext, OrderedLocationProperties autoConfiguredProperties) throws Exception { @@ -958,6 +1011,8 @@ protected void postProcessCamelContext(CamelContext camelContext) throws Excepti configureRoutesLoader(camelContext); // configure custom main listeners configureMainListener(camelContext); + // configure virtual threads early before build() to avoid ThreadType DCL race + configureVirtualThreadsEarly(camelContext); // ensure camel context is build camelContext.build(); diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainVirtualThreadsTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainVirtualThreadsTest.java new file mode 100644 index 0000000000000..c846adf97a6f4 --- /dev/null +++ b/core/camel-main/src/test/java/org/apache/camel/main/MainVirtualThreadsTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.main; + +import java.lang.reflect.Field; + +import org.apache.camel.util.concurrent.ThreadType; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MainVirtualThreadsTest { + + @BeforeEach + @AfterEach + void resetThreadType() throws Exception { + Field field = ThreadType.class.getDeclaredField("current"); + field.setAccessible(true); + field.set(null, null); + System.clearProperty("camel.threads.virtual.enabled"); + } + + @Test + void testProgrammaticVirtualThreadsEnabled() { + Main main = new Main(); + main.configure().withVirtualThreadsEnabled(true); + main.start(); + try { + assertEquals(ThreadType.VIRTUAL, ThreadType.current()); + } finally { + main.stop(); + } + } + + @Test + void testInitialPropertyVirtualThreadsEnabled() { + Main main = new Main(); + main.addInitialProperty("camel.main.virtualThreadsEnabled", "true"); + main.start(); + try { + assertEquals(ThreadType.VIRTUAL, ThreadType.current()); + } finally { + main.stop(); + } + } +} diff --git a/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ThreadType.java b/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ThreadType.java index 074048e3d9a52..1731fe943a0cd 100644 --- a/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ThreadType.java +++ b/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ThreadType.java @@ -53,4 +53,17 @@ public static ThreadType current() { } return type; } + + /** + * Directly enables virtual threads by setting the cached type to {@code VIRTUAL}. + *

+ * This must be called before any thread pools are created, ideally during early bootstrap, to ensure the cached + * value reflects the configured intent regardless of the order in which {@link #current()} was previously invoked. + */ + public static void enable() { + synchronized (ThreadType.class) { + current = VIRTUAL; + } + LOG.info("The type of thread enabled is: {}", VIRTUAL); + } } From ad4fafc979f17d8d170ad5e61b8ffd1e202de3db Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Mon, 18 May 2026 21:29:22 +0200 Subject: [PATCH 2/2] CAMEL-23479: Remove redundant virtualThreadsEnabled autoConfigurationSingleOption configureVirtualThreadsEarly() now covers all configuration sources and runs before camelContext.build(), making the later autoConfigurationSingleOption call for virtualThreadsEnabled redundant. Co-Authored-By: Claude Sonnet 4.6 --- .../main/java/org/apache/camel/main/BaseMainSupport.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index 06d44a4583a09..df830d9473a9b 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -590,15 +590,6 @@ protected void autoconfigure(CamelContext camelContext) throws Exception { autoConfigurationPropertiesComponent(camelContext, autoConfiguredProperties); recorder.endStep(step); step = recorder.beginStep(BaseMainSupport.class, "autoConfigurationSingleOption", "Auto Configure"); - autoConfigurationSingleOption(camelContext, autoConfiguredProperties, "camel.main.virtualThreadsEnabled", - value -> { - boolean enabled = Boolean.parseBoolean(value); - mainConfigurationProperties.setVirtualThreadsEnabled(enabled); - if (enabled) { - System.setProperty("camel.threads.virtual.enabled", "true"); - } - return null; - }); autoConfigurationSingleOption(camelContext, autoConfiguredProperties, "camel.main.routesIncludePattern", value -> { mainConfigurationProperties.setRoutesIncludePattern(value);