Ensure that Java's ForkJoinPool is initialized with Quarkus' ThreadPoolFactory

Closes #30120

Signed-off-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
Alexander Schwartz 2024-06-04 18:34:26 +02:00 committed by Alexander Schwartz
parent 20368b35b9
commit 1b821f3267
4 changed files with 27 additions and 3 deletions

View file

@ -165,7 +165,8 @@ if "x%JAVA%" == "x" (
set CLASSPATH_OPTS="%DIRNAME%..\lib\quarkus-run.jar" 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 OPTIMIZED_OPTION=--optimized
set HELP_LONG_OPTION=--help set HELP_LONG_OPTION=--help

View file

@ -157,7 +157,8 @@ esceval_args() {
JAVA_RUN_OPTS=$(echo "$JAVA_OPTS" | xargs printf '%s\n' | 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 if [ "$PRINT_ENV" = "true" ]; then
echo "Using JAVA_OPTS: $JAVA_OPTS" echo "Using JAVA_OPTS: $JAVA_OPTS"

View file

@ -29,6 +29,7 @@ import static org.keycloak.quarkus.runtime.cli.command.Start.isDevProfileNotAllo
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ForkJoinPool;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import org.keycloak.common.profile.ProfileException; import org.keycloak.common.profile.ProfileException;
@ -56,6 +57,8 @@ import io.quarkus.runtime.annotations.QuarkusMain;
public class KeycloakMain implements QuarkusApplication { public class KeycloakMain implements QuarkusApplication {
public static void main(String[] args) { public static void main(String[] args) {
ensureForkJoinPoolThreadFactoryHasBeenSetToQuarkus();
System.setProperty("kc.version", Version.VERSION); System.setProperty("kc.version", Version.VERSION);
List<String> cliArgs = null; List<String> cliArgs = null;
try { try {
@ -96,6 +99,25 @@ public class KeycloakMain implements QuarkusApplication {
parseAndRun(cliArgs); 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) { private static void handleUsageError(String message) {
handleUsageError(message, null); handleUsageError(message, null);
} }

View file

@ -114,7 +114,7 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<configuration> <configuration>
<argLine>-Djdk.net.hosts.file=${project.build.testOutputDirectory}/hosts_file -XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError --add-opens=java.base/java.security=ALL-UNNAMED</argLine> <argLine>-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</argLine>
<systemPropertyVariables> <systemPropertyVariables>
<kc.quarkus.tests.dist>${kc.quarkus.tests.dist}</kc.quarkus.tests.dist> <kc.quarkus.tests.dist>${kc.quarkus.tests.dist}</kc.quarkus.tests.dist>
</systemPropertyVariables> </systemPropertyVariables>