diff --git a/quarkus/dist/src/main/content/bin/kc.bat b/quarkus/dist/src/main/content/bin/kc.bat index bda2ac5bc1..c9c7ad5721 100644 --- a/quarkus/dist/src/main/content/bin/kc.bat +++ b/quarkus/dist/src/main/content/bin/kc.bat @@ -165,7 +165,8 @@ if "x%JAVA%" == "x" ( set CLASSPATH_OPTS="%DIRNAME%..\lib\quarkus-run.jar" -set JAVA_RUN_OPTS=%JAVA_OPTS% -Dkc.home.dir="%DIRNAME%.." -Djboss.server.config.dir="%DIRNAME%..\conf" -Dkeycloak.theme.dir="%DIRNAME%..\themes" %SERVER_OPTS% -cp %CLASSPATH_OPTS% io.quarkus.bootstrap.runner.QuarkusEntryPoint %CONFIG_ARGS% +rem The property 'java.util.concurrent.ForkJoinPool.common.threadFactory' is set here, as a Java Agent or enabling JMX might initialize the factory before Quarkus can set the property in JDK21+. +set JAVA_RUN_OPTS=-Djava.util.concurrent.ForkJoinPool.common.threadFactory=io.quarkus.bootstrap.forkjoin.QuarkusForkJoinWorkerThreadFactory %JAVA_OPTS% -Dkc.home.dir="%DIRNAME%.." -Djboss.server.config.dir="%DIRNAME%..\conf" -Dkeycloak.theme.dir="%DIRNAME%..\themes" %SERVER_OPTS% -cp %CLASSPATH_OPTS% io.quarkus.bootstrap.runner.QuarkusEntryPoint %CONFIG_ARGS% set OPTIMIZED_OPTION=--optimized set HELP_LONG_OPTION=--help diff --git a/quarkus/dist/src/main/content/bin/kc.sh b/quarkus/dist/src/main/content/bin/kc.sh index 597647767c..5b3d53324a 100644 --- a/quarkus/dist/src/main/content/bin/kc.sh +++ b/quarkus/dist/src/main/content/bin/kc.sh @@ -157,7 +157,8 @@ esceval_args() { JAVA_RUN_OPTS=$(echo "$JAVA_OPTS" | xargs printf '%s\n' | esceval_args) -JAVA_RUN_OPTS="$JAVA_RUN_OPTS $SERVER_OPTS -cp $CLASSPATH_OPTS io.quarkus.bootstrap.runner.QuarkusEntryPoint ${CONFIG_ARGS#?}" +# The property 'java.util.concurrent.ForkJoinPool.common.threadFactory' is set here, as a Java Agent or enabling JMX might initialize the factory before Quarkus can set the property in JDK21+. +JAVA_RUN_OPTS="-Djava.util.concurrent.ForkJoinPool.common.threadFactory=io.quarkus.bootstrap.forkjoin.QuarkusForkJoinWorkerThreadFactory $JAVA_RUN_OPTS $SERVER_OPTS -cp $CLASSPATH_OPTS io.quarkus.bootstrap.runner.QuarkusEntryPoint ${CONFIG_ARGS#?}" if [ "$PRINT_ENV" = "true" ]; then echo "Using JAVA_OPTS: $JAVA_OPTS" diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java index 980684190b..9a4e05a64d 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java @@ -29,6 +29,7 @@ import static org.keycloak.quarkus.runtime.cli.command.Start.isDevProfileNotAllo import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ForkJoinPool; import jakarta.enterprise.context.ApplicationScoped; import org.keycloak.common.profile.ProfileException; @@ -56,6 +57,8 @@ import io.quarkus.runtime.annotations.QuarkusMain; public class KeycloakMain implements QuarkusApplication { public static void main(String[] args) { + ensureForkJoinPoolThreadFactoryHasBeenSetToQuarkus(); + System.setProperty("kc.version", Version.VERSION); List cliArgs = null; try { @@ -96,6 +99,25 @@ public class KeycloakMain implements QuarkusApplication { parseAndRun(cliArgs); } + /** + * Verify that the property for the ForkJoinPool factory set by Quarkus matches the actual factory. + * If this is not the case, the classloader for those threads is not set correctly, and for example loading configurations + * via SmallRye is unreliable. This can happen if a Java Agent or JMX initializes the ForkJoinPool before Java's main method is run. + */ + private static void ensureForkJoinPoolThreadFactoryHasBeenSetToQuarkus() { + // At this point, the settings from the CLI are no longer visible as they have been overwritten in the QuarkusEntryPoint. + // Therefore, the only thing we can do is to check if the thread pool class name is the same as in the configuration. + final String FORK_JOIN_POOL_COMMON_THREAD_FACTORY = "java.util.concurrent.ForkJoinPool.common.threadFactory"; + String sf = System.getProperty(FORK_JOIN_POOL_COMMON_THREAD_FACTORY); + //noinspection resource + if (!ForkJoinPool.commonPool().getFactory().getClass().getName().equals(sf)) { + Logger.getLogger(KeycloakMain.class).errorf("The ForkJoinPool has been initialized with the wrong thread factory. The property '%s' should be set on the Java CLI to ensure Java's ForkJoinPool will always be initialized with '%s' even if there are Java agents which might initialize logging or other capabilities earlier than the main method.", + FORK_JOIN_POOL_COMMON_THREAD_FACTORY, + sf); + throw new RuntimeException("The ForkJoinPool has been initialized with the wrong thread factory"); + } + } + private static void handleUsageError(String message) { handleUsageError(message, null); } diff --git a/quarkus/tests/integration/pom.xml b/quarkus/tests/integration/pom.xml index 1876cbaa77..bd2901ffa0 100644 --- a/quarkus/tests/integration/pom.xml +++ b/quarkus/tests/integration/pom.xml @@ -114,7 +114,7 @@ org.apache.maven.plugins maven-surefire-plugin - -Djdk.net.hosts.file=${project.build.testOutputDirectory}/hosts_file -XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError --add-opens=java.base/java.security=ALL-UNNAMED + -Djdk.net.hosts.file=${project.build.testOutputDirectory}/hosts_file -XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError --add-opens=java.base/java.security=ALL-UNNAMED -Djava.util.concurrent.ForkJoinPool.common.threadFactory=io.quarkus.bootstrap.forkjoin.QuarkusForkJoinWorkerThreadFactory ${kc.quarkus.tests.dist}