+
Skip to content

fix: ensuring exit codes are handled consistently #38661

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@
import static org.keycloak.quarkus.runtime.Environment.isTestLaunchMode;
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTIMIZED_BUILD_OPTION_LONG;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;

import jakarta.enterprise.context.ApplicationScoped;
import picocli.CommandLine;

import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;

import io.quarkus.bootstrap.runner.RunnerClassLoader;
import io.quarkus.runtime.ApplicationLifecycleManager;
import io.quarkus.runtime.Quarkus;

Expand Down Expand Up @@ -68,7 +70,18 @@ public static void main(String[] args) {

System.setProperty("kc.version", Version.VERSION);

main(args, new Picocli());
Picocli picocli;
if (!(Thread.currentThread().getContextClassLoader() instanceof RunnerClassLoader)) {
picocli = new Picocli() { // embedded launch case, avoid System.exit
@Override
public void exit(int exitCode) {
Quarkus.asyncExit(exitCode);
};
};
} else {
picocli = new Picocli();
}
main(args, picocli);
}

private static void ensureVirtualThreadsParallelism() {
Expand Down Expand Up @@ -98,7 +111,7 @@ public static void main(String[] args, Picocli picocli) {
picocli.usageException(e.getMessage(), e.getCause());
return;
}

if (DryRunMixin.isDryRunBuild() && (cliArgs.contains(DryRunMixin.DRYRUN_OPTION_LONG) || Boolean.valueOf(System.getenv().get(DryRunMixin.KC_DRY_RUN_ENV)))) {
PersistedConfigSource.getInstance().useDryRunProperties();
}
Expand Down Expand Up @@ -140,45 +153,38 @@ private static boolean isFastStart(List<String> cliArgs) {
return cliArgs.size() == 2 && cliArgs.get(0).equals(Start.NAME) && cliArgs.stream().anyMatch(OPTIMIZED_BUILD_OPTION_LONG::equals);
}

public static void start(ExecutionExceptionHandler errorHandler, PrintWriter errStream) {
public static void start(Picocli picocli, ExecutionExceptionHandler errorHandler) {
try {
Quarkus.run(KeycloakMain.class, (exitCode, cause) -> {
if (cause != null) {
errorHandler.error(errStream,
errorHandler.error(picocli.getErrWriter(),
String.format("Failed to start server in (%s) mode", getKeycloakModeFromProfile(org.keycloak.common.util.Environment.getProfile())),
cause.getCause());
}

if (Environment.isDistribution()) {
// assume that it is running the distribution
// as we are replacing the default exit handler, we need to force exit
System.exit(exitCode);
}
picocli.exit(exitCode);
});
} catch (Throwable cause) {
errorHandler.error(errStream,
errorHandler.error(picocli.getErrWriter(),
String.format("Unexpected error when starting the server in (%s) mode", getKeycloakModeFromProfile(org.keycloak.common.util.Environment.getProfile())),
cause.getCause());
System.exit(1);
}
picocli.exit(CommandLine.ExitCode.SOFTWARE);
}

/**
* Should be called after the server is fully initialized
*/
@Override
public int run(String... args) throws Exception {
int exitCode = ApplicationLifecycleManager.getExitCode();

if (isTestLaunchMode() || isNonServerMode()) {
// in test mode we exit immediately
// we should be managing this behavior more dynamically depending on the tests requirements (short/long lived)
Quarkus.asyncExit(exitCode);
Quarkus.asyncExit(ApplicationLifecycleManager.getExitCode());
} else {
Quarkus.waitForExit();
}

return exitCode;
return ApplicationLifecycleManager.getExitCode();
Comment on lines -171 to +187
Copy link
Contributor

Choose a reason for hiding this comment

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

Jus out of curiosity, why is this change needed? Why can't we store the exit code in a var?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a potential bug, we don't want to store it because it could change while the waiting for exit.

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,7 @@ public void usageException(String message, Throwable cause) {
}

public void exit(int exitCode) {
if (exitCode != CommandLine.ExitCode.OK && (!Environment.isTestLaunchMode() || isRebuildCheck())) {
// hard exit wanted, as build failed and no subsequent command should be executed. no quarkus involved.
System.exit(exitCode);
}
System.exit(exitCode);
}

private int runReAugmentationIfNeeded(List<String> cliArgs, CommandLine cmd, CommandLine currentCommand) {
Expand Down Expand Up @@ -985,7 +982,7 @@ private static boolean isIgnoredPersistedOption(String key) {
}

public void start() {
KeycloakMain.start(errorHandler, getErrWriter());
KeycloakMain.start(this, errorHandler);
}

public void build() throws Throwable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.keycloak.common.Version;
import org.keycloak.compatibility.CompatibilityResult;
import org.keycloak.compatibility.KeycloakCompatibilityMetadataProvider;
import org.keycloak.infinispan.compatibility.CachingCompatibilityMetadataProvider;
import org.keycloak.it.junit5.extension.CLIResult;
Expand Down Expand Up @@ -98,6 +99,7 @@ public void testWrongVersions(KeycloakDistribution distribution) throws IOExcept
JsonSerialization.mapper.writeValue(jsonFile, info);

var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath());
result.assertExitCode(CompatibilityResult.ExitCode.RECREATE.value());
result.assertError("[%s] Rolling Update is not available. '%s.version' is incompatible: 0.0.0.Final -> %s.".formatted(KeycloakCompatibilityMetadataProvider.ID, KeycloakCompatibilityMetadataProvider.ID, Version.VERSION));

// incompatible infinispan version
Expand Down
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载