Open pompiuses opened 1 year ago
The fix would mean rewriting ConcurrencyUtil.kt to either not use reflection, or refactor it in such a way that it works with GraalVM.
How can we achieve that while still targeting Java 17?
Not sure, that would have to be looked into. The first thing that comes to mind is that it's maybe possible to refactor ReflectiveVirtualThreadBuilder so that the "unstarted" method gets correctly invoked by GraalVMs native-image compiler. One would have to experiment with different solutions until one finds something that works. How does the other frameworks solve this problem (Spring, Micronaut etc)?
One possible solution could be to create a single instance of a virtual thread factory (Thread.ofVirtual().factory()
) to create new threads with instead of Thread.Builder.OfVirtual
in ReflectiveVirtualThreadBuilder
. Then you won't have to call the unstarted
method which GraalVM has problems with. It'd probably also be a slightly more efficient implementation than instantiating a new builder for each thread.
I got around this issue when upgrading to Javalin 6. I can now set the Jetty thread pool directly myself.
Javalin javalin = Javalin.create(config -> {
// Using a custom thread pool for virtual threads instead of the default Javalin LoomUtil.LoomThreadPool.
// This is because it uses reflection to create new threads which causes an error in GraalVM.
config.jetty.threadPool = new ThreadPool() {
@Override
public void join() { /*no-op*/ }
@Override
public int getThreads() {
return 1;
}
@Override
public int getIdleThreads() {
return 1;
}
@Override
public boolean isLowOnThreads() {
return false;
}
@Override
public void execute(@NonNull Runnable command) {
AppConfig.virtualThreadFactory.newThread(command).start();
}
};
});
For reference I previously used the GraalVM SDK to override the use of reflection in Javalin's NamedVirtualThreadFactory. This has now been removed.
import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import io.javalin.util.NamedVirtualThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
@TargetClass(NamedVirtualThreadFactory.class)
public final class Target_io_javalin_util_NamedVirtualThreadFactory {
@Alias
AtomicInteger threadCount;
@Alias
String prefix;
@Substitute
public Thread newThread(Runnable runnable) {
return Thread.ofVirtual()
.name(prefix + "-Virtual-" + threadCount.getAndIncrement())
.unstarted(runnable);
}
}
I assume this issue can be closed. Maybe the Javalin documentation can be updated with the code above in case someone else tries to use Javalin with GraalVM?
Is your feature request related to a problem? Please describe. I've been looking into having Javalin run on GraalVM. In that process I ran into a bug in GraalVM as described here. This bug prevents you from running Javalin with virtual threads in a native image.
The main problem is that GraalVM fails to correctly handle a reflective call in ConcurrencyUtil.kt when creating a new virtual thread.
Even though this isn't Javalins fault it will still block anyone who wants to use GraalVM and Javalin. I also suspect this bug wont get fixed for a while at GraalVMs side. Since the official release of virtual threads in Java 21 is just around the corner I think it'd be beneficial if this problem could get fixed at Javalins side before Java 21 is released.
Describe the solution you'd like The fix would mean rewriting ConcurrencyUtil.kt to either not use reflection, or refactor it in such a way that it works with GraalVM.
Additional context As a sidenote I have managed to create a temporary workaround as described in this thread in the GraalVM slack chat.