javareflectionmodule-infoproject-loomvirtual-threads

How to detect virtual threads on java 19


Lets say I want to ship a program that runs on java 17 as that is what is available widely, but use reflection to detect if im running on a vm with the ability to produce a thread factory via Thread.ofVirtual().name("abc").factory(). Java prohibits reflective access to its internals when they are not properly configured with modules. How do I configure my program to be able to access this method reflectively? The reason for reflective access is to continue compiling into <jdk19 bytecode, but use reflection to use jdk19 features if they are present. Is there a combination of arguments or module-info.java contents that can achieve this goal, or is this not possible?

when you try this in jshell, here is what you get:

jshell --enable-preview
|  Welcome to JShell -- Version 19.0.2
|  For an introduction type: /help intro

jshell> Thread.class.getMethod("ofVirtual")
   ...>                 .invoke(null)
   ...>                 .getClass()
   ...>                 .getMethod("name", String.class, Long.TYPE)
   ...>                 .setAccessible(true)
|  Exception java.lang.reflect.InaccessibleObjectException: Unable to make public java.lang.Thread$Builder$OfVirtual java.lang.ThreadBuilders$VirtualThreadBuilder.name(java.lang.String,long) accessible: module java.base does not "opens java.lang" to unnamed module @30dae81
|        at AccessibleObject.throwInaccessibleObjectException (AccessibleObject.java:387)
|        at AccessibleObject.checkCanSetAccessible (AccessibleObject.java:363)
|        at AccessibleObject.checkCanSetAccessible (AccessibleObject.java:311)
|        at Method.checkCanSetAccessible (Method.java:201)
|        at Method.setAccessible (Method.java:195)
|        at (#1:5)

Exception java.lang.reflect.InaccessibleObjectException: Unable to make public java.lang.Thread$Builder$OfVirtual java.lang.ThreadBuilders$VirtualThreadBuilder.name(java.lang.String,long) accessible: module java.base does not "opens java.lang" to unnamed module @30dae81

adding required java.base; into the module-info.java does not seem to change the outcome either:

// src/main/java/module-info.java
module test_20230518_ {
    requires java.base;
}
// src/main/java/a/A.java

package a;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.ThreadFactory;

public class A {
    public static void main(String[] args) {
        ThreadFactory threadFactory = tf();
        threadFactory.newThread(() ->
                System.out.println("hi from " +
                        Thread.currentThread().getName()));
    }

    private static ThreadFactory tf() {
        Method[] methods = Thread.class.getMethods();
        boolean haveVirtual = Arrays.stream(methods)
                .anyMatch(m -> m.getName().equals("ofVirtual") &&
                        m.getParameterCount() == 0);

        if (haveVirtual) {
            try {
                Object b = Thread.class.getMethod("ofVirtual")
                        .invoke(null);
                b = b.getClass().getMethod("name", String.class, Long.TYPE)
                        .invoke(b, "prefix-", (long) 1);
                b = b.getClass().getMethod("factory")
                        .invoke(b);
                return (ThreadFactory) b;
            } catch (Throwable t) {
                throw new RuntimeException(t);
            }
        } else {
            return Thread::new;
        }
    }
}

still produces:

Exception in thread "main" java.lang.RuntimeException: java.lang.IllegalAccessException: class a.A cannot access a member of class java.lang.ThreadBuilders$VirtualThreadBuilder (in module java.base) with modifiers "public volatile"
    at a.A.tf(A.java:31)
    at a.A.main(A.java:9)
Caused by: java.lang.IllegalAccessException: class a.A cannot access a member of class java.lang.ThreadBuilders$VirtualThreadBuilder (in module java.base) with modifiers "public volatile"
    at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:420)
    at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:709)
    at java.base/java.lang.reflect.Method.invoke(Method.java:569)
    at a.A.tf(A.java:26)
    ... 1 more

class a.A cannot access a member of class java.lang.ThreadBuilders$VirtualThreadBuilder (in module java.base) with modifiers "public volatile"


Solution

  • You can use the Expression class from java.beans package (from java.desktop module)

     import java.beans.Expression;
     var builder = new Expression(Thread.class, "ofVirtual", null).getValue();
     builder = new Expression(builder, "name", new Object[] {"ABC"}).getValue();
     var factory = new Expression(builder, "factory", null).getValue();