Skip to content

Commit

Permalink
Close other HTTP connections on reload
Browse files Browse the repository at this point in the history
This will prevent old persistent connections hanging onto the old
shutdown application.

Fixes quarkusio#18776
  • Loading branch information
stuartwdouglas committed Jul 28, 2021
1 parent 6ff45d3 commit ef81669
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.quarkus.runtime.configuration.ProfileManager;
import io.quarkus.vertx.http.runtime.devmode.ConfigDescription;
import io.quarkus.vertx.http.runtime.devmode.ConfigDescriptionsSupplier;
import io.quarkus.vertx.http.runtime.devmode.VertxHttpHotReplacementSetup;
import io.vertx.core.MultiMap;
import io.vertx.ext.web.RoutingContext;

Expand Down Expand Up @@ -87,8 +88,12 @@ protected void handlePost(RoutingContext event, MultiMap form) throws Exception
writer.newLine();
}
}

DevConsoleManager.getHotReplacementContext().doScan(true);
VertxHttpHotReplacementSetup.setDoingHttpInitiatedReload(true);
try {
DevConsoleManager.getHotReplacementContext().doScan(true);
} finally {
VertxHttpHotReplacementSetup.setDoingHttpInitiatedReload(false);
}
flashMessage(event, "Configuration updated");
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import io.quarkus.vertx.core.runtime.config.VertxConfiguration;
import io.quarkus.vertx.http.runtime.HttpConfiguration.InsecureRequests;
import io.quarkus.vertx.http.runtime.devmode.RemoteSyncHandler;
import io.quarkus.vertx.http.runtime.devmode.VertxHttpHotReplacementSetup;
import io.quarkus.vertx.http.runtime.filters.Filter;
import io.quarkus.vertx.http.runtime.filters.Filters;
import io.quarkus.vertx.http.runtime.filters.GracefulShutdownFilter;
Expand Down Expand Up @@ -168,6 +169,7 @@ public static void shutDownDevMode() {
}
rootHandler = null;
hotReplacementHandler = null;

}

public static void startServerAfterFailedStart() {
Expand Down Expand Up @@ -250,6 +252,13 @@ public void startServer(Supplier<Vertx> vertx, ShutdownContext shutdown,
websocketSubProtocols, auxiliaryApplication);
if (launchMode != LaunchMode.DEVELOPMENT) {
shutdown.addShutdownTask(closeTask);
} else {
shutdown.addShutdownTask(new Runnable() {
@Override
public void run() {
VertxHttpHotReplacementSetup.handleDevModeRestart();
}
});
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package io.quarkus.vertx.http.runtime.devmode;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import io.quarkus.dev.ErrorPageGenerators;
import io.quarkus.dev.spi.HotReplacementContext;
import io.quarkus.dev.spi.HotReplacementSetup;
Expand Down Expand Up @@ -39,7 +44,45 @@ public void handleFailedInitialStart() {
VertxHttpRecorder.startServerAfterFailedStart();
}

private static volatile Set<ConnectionBase> openConnections;
private static volatile boolean doingHttpInitiatedReload;

public static void handleDevModeRestart() {
if (doingHttpInitiatedReload) {
return;
}
Set<ConnectionBase> connections = new HashSet<>(openConnections);
for (ConnectionBase con : connections) {
con.close();
}
}

public static boolean isDoingHttpInitiatedReload() {
return doingHttpInitiatedReload;
}

public static void setDoingHttpInitiatedReload(boolean doingHttpInitiatedReload) {
VertxHttpHotReplacementSetup.doingHttpInitiatedReload = doingHttpInitiatedReload;
}

void handleHotReplacementRequest(RoutingContext routingContext) {
if (openConnections == null) {
synchronized (VertxHttpHotReplacementSetup.class) {
if (openConnections == null) {
openConnections = Collections.newSetFromMap(new ConcurrentHashMap<>());
}
}
}
ConnectionBase connectionBase = (ConnectionBase) routingContext.request().connection();
if (openConnections.add(connectionBase)) {
connectionBase.closeFuture().onComplete(new Handler<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> event) {
openConnections.remove(connectionBase);
}
});
}

if ((nextUpdate > System.currentTimeMillis() && !hotReplacementContext.isTest())
|| routingContext.request().headers().contains(HEADER_NAME)) {
if (hotReplacementContext.getDeploymentProblem() != null) {
Expand All @@ -50,27 +93,42 @@ void handleHotReplacementRequest(RoutingContext routingContext) {
return;
}
ClassLoader current = Thread.currentThread().getContextClassLoader();
ConnectionBase connectionBase = (ConnectionBase) routingContext.request().connection();
connectionBase.getContext().executeBlocking(new Handler<Promise<Boolean>>() {
@Override
public void handle(Promise<Boolean> event) {
//the blocking pool may have a stale TCCL
Thread.currentThread().setContextClassLoader(current);
boolean restart = false;
synchronized (this) {
if (nextUpdate < System.currentTimeMillis() || hotReplacementContext.isTest()) {
nextUpdate = System.currentTimeMillis() + HOT_REPLACEMENT_INTERVAL;
try {
restart = hotReplacementContext.doScan(true);
} catch (Exception e) {
event.fail(new IllegalStateException("Unable to perform live reload scanning", e));
return;
try {
doingHttpInitiatedReload = true;
synchronized (this) {
if (nextUpdate < System.currentTimeMillis() || hotReplacementContext.isTest()) {
nextUpdate = System.currentTimeMillis() + HOT_REPLACEMENT_INTERVAL;
try {
restart = hotReplacementContext.doScan(true);
} catch (Exception e) {
event.fail(new IllegalStateException("Unable to perform live reload scanning", e));
return;
}
}
}
}
if (hotReplacementContext.getDeploymentProblem() != null) {
event.fail(hotReplacementContext.getDeploymentProblem());
return;
if (hotReplacementContext.getDeploymentProblem() != null) {
event.fail(hotReplacementContext.getDeploymentProblem());
return;
}
if (restart) {
//close all connections on close, except for this one
//this prevents long running requests such as SSE or websockets
//from holding onto the old deployment
Set<ConnectionBase> connections = new HashSet<>(openConnections);
for (ConnectionBase con : connections) {
if (con != connectionBase) {
con.close();
}
}
}
} finally {
doingHttpInitiatedReload = false;
}
event.complete(restart);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
Expand Down Expand Up @@ -62,6 +63,8 @@ public class CuratedApplication implements Serializable, AutoCloseable {

final AppModel appModel;

final AtomicInteger runtimeClassLoaderCount = new AtomicInteger();

CuratedApplication(QuarkusBootstrap quarkusBootstrap, CurationResult curationResult,
ConfiguredClassLoading configuredClassLoading) {
this.quarkusBootstrap = quarkusBootstrap;
Expand Down Expand Up @@ -330,7 +333,9 @@ public QuarkusClassLoader createRuntimeClassLoader(Map<String, byte[]> resources
public QuarkusClassLoader createRuntimeClassLoader(ClassLoader base, Map<String, byte[]> resources,
Map<String, byte[]> transformedClasses) {
QuarkusClassLoader.Builder builder = QuarkusClassLoader
.builder("Quarkus Runtime ClassLoader: " + quarkusBootstrap.getMode(),
.builder(
"Quarkus Runtime ClassLoader: " + quarkusBootstrap.getMode() + " restart no:"
+ runtimeClassLoaderCount.getAndIncrement(),
getBaseRuntimeClassLoader(), false)
.setAssertionsEnabled(quarkusBootstrap.isAssertionsEnabled())
.setAggregateParentResources(true);
Expand Down

0 comments on commit ef81669

Please sign in to comment.