classpath
classpath copied to clipboard
install-priority-loader! throws when virtual threads are present
After bumping jdk to 21 and a dependency (logback 1.5.X) to a version that uses virtual threads, I'm getting the following error when calling licp/install-priority-loader!:
java.lang.SecurityException: setContextClassLoader
at java.base/jdk.internal.misc.CarrierThread.setContextClassLoader(CarrierThread.java:90)
at lambdaisland.classpath$install_priority_loader_BANG_$fn__34802.invoke(classpath.clj:404)
at clojure.core$binding_conveyor_fn$fn__5823.invoke(core.clj:2047)
at clojure.lang.AFn.call(AFn.java:18)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
I created an issue in the logback repo, and the change which caused this is their introduction of virtual threads: https://github.com/qos-ch/logback/issues/815.
When using virtual threads, it looks like install-priority-loader!'s business logic tries to call .setContextClassLoader on at least two classes that will reliably throw:
CarrierThread(always throws) https://github.com/openjdk/jdk22/blob/fe9f05023e5a916b21e2db72fa5b1e8368a2c07d/src/java.base/share/classes/jdk/internal/misc/CarrierThread.java#L89-L91InnocuousThread(always throws for non-null values) https://github.com/openjdk/jdk22/blob/fe9f05023e5a916b21e2db72fa5b1e8368a2c07d/src/java.base/share/classes/jdk/internal/misc/InnocuousThread.java#L163-L169
In my case, .setContextClassLoader is attempted by licp because (= (app-loader) (context-classloader thread)) is sometimes true (both are sometimes jdk.internal.loader.ClassLoaders$AppClassLoader@15327b79), even if the thread class doesn't support .setContextClassLoader.
I don't understand the logic behind checking for (= (app-loader) (context-classloader thread)), so my proposed solution might not make sense. However, one change that at least avoids the exception is to check whether a thread is an instance of one of the problematic classes, and not to attempt calling .setContextClassLoader when it is. I implemented this logic here: https://github.com/lambdaisland/classpath/compare/main...AlexChalk:jdk-21-virtual-threads
Does the above seem like a reasonable solution to you? Thanks!