javalin / website

Javalin website source code
https://javalin.io
36 stars 87 forks source link

Make Javalin more GraalVM friendly #236

Open pompiuses opened 1 year ago

pompiuses commented 1 year ago

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.

tipsy commented 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?

pompiuses commented 1 year ago

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)?

pompiuses commented 1 year ago

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.

pompiuses commented 3 months ago

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);
    }
}
pompiuses commented 2 months ago

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?