Skip to content

Commit

Permalink
Fix metaspace leak through unreleased config on devmode restart
Browse files Browse the repository at this point in the history
Some config was lazily created and bound to the dev class loader
through the configsForClassLoader map
io.smallrye.config.SmallRyeConfigProviderResolver#getConfig(java.lang.ClassLoader),
and the corresponding map entry was never cleared,
resulting in the corresponding classloader never being garbage
collected.
  • Loading branch information
yrodiere committed Oct 12, 2023
1 parent 91cce5e commit 702ca52
Showing 1 changed file with 21 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import io.quarkus.runtime.ApplicationLifecycleManager;
import io.quarkus.runtime.configuration.QuarkusConfigFactory;
import io.quarkus.runtime.logging.LoggingSetupRecorder;
import io.smallrye.config.SmallRyeConfigProviderResolver;

public class IsolatedDevModeMain implements BiConsumer<CuratedApplication, Map<String, Object>>, Closeable {

Expand Down Expand Up @@ -291,19 +292,36 @@ public void stop() {
Thread.currentThread().setContextClassLoader(old);
}
}
QuarkusConfigFactory.setConfig(null);

// There may be multiple SmallryeConfigProviderResolver
// instances in multiple classloaders, and each of those may have registered config.
// Ideally we'd avoid getting in this situation,
// but due to config being lazily created in ConfigProviderResolver#getConfig,
// this is challenging.
// To avoid any leak, we try to clean up config in all relevant classloaders.
releaseConfig(Thread.currentThread().getContextClassLoader());
releaseConfig(getClass().getClassLoader());
}

private void releaseConfig(ClassLoader contextClassLoader) {
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
Thread.currentThread().setContextClassLoader(contextClassLoader);
try {
QuarkusConfigFactory.setConfig(null);
final ConfigProviderResolver cpr = ConfigProviderResolver.instance();
if (runner != null) {
// Clean up all references to the runner class loader,
// which may be using implicit config
// lazily created by ConfigProviderResolver#getConfig
((SmallRyeConfigProviderResolver) cpr).releaseConfig(runner.getClassLoader());
}
// Clean up all references to classloaders using the explicitly registered config
cpr.releaseConfig(cpr.getConfig());
} catch (Throwable ignored) {
// just means no config was installed, which is fine
} finally {
Thread.currentThread().setContextClassLoader(old);
}
runner = null;
}

public void close() {
Expand Down

0 comments on commit 702ca52

Please sign in to comment.