classpath icon indicating copy to clipboard operation
classpath copied to clipboard

install-priority-loader! throws when virtual threads are present

Open AlexChalk opened this issue 1 year ago • 0 comments

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-L91
  • InnocuousThread (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!

AlexChalk avatar May 24 '24 16:05 AlexChalk