From b4ad06c0e16c8d60b4f9b91945d7d4497acf2c7c Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 25 Jan 2022 16:41:51 +0200 Subject: [PATCH] Introduce QuarkusBindException This exception is used to record the actual ports that cause binding conflicts, thus allowing Quarkus to properly inform the user of which port(s) caused conflicts instead of guessing based on what the provided configuration. Fixes: #22967 --- .../runtime/ApplicationLifecycleManager.java | 52 ++++++------------ .../quarkus/runtime/QuarkusBindException.java | 29 ++++++++++ .../grpc/runtime/GrpcServerRecorder.java | 54 ++++++++++++++----- .../vertx/http/runtime/VertxHttpRecorder.java | 16 +++++- 4 files changed, 100 insertions(+), 51 deletions(-) create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/QuarkusBindException.java diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java index fb9c4e867f87d..3edb831e56123 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java @@ -1,6 +1,6 @@ package io.quarkus.runtime; -import java.net.BindException; +import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Set; @@ -10,6 +10,7 @@ import java.util.function.Consumer; import java.util.logging.ConsoleHandler; import java.util.logging.Handler; +import java.util.stream.Collectors; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.Any; @@ -17,8 +18,6 @@ import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.CDI; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.graalvm.nativeimage.ImageInfo; import org.jboss.logging.Logger; import org.jboss.logmanager.handlers.AsyncHandler; @@ -155,51 +154,32 @@ public static void run(Application application, Class ports = ((QuarkusBindException) rootCause).getPorts(); + if (ports.size() == 1) { applicationLogger.errorf("Port %d seems to be in use by another process. " + - "Quarkus may already be running or the port is used by another application.", port); - } else if (config.getOptionalValue("quarkus.http.ssl.certificate.file", String.class).isPresent() - || config.getOptionalValue("quarkus.http.ssl.certificate.key-file", String.class).isPresent() - || config.getOptionalValue("quarkus.http.ssl.certificate.key-store-file", String.class) - .isPresent()) { - // The port which is already bound could be either http or https, so we check if https is enabled by looking at the config properties - port = config.getOptionalValue("quarkus.http.port", Integer.class).orElse(8080); - sslPort = config.getOptionalValue("quarkus.http.ssl-port", Integer.class).orElse(8443); + "Quarkus may already be running or the port is used by another application.", ports.get(0)); + } else { applicationLogger.errorf( - "Either port %d or port %d seem to be in use by another process. " + + "One or more of the following ports: %s seem to be in use by another process. " + "Quarkus may already be running or one of the ports is used by another application.", - port, sslPort); - } else { - // If no ssl configuration is found, and http port is not disabled, then it must be the one which is already bound - port = config.getOptionalValue("quarkus.http.port", Integer.class).orElse(8080); - applicationLogger.errorf("Port %d seems to be in use by another process. " + - "Quarkus may already be running or the port is used by another application.", port); + ports.stream().map( + Object::toString).collect(Collectors.joining(","))); } if (IS_WINDOWS) { applicationLogger.warn("Use 'netstat -a -b -n -o' to identify the process occupying the port."); applicationLogger.warn("You can try to kill it with 'taskkill /PID ' or via the Task Manager."); } else if (IS_MAC) { - applicationLogger - .warnf("Use 'netstat -anv | grep %d' to identify the process occupying the port.", port); - if (sslPort != null) + for (Integer port : ports) { applicationLogger - .warnf("Use 'netstat -anv | grep %d' to identify the process occupying the port.", sslPort); + .warnf("Use 'netstat -anv | grep %d' to identify the process occupying the port.", port); + } applicationLogger.warn("You can try to kill it with 'kill -9 '."); } else { - applicationLogger - .warnf("Use 'netstat -anop | grep %d' to identify the process occupying the port.", port); - if (sslPort != null) + for (Integer port : ports) { applicationLogger - .warnf("Use 'netstat -anop | grep %d' to identify the process occupying the port.", - sslPort); + .warnf("Use 'netstat -anop | grep %d' to identify the process occupying the port.", port); + } applicationLogger.warn("You can try to kill it with 'kill -9 '."); } } else if (rootCause instanceof ConfigurationException) { diff --git a/core/runtime/src/main/java/io/quarkus/runtime/QuarkusBindException.java b/core/runtime/src/main/java/io/quarkus/runtime/QuarkusBindException.java new file mode 100644 index 0000000000000..b0b9cdd270af7 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/QuarkusBindException.java @@ -0,0 +1,29 @@ +package io.quarkus.runtime; + +import java.net.BindException; +import java.util.Collections; +import java.util.List; + +/** + * An exception that is meant to stand in for {@link BindException} and provide information + * about the target which caused the bind exception. + */ +public class QuarkusBindException extends BindException { + + private final List ports; + + public QuarkusBindException(int port) { + this(Collections.singletonList(port)); + } + + public QuarkusBindException(List ports) { + if (ports.isEmpty()) { + throw new IllegalStateException("ports must not be empty"); + } + this.ports = ports; + } + + public List getPorts() { + return ports; + } +} diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java index 2230ec0a99be3..5599144e57155 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java @@ -5,7 +5,9 @@ import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; +import java.net.BindException; import java.time.Duration; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -45,6 +47,7 @@ import io.quarkus.grpc.runtime.supports.CompressionInterceptor; import io.quarkus.grpc.runtime.supports.blocking.BlockingServerInterceptor; import io.quarkus.runtime.LaunchMode; +import io.quarkus.runtime.QuarkusBindException; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; @@ -164,13 +167,20 @@ private void devModeStart(GrpcContainer grpcContainer, Vertx vertx, GrpcServerCo devModeWrapper = new DevModeWrapper(Thread.currentThread().getContextClassLoader()); - VertxServer vertxServer = buildServer(vertx, configuration, grpcContainer, launchMode) - .start(new Handler>() { // NOSONAR + Map.Entry portToServer = buildServer(vertx, configuration, grpcContainer, launchMode); + + VertxServer vertxServer = portToServer.getValue() + .start(new Handler<>() { // NOSONAR @Override public void handle(AsyncResult ar) { if (ar.failed()) { - LOGGER.error("Unable to start the gRPC server", ar.cause()); - future.completeExceptionally(ar.cause()); + Throwable effectiveCause = getEffectiveThrowable(ar, portToServer); + if (effectiveCause instanceof QuarkusBindException) { + LOGGER.error("Unable to start the gRPC server"); + } else { + LOGGER.error("Unable to start the gRPC server", effectiveCause); + } + future.completeExceptionally(effectiveCause); } else { postStartup(configuration, false); future.complete(true); @@ -250,6 +260,17 @@ private static List collectServiceDefinitions(Instance ar, Map.Entry portToServer) { + Throwable effectiveCause = ar.cause(); + while (effectiveCause.getCause() != null) { + effectiveCause = effectiveCause.getCause(); + } + if (effectiveCause instanceof BindException) { + effectiveCause = new QuarkusBindException(portToServer.getKey()); + } + return effectiveCause; + } + public static final class GrpcServiceDefinition { public final BindableService service; @@ -320,11 +341,10 @@ public RuntimeValue initServerInterceptorStorage( return new RuntimeValue<>(new ServerInterceptorStorage(perServiceInterceptors, globalInterceptors)); } - private VertxServer buildServer(Vertx vertx, GrpcServerConfiguration configuration, + private Map.Entry buildServer(Vertx vertx, GrpcServerConfiguration configuration, GrpcContainer grpcContainer, LaunchMode launchMode) { - VertxServerBuilder builder = VertxServerBuilder - .forAddress(vertx, configuration.host, - launchMode == LaunchMode.TEST ? configuration.testPort : configuration.port); + int port = launchMode == LaunchMode.TEST ? configuration.testPort : configuration.port; + VertxServerBuilder builder = VertxServerBuilder.forAddress(vertx, configuration.host, port); AtomicBoolean usePlainText = new AtomicBoolean(); builder.useSsl(new Handler() { // NOSONAR @@ -401,10 +421,10 @@ public void handle(AsyncResult result) { } LOGGER.debugf("Starting gRPC Server on %s:%d [SSL enabled: %s]...", - configuration.host, launchMode == LaunchMode.TEST ? configuration.testPort : configuration.port, + configuration.host, port, !usePlainText.get()); - return builder.build(); + return new AbstractMap.SimpleEntry<>(port, builder.build()); } /** @@ -460,13 +480,19 @@ public void start(Promise startPromise) { "Unable to find bean exposing the `BindableService` interface - not starting the gRPC server"); return; } - grpcServer = buildServer(getVertx(), configuration, grpcContainer, launchMode) - .start(new Handler>() { // NOSONAR + Map.Entry portToServer = buildServer(getVertx(), configuration, grpcContainer, launchMode); + grpcServer = portToServer.getValue() + .start(new Handler<>() { // NOSONAR @Override public void handle(AsyncResult ar) { if (ar.failed()) { - LOGGER.error("Unable to start the gRPC server", ar.cause()); - startPromise.fail(ar.cause()); + Throwable effectiveCause = getEffectiveThrowable(ar, portToServer); + if (effectiveCause instanceof QuarkusBindException) { + LOGGER.error("Unable to start the gRPC server"); + } else { + LOGGER.error("Unable to start the gRPC server", effectiveCause); + } + startPromise.fail(effectiveCause); } else { startPromise.complete(); grpcVerticleCount.incrementAndGet(); diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java index fcb52a1adf75d..b6385ef98a3c5 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.BindException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; @@ -50,6 +51,7 @@ import io.quarkus.netty.runtime.virtual.VirtualServerChannel; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.LiveReloadConfig; +import io.quarkus.runtime.QuarkusBindException; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; @@ -547,7 +549,19 @@ public Verticle get() { @Override public void handle(AsyncResult event) { if (event.failed()) { - futureResult.completeExceptionally(event.cause()); + Throwable effectiveCause = event.cause(); + if (effectiveCause instanceof BindException) { + if ((sslConfig == null) && (httpServerOptions != null)) { + effectiveCause = new QuarkusBindException(httpServerOptions.getPort()); + } else if ((httpConfiguration.insecureRequests == InsecureRequests.DISABLED) && (sslConfig != null)) { + effectiveCause = new QuarkusBindException(sslConfig.getPort()); + } else if ((sslConfig != null) && (httpConfiguration.insecureRequests == InsecureRequests.ENABLED) + && (httpServerOptions != null)) { + effectiveCause = new QuarkusBindException( + List.of(httpServerOptions.getPort(), sslConfig.getPort())); + } + } + futureResult.completeExceptionally(effectiveCause); } else { futureResult.complete(event.result()); }