Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide infrastructure to debug QuarkusClassLoader lifecyle #41692

Merged
merged 1 commit into from
Jul 5, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@
*/
public class QuarkusClassLoader extends ClassLoader implements Closeable {
private static final Logger log = Logger.getLogger(QuarkusClassLoader.class);
private static final Logger lifecycleLog = Logger.getLogger(QuarkusClassLoader.class.getName() + ".lifecycle");
private static final boolean LOG_ACCESS_TO_CLOSED_CLASS_LOADERS = Boolean
.getBoolean("quarkus-log-access-to-closed-class-loaders");

private static final byte STATUS_OPEN = 1;
private static final byte STATUS_CLOSING = 0;
private static final byte STATUS_CLOSED = -1;

protected static final String META_INF_SERVICES = "META-INF/services/";
protected static final String JAVA = "java.";

Expand Down Expand Up @@ -103,7 +111,7 @@ public static boolean isResourcePresentAtRuntime(String resourcePath) {
private final List<ClassLoaderEventListener> classLoaderEventListeners;

/**
* The element that holds resettable in-memory classses.
* The element that holds resettable in-memory classes.
* <p>
* A reset occurs when new transformers and in-memory classes are added to a ClassLoader. It happens after each
* start in dev mode, however in general the reset resources will be the same. There are some cases where this is
Expand Down Expand Up @@ -131,14 +139,15 @@ public static boolean isResourcePresentAtRuntime(String resourcePath) {
PLATFORM_CLASS_LOADER = cl;
}

private boolean closed;
private volatile byte status;
private volatile boolean driverLoaded;

private QuarkusClassLoader(Builder builder) {
// Not passing the name to the parent constructor on purpose:
// stacktraces become very ugly if we do that.
super(builder.parent);
this.name = builder.name;
this.status = STATUS_OPEN;
this.elements = builder.elements;
this.bannedElements = builder.bannedElements;
this.parentFirstElements = builder.parentFirstElements;
Expand All @@ -151,6 +160,10 @@ private QuarkusClassLoader(Builder builder) {
this.classLoaderEventListeners = builder.classLoaderEventListeners.isEmpty() ? Collections.emptyList()
: builder.classLoaderEventListeners;
setDefaultAssertionStatus(builder.assertionsEnabled);

if (lifecycleLog.isDebugEnabled()) {
lifecycleLog.debugf(new RuntimeException("Created to log a stacktrace"), "Creating class loader %s", this);
}
}

public static Builder builder(String name, ClassLoader parent, boolean parentFirst) {
Expand All @@ -171,6 +184,8 @@ private String sanitizeName(String name) {
* Returns true if the supplied class is a class that would be loaded parent-first
*/
public boolean isParentFirst(String name) {
ensureOpen();

if (name.startsWith(JAVA)) {
return true;
}
Expand Down Expand Up @@ -198,6 +213,8 @@ private boolean parentFirst(String name, ClassLoaderState state) {
}

public void reset(Map<String, byte[]> generatedResources, Map<String, byte[]> transformedClasses) {
ensureOpen();

if (resettableElement == null) {
throw new IllegalStateException("Classloader is not resettable");
}
Expand All @@ -210,10 +227,14 @@ public void reset(Map<String, byte[]> generatedResources, Map<String, byte[]> tr

@Override
public Enumeration<URL> getResources(String unsanitisedName) throws IOException {
ensureOpen();

return getResources(unsanitisedName, false);
}

public Enumeration<URL> getResources(String unsanitisedName, boolean parentAlreadyFoundResources) throws IOException {
ensureOpen();

for (ClassLoaderEventListener l : classLoaderEventListeners) {
l.enumeratingResourceURLs(unsanitisedName, this.name);
}
Expand Down Expand Up @@ -244,7 +265,7 @@ public Enumeration<URL> getResources(String unsanitisedName, boolean parentAlrea
}
}
//TODO: in theory resources could have been added in dev mode
//but I don't thing this really matters for this code path
//but I don't think this really matters for this code path
Set<URL> resources = new LinkedHashSet<>();
ClassPathElement[] providers = state.loadableResources.get(name);
if (providers != null) {
Expand Down Expand Up @@ -356,6 +377,8 @@ private ClassLoaderState getState() {

@Override
public URL getResource(String unsanitisedName) {
ensureOpen();

for (ClassLoaderEventListener l : classLoaderEventListeners) {
l.gettingURLFromResource(unsanitisedName, this.name);
}
Expand Down Expand Up @@ -404,6 +427,8 @@ public URL getResource(String unsanitisedName) {

@Override
public InputStream getResourceAsStream(String unsanitisedName) {
ensureOpen();

for (ClassLoaderEventListener l : classLoaderEventListeners) {
l.openResourceStream(unsanitisedName, this.name);
}
Expand Down Expand Up @@ -455,6 +480,8 @@ public InputStream getResourceAsStream(String unsanitisedName) {
*/
@Override
protected Class<?> findClass(String moduleName, String name) {
ensureOpen();

try {
return loadClass(name, false);
} catch (ClassNotFoundException e) {
Expand All @@ -463,21 +490,29 @@ protected Class<?> findClass(String moduleName, String name) {
}

protected URL findResource(String name) {
ensureOpen();

return getResource(name);
}

@Override
protected Enumeration<URL> findResources(String name) throws IOException {
ensureOpen();

return getResources(name);
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
ensureOpen();

return loadClass(name, false);
}

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
ensureOpen();

for (ClassLoaderEventListener l : classLoaderEventListeners) {
l.loadClass(name, this.name);
}
Expand Down Expand Up @@ -574,10 +609,14 @@ private String getPackageNameFromClassName(String className) {
}

public List<ClassPathElement> getElementsWithResource(String name) {
ensureOpen();

return getElementsWithResource(name, false);
}

public List<ClassPathElement> getElementsWithResource(String name, boolean localOnly) {
ensureOpen();

List<ClassPathElement> ret = new ArrayList<>();
if (parent instanceof QuarkusClassLoader && !localOnly) {
ret.addAll(((QuarkusClassLoader) parent).getElementsWithResource(name));
Expand All @@ -591,6 +630,8 @@ public List<ClassPathElement> getElementsWithResource(String name, boolean local
}

public List<String> getLocalClassNames() {
ensureOpen();

List<String> ret = new ArrayList<>();
for (String name : getState().loadableResources.keySet()) {
if (name.endsWith(".class")) {
Expand All @@ -602,10 +643,14 @@ public List<String> getLocalClassNames() {
}

public Class<?> visibleDefineClass(String name, byte[] b, int off, int len) throws ClassFormatError {
ensureOpen();

return super.defineClass(name, b, off, len);
}

public void addCloseTask(Runnable task) {
ensureOpen();

synchronized (closeTasks) {
closeTasks.add(task);
}
Expand All @@ -614,11 +659,16 @@ public void addCloseTask(Runnable task) {
@Override
public void close() {
synchronized (this) {
if (closed) {
if (status < STATUS_OPEN) {
return;
}
closed = true;
status = STATUS_CLOSING;
}

if (lifecycleLog.isDebugEnabled()) {
lifecycleLog.debugf(new RuntimeException("Created to log a stacktrace"), "Closing class loader %s", this);
}

List<Runnable> tasks;
synchronized (closeTasks) {
tasks = new ArrayList<>(closeTasks);
Expand Down Expand Up @@ -664,10 +714,19 @@ public void close() {
}
ResourceBundle.clearCache(this);

status = STATUS_CLOSED;
}

public boolean isClosed() {
return closed;
return status < STATUS_OPEN;
}

private void ensureOpen() {
if (LOG_ACCESS_TO_CLOSED_CLASS_LOADERS && status == STATUS_CLOSED) {
// we do not use a logger as it might require some class loading
System.out.println("Class loader " + this + " has been closed and may not be accessed anymore");
Thread.dumpStack();
gsmet marked this conversation as resolved.
Show resolved Hide resolved
}
}

@Override
Expand Down
Loading