diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeMain.java index 02bfdcd3405e5..6377b9c3b908a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeMain.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeMain.java @@ -40,7 +40,7 @@ public class DevModeMain implements Closeable { private final DevModeContext context; - private static volatile CuratedApplication curatedApplication; + private volatile CuratedApplication curatedApplication; private Closeable realCloseable; public DevModeMain(DevModeContext context) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java index b68f3b9bbc676..7fb810f807708 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java @@ -15,6 +15,7 @@ import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -61,16 +62,16 @@ public class IsolatedDevModeMain implements BiConsumer hotReplacementSetups = new ArrayList<>(); - private static volatile RunningQuarkusApplication runner; - static volatile Throwable deploymentProblem; - private static volatile CuratedApplication curatedApplication; - private static volatile AugmentAction augmentAction; - private static volatile boolean restarting; - private static volatile boolean firstStartCompleted; - private static final CountDownLatch shutdownLatch = new CountDownLatch(1); + private volatile RunningQuarkusApplication runner; + final AtomicReference deploymentProblem = new AtomicReference<>(); + private volatile CuratedApplication curatedApplication; + private volatile AugmentAction augmentAction; + private volatile boolean restarting; + private volatile boolean firstStartCompleted; + private final CountDownLatch shutdownLatch = new CountDownLatch(1); private Thread shutdownThread; private CodeGenWatcher codeGenWatcher; - private static volatile ConsoleStateManager.ConsoleContext consoleContext; + private volatile ConsoleStateManager.ConsoleContext consoleContext; private final List listeners = new ArrayList<>(); private synchronized void firstStart() { @@ -85,30 +86,7 @@ private synchronized void firstStart() { //TODO: look at implementing a common core classloader, that removes the need for this sort of crappy hack curatedApplication.getOrCreateBaseRuntimeClassLoader().loadClass(ApplicationLifecycleManager.class.getName()) .getMethod("setDefaultExitCodeHandler", Consumer.class) - .invoke(null, new Consumer() { - @Override - public void accept(Integer integer) { - if (restarting || ApplicationLifecycleManager.isVmShuttingDown() - || context.isAbortOnFailedStart() || - context.isTest()) { - return; - } - if (consoleContext == null) { - consoleContext = ConsoleStateManager.INSTANCE - .createContext("Completed Application"); - } - //this sucks, but when we get here logging is gone - //so we just setup basic console logging - InitialConfigurator.DELAYED_HANDLER.addHandler(new ConsoleHandler( - ConsoleHandler.Target.SYSTEM_OUT, - new ColorPatternFormatter("%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n"))); - consoleContext.reset(new ConsoleCommand(' ', "Restarts the application", "to restart", 0, null, - () -> { - consoleContext.reset(); - RuntimeUpdatesProcessor.INSTANCE.doScan(true, true); - })); - } - }); + .invoke(null, getExitCodeHandler()); StartupAction start = augmentAction.createInitialRuntimeApplication(); @@ -127,7 +105,7 @@ public void accept(Integer integer) { rootCause = rootCause.getCause(); } if (!(rootCause instanceof BindException)) { - deploymentProblem = t; + deploymentProblem.set(t); if (!context.isAbortOnFailedStart()) { //we need to set this here, while we still have the correct TCCL //this is so the config is still valid, and we can read HTTP config from application.properties @@ -174,6 +152,35 @@ public void accept(Integer integer) { } } + private Consumer getExitCodeHandler() { + if (context.isTest() || context.isAbortOnFailedStart()) { + return TestExitCodeHandler.INSTANCE; + } + + return new Consumer() { + @Override + public void accept(Integer integer) { + if (restarting || ApplicationLifecycleManager.isVmShuttingDown()) { + return; + } + if (consoleContext == null) { + consoleContext = ConsoleStateManager.INSTANCE + .createContext("Completed Application"); + } + //this sucks, but when we get here logging is gone + //so we just setup basic console logging + InitialConfigurator.DELAYED_HANDLER.addHandler(new ConsoleHandler( + ConsoleHandler.Target.SYSTEM_OUT, + new ColorPatternFormatter("%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n"))); + consoleContext.reset(new ConsoleCommand(' ', "Restarts the application", "to restart", 0, null, + () -> { + consoleContext.reset(); + RuntimeUpdatesProcessor.INSTANCE.doScan(true, true); + })); + } + }; + } + public void restartCallback(Set changedResources, ClassScanResult result) { restartApp(changedResources, new ClassChangeInformation(result.changedClassNames, result.deletedClassNames, result.addedClassNames)); @@ -186,7 +193,7 @@ public synchronized void restartApp(Set changedResources, ClassChangeInf } stop(); Timing.restart(curatedApplication.getOrCreateAugmentClassLoader()); - deploymentProblem = null; + deploymentProblem.set(null); ClassLoader old = Thread.currentThread().getContextClassLoader(); try { @@ -200,7 +207,7 @@ public synchronized void restartApp(Set changedResources, ClassChangeInf firstStartCompleted = true; } } catch (Throwable t) { - deploymentProblem = t; + deploymentProblem.set(t); Throwable rootCause = t; while (rootCause.getCause() != null) { rootCause = rootCause.getCause(); @@ -253,7 +260,7 @@ private RuntimeUpdatesProcessor setupRuntimeCompilation(DevModeContext context, public byte[] apply(String s, byte[] bytes) { return ClassTransformingBuildStep.transform(s, bytes); } - }, testSupport); + }, testSupport, deploymentProblem); for (HotReplacementSetup service : ServiceLoader.load(HotReplacementSetup.class, curatedApplication.getOrCreateBaseRuntimeClassLoader())) { @@ -350,6 +357,7 @@ public void close() { curatedApplication.close(); curatedApplication = null; augmentAction = null; + deploymentProblem.set(null); } finally { if (shutdownThread != null) { try { @@ -422,10 +430,11 @@ public void run() { firstStart(); // doStart(false, Collections.emptySet()); - if (deploymentProblem != null || RuntimeUpdatesProcessor.INSTANCE.getCompileProblem() != null) { + if (deploymentProblem.get() != null || RuntimeUpdatesProcessor.INSTANCE.getCompileProblem() != null) { if (context.isAbortOnFailedStart()) { - Throwable throwable = deploymentProblem == null ? RuntimeUpdatesProcessor.INSTANCE.getCompileProblem() - : deploymentProblem; + Throwable throwable = deploymentProblem.get() == null + ? RuntimeUpdatesProcessor.INSTANCE.getCompileProblem() + : deploymentProblem.get(); throw (throwable instanceof RuntimeException ? (RuntimeException) throwable : new RuntimeException(throwable)); @@ -483,4 +492,14 @@ public boolean test(String s) { }).produces(ApplicationClassPredicateBuildItem.class).build(); } } + + private static class TestExitCodeHandler implements Consumer { + + private static final TestExitCodeHandler INSTANCE = new TestExitCodeHandler(); + + @Override + public void accept(Integer exitCode) { + // do nothing + } + } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedRemoteDevModeMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedRemoteDevModeMain.java index a1e691f0748ed..17d4b5e547fb8 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedRemoteDevModeMain.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedRemoteDevModeMain.java @@ -21,6 +21,7 @@ import java.util.Optional; import java.util.ServiceLoader; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; @@ -57,7 +58,7 @@ public class IsolatedRemoteDevModeMain implements BiConsumer hotReplacementSetups = new ArrayList<>(); - static volatile Throwable deploymentProblem; + private AtomicReference deploymentProblem = new AtomicReference<>(); static volatile RemoteDevClient remoteDevClient; static volatile Closeable remoteDevClientSession; private static volatile CuratedApplication curatedApplication; @@ -99,7 +100,7 @@ private synchronized JarResult generateApplication() { curatedApplication.getApplicationModel(), null); return start.getJar(); } catch (Throwable t) { - deploymentProblem = t; + deploymentProblem.set(t); log.error("Failed to generate Quarkus application", t); return null; } @@ -137,7 +138,7 @@ public void accept(DevModeContext.ModuleInfo moduleInfo, String s) { public byte[] apply(String s, byte[] bytes) { return ClassTransformingBuildStep.transform(s, bytes); } - }, null); + }, null, deploymentProblem); for (HotReplacementSetup service : ServiceLoader.load(HotReplacementSetup.class, curatedApplication.getOrCreateBaseRuntimeClassLoader())) { @@ -189,6 +190,7 @@ public void close() { } } } finally { + deploymentProblem.set(null); curatedApplication.close(); } @@ -248,7 +250,7 @@ public void run() { } private Closeable doConnect() { - return remoteDevClient.sendConnectRequest(new RemoteDevState(currentHashes, deploymentProblem), + return remoteDevClient.sendConnectRequest(new RemoteDevState(currentHashes, deploymentProblem.get()), new Function, Map>() { @Override public Map apply(Set fileNames) { @@ -283,6 +285,7 @@ private RemoteDevClient.SyncResult runSync() { Set removed = new HashSet<>(); Map changed = new HashMap<>(); try { + deploymentProblem.set(null); boolean scanResult = RuntimeUpdatesProcessor.INSTANCE.doScan(true); if (!scanResult && !copiedStaticResources.isEmpty()) { scanResult = true; @@ -305,7 +308,7 @@ private RemoteDevClient.SyncResult runSync() { currentHashes = newHashes; } } catch (IOException e) { - deploymentProblem = e; + deploymentProblem.set(e); } return new RemoteDevClient.SyncResult() { @Override diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedTestModeMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedTestModeMain.java index 1f5b37aa311f0..9fa94f1ff6aec 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedTestModeMain.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedTestModeMain.java @@ -36,7 +36,6 @@ public class IsolatedTestModeMain extends IsolatedDevModeMain { private volatile DevModeContext context; private final List hotReplacementSetups = new ArrayList<>(); - static volatile Throwable deploymentProblem; private static volatile CuratedApplication curatedApplication; private static volatile AugmentAction augmentAction; @@ -68,7 +67,7 @@ public void accept(DevModeContext.ModuleInfo moduleInfo, String s) { public byte[] apply(String s, byte[] bytes) { return ClassTransformingBuildStep.transform(s, bytes); } - }, testSupport); + }, testSupport, deploymentProblem); for (HotReplacementSetup service : ServiceLoader.load(HotReplacementSetup.class, curatedApplication.getOrCreateBaseRuntimeClassLoader())) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java index f32d052a25aec..ca8f49634d88a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java @@ -40,6 +40,7 @@ import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiConsumer; @@ -91,6 +92,7 @@ public class RuntimeUpdatesProcessor implements HotReplacementContext, Closeable volatile Throwable compileProblem; volatile Throwable testCompileProblem; volatile Throwable hotReloadProblem; + private final AtomicReference deploymentProblem; private volatile Predicate disableInstrumentationForClassPredicate = new AlwaysFalsePredicate<>(); private volatile Predicate disableInstrumentationForIndexPredicate = new AlwaysFalsePredicate<>(); @@ -141,7 +143,7 @@ public RuntimeUpdatesProcessor(Path applicationRoot, DevModeContext context, Qua DevModeType devModeType, BiConsumer, ClassScanResult> restartCallback, BiConsumer copyResourceNotification, BiFunction classTransformers, - TestSupport testSupport) { + TestSupport testSupport, AtomicReference deploymentProblem) { this.applicationRoot = applicationRoot; this.context = context; this.compiler = compiler; @@ -180,6 +182,7 @@ public void testsDisabled() { } }); } + this.deploymentProblem = deploymentProblem; } public TestSupport getTestSupport() { @@ -392,7 +395,7 @@ public List getResourcesDir() { public Throwable getDeploymentProblem() { //we differentiate between these internally, however for the error reporting they are the same return compileProblem != null ? compileProblem - : IsolatedDevModeMain.deploymentProblem != null ? IsolatedDevModeMain.deploymentProblem + : deploymentProblem.get() != null ? deploymentProblem.get() : hotReloadProblem; } @@ -535,7 +538,7 @@ public boolean doScan(boolean userInitiated, boolean forceRestart) { //all broken we just assume the reason that they have refreshed is because they have fixed something //trying to watch all resource files is complex and this is likely a good enough solution for what is already an edge case boolean restartNeeded = !instrumentationChange && (changedClassResults.isChanged() - || (IsolatedDevModeMain.deploymentProblem != null && userInitiated) || fileRestartNeeded); + || (deploymentProblem.get() != null && userInitiated) || fileRestartNeeded); if (restartNeeded) { String changeString = changedFilesForRestart.stream().map(Path::getFileName).map(Object::toString) .collect(Collectors.joining(", ")); 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 fa2d8b32c4255..48c18f868b7b2 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java @@ -225,6 +225,7 @@ public static void run(Application application, Class