From c8e4f538b455f4c4890815eb330fbffca6416e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Aug 2023 10:57:51 +0200 Subject: [PATCH 1/8] Clean up VertxRecorder.vertx reference on Quarkus shutdown To avoid memory leaks caused by references to closed threads. --- .../src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java index 12dbb305919cf..bd2e203ea3dd0 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java @@ -77,6 +77,7 @@ public static Vertx getVertx() { void destroy() { messageConsumers = null; + vertx = null; } void registerMessageConsumers(Map messageConsumerConfigurations) { From 3a3209540937ff91a46bd45ec1115bd328b1e260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Aug 2023 11:42:53 +0200 Subject: [PATCH 2/8] Clean up TestSupport's watched files listener upon stopping tests --- .../java/io/quarkus/deployment/dev/testing/TestSupport.java | 4 ++++ .../main/java/io/quarkus/dev/testing/TestWatchedFiles.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java index a03138e2f7e3a..50fa7935ddc2a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java @@ -17,7 +17,9 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -322,6 +324,8 @@ public synchronized void stop() { for (var runner : moduleRunners) { runner.abort(); } + TestWatchedFiles.setWatchedFilesListener( + (BiConsumer, List, Boolean>>>) null); } public void runTests() { diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/testing/TestWatchedFiles.java b/core/devmode-spi/src/main/java/io/quarkus/dev/testing/TestWatchedFiles.java index 2744d7f636778..134b26f1f333c 100644 --- a/core/devmode-spi/src/main/java/io/quarkus/dev/testing/TestWatchedFiles.java +++ b/core/devmode-spi/src/main/java/io/quarkus/dev/testing/TestWatchedFiles.java @@ -62,7 +62,7 @@ public synchronized static void setWatchedFilePaths(Map watched public synchronized static void setWatchedFilesListener( BiConsumer, List, Boolean>>> watchedFilesListener) { TestWatchedFiles.watchedFilesListener = watchedFilesListener; - if (watchedFilePaths != null) { + if (watchedFilesListener != null && watchedFilePaths != null) { watchedFilesListener.accept(watchedFilePaths, watchedFilePredicates); } } From 0dd08c5b0d8611909d6181fea73dffab16bbe22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Aug 2023 11:56:04 +0200 Subject: [PATCH 3/8] Clean up Vertx MDCProvider on shutdown Because LateBoundMDCProvider lives in the AppClassLoader, so it must not reference instances of classes in QuarkusClassLoader after shutdown. --- .../java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java index 5d578a4160c90..26e0cbb4ad1c9 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java @@ -423,6 +423,7 @@ public void handle(AsyncResult ar) { Thread.currentThread().interrupt(); throw new IllegalStateException("Exception when closing Vert.x instance", e); } + LateBoundMDCProvider.setMDCProviderDelegate(null); vertx = null; } } From 9b49fe891c0bc957fc916d544115ad52a0e43a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Aug 2023 12:06:23 +0200 Subject: [PATCH 4/8] Avoid unnecessary references to the QuarkusClassLoader in shutdown tasks of NioThreadPoolRecorder --- .../runtime/dev/io/NioThreadPoolRecorder.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/dev/io/NioThreadPoolRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/dev/io/NioThreadPoolRecorder.java index c14b2829d6731..0138b5bf85585 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/dev/io/NioThreadPoolRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/dev/io/NioThreadPoolRecorder.java @@ -8,12 +8,17 @@ public class NioThreadPoolRecorder { public void updateTccl(ShutdownContext context) { - ClassLoader old = NioThreadPoolThreadFactory.updateTccl(Thread.currentThread().getContextClassLoader()); - context.addLastShutdownTask(new Runnable() { - @Override - public void run() { - NioThreadPoolThreadFactory.updateTccl(old); - } - }); + ClassLoader newTccl = Thread.currentThread().getContextClassLoader(); + ClassLoader oldTccl = NioThreadPoolThreadFactory.updateTccl(newTccl); + if (newTccl != oldTccl) { + context.addLastShutdownTask(new Runnable() { + @Override + public void run() { + NioThreadPoolThreadFactory.updateTccl(oldTccl); + } + }); + } + // Else: don't add an unnecessary shutdown task that may hold a reference to a QuarkusClassLoader, + // which could be a problem with QuarkusUnitTest since it creates one classloader per test. } } From 43f140a8e56e1b90b1b0d02c65a4dbebf9bf514a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Aug 2023 15:36:54 +0200 Subject: [PATCH 5/8] Unregister proxy factories when their target classloader gets closed Instead of when the proxied class' classloader gets closed, because the proxied class can simply be in the AppClassLoader which never gets closed. --- .../main/java/io/quarkus/deployment/proxy/ProxyFactory.java | 4 ++++ .../deployment/recording/RecordingProxyFactories.java | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/proxy/ProxyFactory.java b/core/deployment/src/main/java/io/quarkus/deployment/proxy/ProxyFactory.java index 7b379d7c1d5af..571c1fcb5232d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/proxy/ProxyFactory.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/proxy/ProxyFactory.java @@ -79,6 +79,10 @@ public ProxyFactory(ProxyConfiguration configuration) { } } + public ClassLoader getClassLoader() { + return classLoader; + } + private boolean findConstructor(Class clazz, boolean allowPackagePrivate, boolean allowInject) { Constructor[] ctors = clazz.getDeclaredConstructors(); if (allowInject) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/RecordingProxyFactories.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/RecordingProxyFactories.java index 29f39a82f0c86..73f6c009a55a3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/recording/RecordingProxyFactories.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/RecordingProxyFactories.java @@ -13,8 +13,9 @@ class RecordingProxyFactories { static void put(Class clazz, ProxyFactory proxyFactory) { RECORDING_PROXY_FACTORIES.put(clazz, proxyFactory); - if (clazz.getClassLoader() instanceof QuarkusClassLoader) { - ((QuarkusClassLoader) clazz.getClassLoader()).addCloseTask(new Runnable() { + ClassLoader proxyClassLoader = proxyFactory.getClassLoader(); + if (proxyClassLoader instanceof QuarkusClassLoader) { + ((QuarkusClassLoader) proxyClassLoader).addCloseTask(new Runnable() { @Override public void run() { RecordingProxyFactories.RECORDING_PROXY_FACTORIES.remove(clazz); From 3933295b3118726f0d257e37924876cf13551c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Aug 2023 15:43:54 +0200 Subject: [PATCH 6/8] Ensure RuntimeUpdatesProcessor gets garbage-collected after shutdown --- .../java/io/quarkus/deployment/dev/IsolatedDevModeMain.java | 2 ++ .../io/quarkus/deployment/dev/IsolatedRemoteDevModeMain.java | 2 ++ .../java/io/quarkus/deployment/dev/IsolatedTestModeMain.java | 2 ++ .../java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java | 2 +- 4 files changed, 7 insertions(+), 1 deletion(-) 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 745e6a94317b2..25ef8e28951fd 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 @@ -321,6 +321,8 @@ public void close() { RuntimeUpdatesProcessor.INSTANCE.close(); } catch (IOException e) { log.error("Failed to close compiler", e); + } finally { + RuntimeUpdatesProcessor.INSTANCE = null; } } for (HotReplacementSetup i : hotReplacementSetups) { 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 4eb1f162bbf56..b01dce10be579 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 @@ -176,6 +176,8 @@ public void close() { RuntimeUpdatesProcessor.INSTANCE.close(); } catch (IOException e) { log.error("Failed to close compiler", e); + } finally { + RuntimeUpdatesProcessor.INSTANCE = null; } for (HotReplacementSetup i : hotReplacementSetups) { i.close(); 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 dce3eb0cfc3cb..e58029cd79cf2 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 @@ -90,6 +90,8 @@ public void close() { RuntimeUpdatesProcessor.INSTANCE.close(); } catch (IOException e) { e.printStackTrace(); + } finally { + RuntimeUpdatesProcessor.INSTANCE = null; } for (HotReplacementSetup i : hotReplacementSetups) { i.close(); 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 c6066e29c3890..6b14e7198c9f3 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 @@ -229,7 +229,7 @@ public static void shutDownDevMode() { } rootHandler = null; hotReplacementHandler = null; - + hotReplacementContext = null; } public static void startServerAfterFailedStart() { From 14739afc7343c6da2297eabecd8945af383abfcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Aug 2023 16:56:30 +0200 Subject: [PATCH 7/8] Clean up ClassTransformingBuildStep's static variable between each test run --- .../deployment/steps/ClassTransformingBuildStep.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassTransformingBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassTransformingBuildStep.java index a0f7c275eda8e..f9232542a3a8c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassTransformingBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassTransformingBuildStep.java @@ -40,6 +40,7 @@ import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.ArchiveRootBuildItem; import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; +import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.LiveReloadBuildItem; import io.quarkus.deployment.builditem.RemovedResourceBuildItem; @@ -71,13 +72,19 @@ public static byte[] transform(String className, byte[] classData) { return lastTransformers.apply(className, classData); } + private static void reset() { + lastTransformers = null; + transformedClassesCache.clear(); + } + @BuildStep TransformedClassesBuildItem handleClassTransformation(List bytecodeTransformerBuildItems, ApplicationArchivesBuildItem appArchives, LiveReloadBuildItem liveReloadBuildItem, LaunchModeBuildItem launchModeBuildItem, ClassLoadingConfig classLoadingConfig, CurateOutcomeBuildItem curateOutcomeBuildItem, List removedResourceBuildItems, ArchiveRootBuildItem archiveRoot, LaunchModeBuildItem launchMode, PackageConfig packageConfig, - ExecutorService buildExecutor) + ExecutorService buildExecutor, + CuratedApplicationShutdownBuildItem shutdown) throws ExecutionException, InterruptedException { if (bytecodeTransformerBuildItems.isEmpty() && classLoadingConfig.removedResources.isEmpty() && removedResourceBuildItems.isEmpty()) { @@ -117,6 +124,7 @@ TransformedClassesBuildItem handleClassTransformation(List> transformed = new ConcurrentLinkedDeque<>(); final Map> transformedClassesByJar = new HashMap<>(); ClassLoader transformCl = Thread.currentThread().getContextClassLoader(); + shutdown.addCloseTask(ClassTransformingBuildStep::reset, true); lastTransformers = new BiFunction() { @Override public byte[] apply(String className, byte[] originalBytes) { From bbaa3050583223e474cbb4eac80a4dfd1334e5d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Aug 2023 18:01:47 +0200 Subject: [PATCH 8/8] Clean up CurrentConfig on shutdown To avoid ClassLoader leaks. --- .../devui/deployment/menu/ConfigurationProcessor.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ConfigurationProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ConfigurationProcessor.java index 6818ff8fb5c38..2f2842e10d1b4 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ConfigurationProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ConfigurationProcessor.java @@ -24,6 +24,7 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ConfigDescriptionBuildItem; +import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; import io.quarkus.deployment.builditem.DevServicesLauncherConfigResultBuildItem; import io.quarkus.dev.config.CurrentConfig; import io.quarkus.dev.console.DevConsoleManager; @@ -95,7 +96,8 @@ void registerConfigs(List configDescriptionBuildItem void registerJsonRpcService( BuildProducer jsonRPCProvidersProducer, BuildProducer syntheticBeanProducer, - ConfigDevUIRecorder recorder) { + ConfigDevUIRecorder recorder, + CuratedApplicationShutdownBuildItem shutdown) { DevConsoleManager.register("config-update-property", map -> { Map values = Collections.singletonMap(map.get("name"), map.get("value")); @@ -116,6 +118,13 @@ void registerJsonRpcService( .done()); CurrentConfig.EDITOR = ConfigurationProcessor::updateConfig; + shutdown.addCloseTask(new Runnable() { + @Override + public void run() { + CurrentConfig.EDITOR = null; + CurrentConfig.CURRENT = Collections.emptyList(); + } + }, true); jsonRPCProvidersProducer.produce(new JsonRPCProvidersBuildItem("devui-configuration", ConfigJsonRPCService.class)); }