Skip to content

Commit

Permalink
Introduce QuarkusBindException
Browse files Browse the repository at this point in the history
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: quarkusio#22967
  • Loading branch information
geoand committed Jan 25, 2022
1 parent f8346d7 commit b4ad06c
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,15 +10,14 @@
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;
import javax.enterprise.inject.spi.Bean;
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;
Expand Down Expand Up @@ -155,51 +154,32 @@ public static void run(Application application, Class<? extends QuarkusApplicati
rootCause = rootCause.getCause();
}
Logger applicationLogger = Logger.getLogger(Application.class);
if (rootCause instanceof BindException) {
Config config = ConfigProviderResolver.instance().getConfig();
Integer port = null;
Integer sslPort = null;

if (config.getOptionalValue("quarkus.http.insecure-requests", String.class).orElse("")
.equalsIgnoreCase("disabled")) {
// If http port is disabled, then the exception must have been thrown because of the https port
port = config.getOptionalValue("quarkus.http.ssl-port", Integer.class).orElse(8443);
if (rootCause instanceof QuarkusBindException) {
List<Integer> 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 <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 <pid>'.");
} 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 <pid>'.");
}
} else if (rootCause instanceof ConfigurationException) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Integer> ports;

public QuarkusBindException(int port) {
this(Collections.singletonList(port));
}

public QuarkusBindException(List<Integer> ports) {
if (ports.isEmpty()) {
throw new IllegalStateException("ports must not be empty");
}
this.ports = ports;
}

public List<Integer> getPorts() {
return ports;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<AsyncResult<Void>>() { // NOSONAR
Map.Entry<Integer, VertxServer> portToServer = buildServer(vertx, configuration, grpcContainer, launchMode);

VertxServer vertxServer = portToServer.getValue()
.start(new Handler<>() { // NOSONAR
@Override
public void handle(AsyncResult<Void> 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);
Expand Down Expand Up @@ -250,6 +260,17 @@ private static List<GrpcServiceDefinition> collectServiceDefinitions(Instance<Bi
return definitions;
}

private Throwable getEffectiveThrowable(AsyncResult<Void> ar, Map.Entry<Integer, VertxServer> 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;
Expand Down Expand Up @@ -320,11 +341,10 @@ public RuntimeValue<ServerInterceptorStorage> initServerInterceptorStorage(
return new RuntimeValue<>(new ServerInterceptorStorage(perServiceInterceptors, globalInterceptors));
}

private VertxServer buildServer(Vertx vertx, GrpcServerConfiguration configuration,
private Map.Entry<Integer, VertxServer> 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<HttpServerOptions>() { // NOSONAR
Expand Down Expand Up @@ -401,10 +421,10 @@ public void handle(AsyncResult<Boolean> 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());
}

/**
Expand Down Expand Up @@ -460,13 +480,19 @@ public void start(Promise<Void> startPromise) {
"Unable to find bean exposing the `BindableService` interface - not starting the gRPC server");
return;
}
grpcServer = buildServer(getVertx(), configuration, grpcContainer, launchMode)
.start(new Handler<AsyncResult<Void>>() { // NOSONAR
Map.Entry<Integer, VertxServer> portToServer = buildServer(getVertx(), configuration, grpcContainer, launchMode);
grpcServer = portToServer.getValue()
.start(new Handler<>() { // NOSONAR
@Override
public void handle(AsyncResult<Void> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -547,7 +549,19 @@ public Verticle get() {
@Override
public void handle(AsyncResult<String> 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());
}
Expand Down

0 comments on commit b4ad06c

Please sign in to comment.