Skip to content

Commit

Permalink
Improve virtual thread related validation in RESTEasy Reactive
Browse files Browse the repository at this point in the history
The changes include:

* Validation is only performed on methods that are annotated with `@RunOnVirtualThread`
* Warnings about inability to use virtual threads have been converted to errors
* Additional validation is performed regarding the target version of the project
  • Loading branch information
geoand committed May 25, 2022
1 parent 81b9cfa commit cc53ddf
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public interface JavaVersion {

Status isJava17OrHigher();

Status isJava19OrHigher();

enum Status {
TRUE,
FALSE,
Expand Down Expand Up @@ -57,12 +59,18 @@ public Status isJava11OrHigher() {
public Status isJava17OrHigher() {
return Status.UNKNOWN;
}

@Override
public Status isJava19OrHigher() {
return Status.UNKNOWN;
}
}

final class Known implements JavaVersion {

private static final int JAVA_11_MAJOR = 55;
private static final int JAVA_17_MAJOR = 61;
private static final int JAVA_19_MAJOR = 63;

private final int determinedMajor;

Expand All @@ -85,6 +93,11 @@ public Status isJava17OrHigher() {
return higherOrEqualStatus(JAVA_17_MAJOR);
}

@Override
public Status isJava19OrHigher() {
return higherOrEqualStatus(JAVA_19_MAJOR);
}

private Status higherOrEqualStatus(int javaMajor) {
return determinedMajor >= javaMajor ? Status.TRUE : Status.FALSE;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import org.jboss.resteasy.reactive.common.processor.DefaultProducesHandler;
import org.jboss.resteasy.reactive.common.processor.EndpointIndexer;
import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames;
import org.jboss.resteasy.reactive.common.processor.TargetJavaVersion;
import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult;
import org.jboss.resteasy.reactive.common.processor.scanning.ResourceScanningResult;
import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationsTransformer;
Expand Down Expand Up @@ -117,6 +118,7 @@
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem;
import io.quarkus.deployment.configuration.ConfigurationError;
import io.quarkus.deployment.pkg.builditem.CompiledJavaVersionBuildItem;
import io.quarkus.deployment.recording.RecorderContext;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.MethodCreator;
Expand Down Expand Up @@ -359,6 +361,7 @@ public void setupEndpoints(ApplicationIndexBuildItem applicationIndexBuildItem,
List<MethodScannerBuildItem> methodScanners,
List<AnnotationsTransformerBuildItem> annotationTransformerBuildItems,
List<ContextTypeBuildItem> contextTypeBuildItems,
CompiledJavaVersionBuildItem compiledJavaVersionBuildItem,
Capabilities capabilities)
throws NoSuchMethodException {

Expand Down Expand Up @@ -514,7 +517,28 @@ private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName an
}
})
.setResteasyReactiveRecorder(recorder)
.setApplicationClassPredicate(applicationClassPredicate);
.setApplicationClassPredicate(applicationClassPredicate)
.setTargetJavaVersion(new TargetJavaVersion() {

private final Status result;

{
CompiledJavaVersionBuildItem.JavaVersion.Status status = compiledJavaVersionBuildItem
.getJavaVersion().isJava19OrHigher();
if (status == CompiledJavaVersionBuildItem.JavaVersion.Status.FALSE) {
result = Status.FALSE;
} else if (status == CompiledJavaVersionBuildItem.JavaVersion.Status.TRUE) {
result = Status.TRUE;
} else {
result = Status.UNKNOWN;
}
}

@Override
public Status isJava19OrHigher() {
return result;
}
});

if (!serverDefaultProducesHandlers.isEmpty()) {
List<DefaultProducesHandler> handlers = new ArrayList<>(serverDefaultProducesHandlers.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
import org.jboss.resteasy.reactive.common.model.ParameterType;
import org.jboss.resteasy.reactive.common.model.ResourceClass;
import org.jboss.resteasy.reactive.common.model.ResourceMethod;
import org.jboss.resteasy.reactive.common.processor.TargetJavaVersion.Status;
import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult;
import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationStore;
import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationsTransformer;
Expand Down Expand Up @@ -159,6 +160,8 @@ public abstract class EndpointIndexer<T extends EndpointIndexer<T, PARAM, METHOD
public static final String METHOD_CONTEXT_ANNOTATION_STORE = "ANNOTATION_STORE";
public static final String METHOD_PRODUCES = "METHOD_PRODUCES";

private static final boolean JDK_SUPPORTS_VIRTUAL_THREADS;

static {
Map<String, String> prims = new HashMap<>();
prims.put(byte.class.getName(), Byte.class.getName());
Expand Down Expand Up @@ -195,6 +198,14 @@ public abstract class EndpointIndexer<T extends EndpointIndexer<T, PARAM, METHOD
supportedReaderJavaTps.put(BIG_DECIMAL, BigDecimal.class);
supportedReaderJavaTps.put(BIG_INTEGER, BigInteger.class);
supportedReaderJavaTypes = Collections.unmodifiableMap(supportedReaderJavaTps);

boolean isJDKCompatible = true;
try {
Class.forName("java.lang.ThreadBuilders");
} catch (ClassNotFoundException e) {
isJDKCompatible = false;
}
JDK_SUPPORTS_VIRTUAL_THREADS = isJDKCompatible;
}

protected final IndexView index;
Expand All @@ -217,6 +228,7 @@ public abstract class EndpointIndexer<T extends EndpointIndexer<T, PARAM, METHOD
private final Set<DotName> contextTypes;
private final MultipartReturnTypeIndexerExtension multipartReturnTypeIndexerExtension;
private final MultipartParameterIndexerExtension multipartParameterIndexerExtension;
private final TargetJavaVersion targetJavaVersion;

protected EndpointIndexer(Builder<T, ?, METHOD> builder) {
this.index = builder.index;
Expand All @@ -238,6 +250,7 @@ protected EndpointIndexer(Builder<T, ?, METHOD> builder) {
this.contextTypes = builder.contextTypes;
this.multipartReturnTypeIndexerExtension = builder.multipartReturnTypeIndexerExtension;
this.multipartParameterIndexerExtension = builder.multipartParameterIndexerExtension;
this.targetJavaVersion = builder.targetJavaVersion;
}

public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean considerApplication) {
Expand Down Expand Up @@ -700,20 +713,6 @@ private String getAnnotationValueAsString(AnnotationTarget target, DotName annot

private boolean isRunOnVirtualThread(MethodInfo info, BlockingDefault defaultValue) {
boolean isRunOnVirtualThread = false;
boolean isJDKCompatible = true;
try {
Class.forName("java.lang.ThreadBuilders");
} catch (ClassNotFoundException e) {
isJDKCompatible = false;
}

if (!isJDKCompatible) {
log.warn("Your version of the JDK is '" + Runtime.version() +
"' and doesn't support Loom's virtual threads" +
", your runtime will have to use jdk-19-loom or superior to leverage virtual threads " +
"(else java platform threads will be used instead).");
}

Map.Entry<AnnotationTarget, AnnotationInstance> runOnVirtualThreadAnnotation = getInheritableAnnotation(info,
RUN_ON_VIRTUAL_THREAD);

Expand All @@ -725,6 +724,15 @@ private boolean isRunOnVirtualThread(MethodInfo info, BlockingDefault defaultVal
}

if (runOnVirtualThreadAnnotation != null) {
if (!JDK_SUPPORTS_VIRTUAL_THREADS) {
throw new DeploymentException("Method '" + info.name() + "' of class '" + info.declaringClass().name()
+ "' uses @RunOnVirtualThread but the JDK version '" + Runtime.version() +
"' and doesn't support virtual threads");
}
if (targetJavaVersion.isJava19OrHigher() == Status.FALSE) {
throw new DeploymentException("Method '" + info.name() + "' of class '" + info.declaringClass().name()
+ "' uses @RunOnVirtualThread but the target JDK version doesn't support virtual threads. Please configure your build tool to target Java 19 or above");
}
isRunOnVirtualThread = true;
}

Expand Down Expand Up @@ -1403,6 +1411,7 @@ public boolean handleMultipartForReturnType(AdditionalWriters additionalWriters,
public void handleMultipartParameter(ClassInfo multipartClassInfo, IndexView indexView) {
}
};
private TargetJavaVersion targetJavaVersion = new TargetJavaVersion.Unknown();

public B setMultipartReturnTypeIndexerExtension(MultipartReturnTypeIndexerExtension multipartReturnTypeHandler) {
this.multipartReturnTypeIndexerExtension = multipartReturnTypeHandler;
Expand Down Expand Up @@ -1504,6 +1513,11 @@ public B setApplicationScanningResult(ApplicationScanningResult applicationScann
return (B) this;
}

public B setTargetJavaVersion(TargetJavaVersion targetJavaVersion) {
this.targetJavaVersion = targetJavaVersion;
return (B) this;
}

public abstract T build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.jboss.resteasy.reactive.common.processor;

/**
* Used to determine the java version of the compiled Java code
*/
public interface TargetJavaVersion {

Status isJava19OrHigher();

enum Status {
TRUE,
FALSE,
UNKNOWN
}

final class Unknown implements TargetJavaVersion {

public static final Unknown INSTANCE = new Unknown();

Unknown() {
}

@Override
public Status isJava19OrHigher() {
return Status.UNKNOWN;
}
}
}

0 comments on commit cc53ddf

Please sign in to comment.