Skip to content

Commit

Permalink
Aggressively clear the fields of the QuarkusClassLoader on close
Browse files Browse the repository at this point in the history
We know that the class loaders can leak, for instance due to long lived
resources that span the boundaries of a test so we try to limit the
effects by clearing the fields from the class loader and especially all
the ClassPathElements.

Note that for now, I didn't nullify the parent field that points to the
parent CL but I wonder if we should do it.

We also add some logging to debug the lifecycle of the class loader. We
can't easily log things in the close() method so things are not as clean
as they could be. That's the reason why we are using a system property
to enable the logging.

You can log the constructor and close() calls by enabling debug logging
for category
`io.quarkus.bootstrap.classloading.QuarkusClassLoader.lifecycle`.

You can log late accesses to closed class loaders by passing
`-Dquarkus-log-access-to-closed-class-loaders` to your build command.
  • Loading branch information
gsmet committed Jul 7, 2024
1 parent c629bfe commit d00b246
Showing 1 changed file with 26 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -692,17 +692,27 @@ public void close() {
log.debug("Failed to clean up DB drivers");
}
}
for (ClassPathElement element : elements) {
//note that this is a 'soft' close
//all resources are closed, however the CL can still be used
//but after close no resources will be held past the scope of an operation
try (ClassPathElement ignored = element) {
//the close() operation is implied by the try-with syntax
} catch (Exception e) {
log.error("Failed to close " + element, e);
}
closeClassPathElements(elements);
closeClassPathElements(bannedElements);
closeClassPathElements(parentFirstElements);
closeClassPathElements(lesserPriorityElements);

definedPackages.clear();
resettableElement = null;
transformedClasses = null;
if (state != null) {
state.clear();
}
for (ClassPathElement element : bannedElements) {
closeTasks.clear();
classLoaderEventListeners.clear();

ResourceBundle.clearCache(this);

status = STATUS_CLOSED;
}

private static void closeClassPathElements(List<ClassPathElement> classPathElements) {
for (ClassPathElement element : classPathElements) {
//note that this is a 'soft' close
//all resources are closed, however the CL can still be used
//but after close no resources will be held past the scope of an operation
Expand All @@ -712,9 +722,7 @@ public void close() {
log.error("Failed to close " + element, e);
}
}
ResourceBundle.clearCache(this);

status = STATUS_CLOSED;
classPathElements.clear();
}

public boolean isClosed() {
Expand Down Expand Up @@ -900,6 +908,11 @@ static final class ClassLoaderState {
this.bannedResources = bannedResources;
this.parentFirstResources = parentFirstResources;
}

void clear() {
// when the CL is closed, we make sure the resources are not loadable anymore
loadableResources.clear();
}
}

@Override
Expand Down

0 comments on commit d00b246

Please sign in to comment.