diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/console/DevConsoleManager.java b/core/devmode-spi/src/main/java/io/quarkus/dev/console/DevConsoleManager.java index 9648f1f78c5d6..1182b81a9165a 100644 --- a/core/devmode-spi/src/main/java/io/quarkus/dev/console/DevConsoleManager.java +++ b/core/devmode-spi/src/main/java/io/quarkus/dev/console/DevConsoleManager.java @@ -13,6 +13,7 @@ public class DevConsoleManager { private static volatile Map> templateInfo; private static volatile HotReplacementContext hotReplacementContext; private static volatile Object quarkusBootstrap; + private static volatile boolean doingHttpInitiatedReload; /** * Global map that can be used to share data betweeen the runtime and deployment side * to enable communication. @@ -80,6 +81,14 @@ public static T getGlobal(String name) { return (T) globals.get(name); } + public static boolean isDoingHttpInitiatedReload() { + return doingHttpInitiatedReload; + } + + public static void setDoingHttpInitiatedReload(boolean doingHttpInitiatedReload) { + DevConsoleManager.doingHttpInitiatedReload = doingHttpInitiatedReload; + } + public static void close() { handler = null; templateInfo = null; diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfigEditorProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfigEditorProcessor.java index d4083f30145a7..2c7f88605022b 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfigEditorProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfigEditorProcessor.java @@ -87,8 +87,13 @@ protected void handlePost(RoutingContext event, MultiMap form) throws Exception writer.newLine(); } } - - DevConsoleManager.getHotReplacementContext().doScan(true); + //if we don't set this the connection will be killed on restart + DevConsoleManager.setDoingHttpInitiatedReload(true); + try { + DevConsoleManager.getHotReplacementContext().doScan(true); + } finally { + DevConsoleManager.setDoingHttpInitiatedReload(false); + } flashMessage(event, "Configuration updated"); } }); 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 6638552083ee2..2119958b03dbd 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 @@ -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; @@ -168,6 +169,7 @@ public static void shutDownDevMode() { } rootHandler = null; hotReplacementHandler = null; + } public static void startServerAfterFailedStart() { @@ -250,6 +252,13 @@ public void startServer(Supplier vertx, ShutdownContext shutdown, websocketSubProtocols, auxiliaryApplication); if (launchMode != LaunchMode.DEVELOPMENT) { shutdown.addShutdownTask(closeTask); + } else { + shutdown.addShutdownTask(new Runnable() { + @Override + public void run() { + VertxHttpHotReplacementSetup.handleDevModeRestart(); + } + }); } } } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/VertxHttpHotReplacementSetup.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/VertxHttpHotReplacementSetup.java index d5697aae86166..3a81320c0c37a 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/VertxHttpHotReplacementSetup.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/VertxHttpHotReplacementSetup.java @@ -1,6 +1,12 @@ 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.console.DevConsoleManager; import io.quarkus.dev.spi.HotReplacementContext; import io.quarkus.dev.spi.HotReplacementSetup; import io.quarkus.vertx.http.runtime.VertxHttpRecorder; @@ -39,7 +45,38 @@ public void handleFailedInitialStart() { VertxHttpRecorder.startServerAfterFailedStart(); } + private static volatile Set openConnections; + + public static void handleDevModeRestart() { + if (DevConsoleManager.isDoingHttpInitiatedReload()) { + return; + } + Set cons = VertxHttpHotReplacementSetup.openConnections; + if (cons != null) { + for (ConnectionBase con : cons) { + con.close(); + } + } + } + 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>() { + @Override + public void handle(AsyncResult event) { + openConnections.remove(connectionBase); + } + }); + } + if ((nextUpdate > System.currentTimeMillis() && !hotReplacementContext.isTest()) || routingContext.request().headers().contains(HEADER_NAME)) { if (hotReplacementContext.getDeploymentProblem() != null) { @@ -50,27 +87,42 @@ void handleHotReplacementRequest(RoutingContext routingContext) { return; } ClassLoader current = Thread.currentThread().getContextClassLoader(); - ConnectionBase connectionBase = (ConnectionBase) routingContext.request().connection(); connectionBase.getContext().executeBlocking(new Handler>() { @Override public void handle(Promise 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 { + DevConsoleManager.setDoingHttpInitiatedReload(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 connections = new HashSet<>(openConnections); + for (ConnectionBase con : connections) { + if (con != connectionBase) { + con.close(); + } + } + } + } finally { + DevConsoleManager.setDoingHttpInitiatedReload(false); } event.complete(restart); } diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java index 2856b5e19e2e1..a8f2b382e3fdb 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java @@ -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; @@ -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; @@ -330,7 +333,9 @@ public QuarkusClassLoader createRuntimeClassLoader(Map resources public QuarkusClassLoader createRuntimeClassLoader(ClassLoader base, Map resources, Map 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);