Skip to content

Commit

Permalink
Add virtual-threads support in Quarkus
Browse files Browse the repository at this point in the history
- when an endpoint has the annotation @RunOnVirtualThread its blockingHandler will use a special Excutor spawing a new virtual threads for every request
- added isRunOnVirtualThread method to check that @RunOnVirtualThread is possible (must be on a @Blocking endpoint, the user is warned if the jdk they use to compile is not loom-compliant)
- an endpoint with the @transactional annotation will override the @RunOnVirtualThread annotation -> arbitrary choice of caution (@transactional might use thread locals for instance ?)
- the loom-related pieces are called by reflective calls to avoid dependencies on java 19 (if the runtime is not compliant, the endpoint falls back to traditional workers)

Add a new extension : netty-loom-adaptor that performs bytecode manipulation on netty's PooledByteBufAllocator to use a concurrentHashMap instead of threadLocals
- when creating a DirectBuffer, we check if the current Thread is virtual or not
- if it is, we get its carrier to check if the threadCaches hashMap has the carrier's name as a key
- if so, we return the PoolThreadCache associated to the carrier
- else, we create a new PoolThreadCache (wip) and return it

- after that, we will use this cache instead of the PoolThreadLocalCache

- added static fields to cache isVirtual() method (instead of using reflection to get it for every request)
- added static fields to cache currentCarrierThread method (same goal as for isVirtualMethod field)
- tested quarkus-loom with the application --> from 20 000 reqs during benchmark to ~ 160 000 requests during benchmark (9.5% quarkus perf to ~71% quarkus perf)
- netty-loom-adaptor extension will do its magic only if @RunOnVirtualThread annotations are present in the combinedIndexBuildItem, else it won't do anything

When using quarkus dev services with loom code, the user must specify -Dopen-lang-package. If they don't the reflective operations will throw exceptions
  • Loading branch information
anavarr committed May 24, 2022
1 parent de381dc commit 8da31fd
Show file tree
Hide file tree
Showing 19 changed files with 1,144 additions and 10 deletions.
7 changes: 7 additions & 0 deletions devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
Expand Up @@ -212,6 +212,8 @@ public class DevMojo extends AbstractMojo {
@Parameter(defaultValue = "${debug}")
private String debug;

@Parameter(defaultValue = "${open-lang-package}")
private boolean openJavaLang;
/**
* Whether or not the JVM launch, in debug mode, should be suspended. This parameter is only
* relevant when the JVM is launched in {@link #debug debug mode}. This parameter supports the
Expand Down Expand Up @@ -956,6 +958,11 @@ private QuarkusDevModeLauncher newLauncher() throws Exception {
builder.jvmArgs("-Dio.quarkus.force-color-support=true");
}

if (openJavaLang) {
builder.jvmArgs("--add-opens");
builder.jvmArgs("java.base/java.lang=ALL-UNNAMED");
}

builder.projectDir(project.getFile().getParentFile());
builder.buildSystemProperties((Map) project.getProperties());

Expand Down
52 changes: 52 additions & 0 deletions extensions/netty-loom-adaptor/deployment/pom.xml
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-netty-loom-adaptor-parent</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>quarkus-netty-loom-adaptor-deployment</artifactId>
<name>Quarkus - Netty Loom Adaptor - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-netty-loom-adaptor</artifactId>
<version>999-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-netty-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions extensions/netty-loom-adaptor/pom.xml
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<artifactId>quarkus-extensions-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>quarkus-netty-loom-adaptor-parent</artifactId>
<name>Quarkus - Netty Loom Adaptor</name>
<packaging>pom</packaging>

<modules>
<module>runtime</module>
<module>deployment</module>
</modules>



</project>
61 changes: 61 additions & 0 deletions extensions/netty-loom-adaptor/runtime/pom.xml
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-netty-loom-adaptor-parent</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>quarkus-netty-loom-adaptor</artifactId>
<name>Quarkus - Netty Loom Adaptor - Runtime</name>
<description>Modifies some Netty classes to make it work with loom</description>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>commons-logging-jboss-logging</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-netty</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-bootstrap-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
3 changes: 3 additions & 0 deletions extensions/pom.xml
Expand Up @@ -14,6 +14,8 @@
<name>Quarkus - Extensions - Parent pom</name>
<packaging>pom</packaging>
<modules>
<!-- Netty loom adaptor-->
<module>netty-loom-adaptor</module>
<!-- Plumbing -->
<module>arc</module>
<module>scheduler</module>
Expand Down Expand Up @@ -193,6 +195,7 @@
<module>grpc-common</module>

<module>awt</module>

</modules>

<build>
Expand Down
Expand Up @@ -15,6 +15,7 @@
import io.quarkus.vertx.web.RouteFilter;
import io.quarkus.vertx.web.RoutingExchange;
import io.smallrye.common.annotation.Blocking;
import io.smallrye.common.annotation.RunOnVirtualThread;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.vertx.core.buffer.Buffer;
Expand Down Expand Up @@ -51,6 +52,7 @@ final class DotNames {
static final DotName EXCEPTION = DotName.createSimple(Exception.class.getName());
static final DotName THROWABLE = DotName.createSimple(Throwable.class.getName());
static final DotName BLOCKING = DotName.createSimple(Blocking.class.getName());
static final DotName RUN_ON_VIRTUAL_THREAD = DotName.createSimple(RunOnVirtualThread.class.getName());
static final DotName COMPLETION_STAGE = DotName.createSimple(CompletionStage.class.getName());
static final DotName COMPRESSED = DotName.createSimple(Compressed.class.getName());
static final DotName UNCOMPRESSED = DotName.createSimple(Uncompressed.class.getName());
Expand Down
Expand Up @@ -3,14 +3,18 @@
import static io.quarkus.resteasy.reactive.server.runtime.NotFoundExceptionMapper.classMappers;

import java.io.Closeable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import javax.ws.rs.core.Application;

import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.common.core.SingletonBeanFactory;
import org.jboss.resteasy.reactive.common.model.ResourceContextResolver;
import org.jboss.resteasy.reactive.common.model.ResourceExceptionMapper;
Expand Down Expand Up @@ -55,12 +59,62 @@
@Recorder
public class ResteasyReactiveRecorder extends ResteasyReactiveCommonRecorder implements EndpointInvokerFactory {

static final Logger logger = Logger.getLogger("io.quarkus");

public static final Supplier<Executor> EXECUTOR_SUPPLIER = new Supplier<Executor>() {
@Override
public Executor get() {
return ExecutorRecorder.getCurrent();
}
};
public static final Supplier<Executor> VIRTUAL_EXECUTOR_SUPPLIER = new Supplier<Executor>() {
Executor current = null;

/**
* This method is used to specify a custom executor to dispatch virtual threads on carrier threads
* We need reflection for both ease of use (see {@link #get() Get} method) but also because we call methods
* of private classes from the java.lang package.
*
* It is used for testing purposes only for now
*/
private Executor setVirtualThreadCustomScheduler(Executor executor) throws ClassNotFoundException,
InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
var vtf = Class.forName("java.lang.ThreadBuilders").getDeclaredClasses()[0];
Constructor constructor = vtf.getDeclaredConstructors()[0];
constructor.setAccessible(true);
ThreadFactory tf = (ThreadFactory) constructor.newInstance(
new Object[] { executor, "quarkus-virtual-factory-", 0, 0,
null });

return (Executor) Executors.class.getMethod("newThreadPerTaskExecutor", ThreadFactory.class)
.invoke(this, tf);
}

/**
* This method uses reflection in order to allow developers to quickly test quarkus-loom without needing to
* change --release, --source, --target flags and to enable previews.
* Since we try to load the "Loom-preview" classes/methods at runtime, the application can even be compiled
* using java 11 and executed with a loom-compliant JDK.
*/
@Override
public Executor get() {
if (current == null) {
try {
current = (Executor) Executors.class.getMethod("newVirtualThreadPerTaskExecutor")
.invoke(this);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
System.err.println(e);
//quite ugly but works
logger.warnf("You weren't able to create an executor that spawns virtual threads, the default" +
" blocking executor will be used, please check that your JDK is compatible with " +
"virtual threads");
//if for some reason a class/method can't be loaded or invoked we return the traditional EXECUTOR
current = EXECUTOR_SUPPLIER.get();
}
}
return current;
}
};

static volatile Deployment currentDeployment;

Expand Down Expand Up @@ -114,6 +168,7 @@ public ResteasyReactiveRequestContext createContext(Deployment deployment,
}

RuntimeDeploymentManager runtimeDeploymentManager = new RuntimeDeploymentManager(info, EXECUTOR_SUPPLIER,
VIRTUAL_EXECUTOR_SUPPLIER,
closeTaskHandler, contextFactory, new ArcThreadSetupAction(beanContainer.requestContext()),
vertxConfig.rootPath);
Deployment deployment = runtimeDeploymentManager.deploy();
Expand Down Expand Up @@ -164,6 +219,7 @@ public Application get() {
SingletonBeanFactory.setInstance(i.getClass().getName(), i);
}
applicationSupplier = new Supplier<Application>() {

@Override
public Application get() {
return application;
Expand Down
Expand Up @@ -6,5 +6,6 @@ public enum BlockingDefault {
*/
AUTOMATIC,
BLOCKING,
NON_BLOCKING
NON_BLOCKING,
RUN_ON_VIRTUAL_THREAD
}

0 comments on commit 8da31fd

Please sign in to comment.