Skip to content

Commit

Permalink
Merge pull request quarkusio#44925 from Malandril/websockets-next_vir…
Browse files Browse the repository at this point in the history
…tual_threads_on_class

Add support of @RunOnVirtualThread on class for websockets next
  • Loading branch information
mkouba authored Dec 10, 2024
2 parents f60a94b + 890c787 commit c326bec
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 6 deletions.
12 changes: 8 additions & 4 deletions docs/src/main/asciidoc/websockets-next-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -240,15 +240,19 @@ WebSocket Next supports _blocking_ and _non-blocking_ logic, akin to Quarkus RES
Here are the rules governing execution:

* Methods annotated with `@RunOnVirtualThread`, `@Blocking` or `@Transactional` are considered blocking.
* Methods declared in a class annotated with `@RunOnVirtualThread` are considered blocking.
* Methods annotated with `@NonBlocking` are considered non-blocking.
* Methods declared on a class annotated with `@Transactional` are considered blocking unless annotated with `@NonBlocking`.
* Methods declared in a class annotated with `@Transactional` are considered blocking unless annotated with `@NonBlocking`.
* If the method does not declare any of the annotations listed above the execution model is derived from the return type:
** Methods returning `Uni` and `Multi` are considered non-blocking.
** Methods returning `void` or any other type are considered blocking.
* Kotlin `suspend` functions are always considered non-blocking and may not be annotated with `@Blocking`, `@NonBlocking` or `@RunOnVirtualThread`.
* Kotlin `suspend` functions are always considered non-blocking and may not be annotated with `@Blocking`, `@NonBlocking`
or `@RunOnVirtualThread` and may not be in a class annotated with `@RunOnVirtualThread`.
* Non-blocking methods must execute on the connection's event loop thread.
* Blocking methods must execute on a worker thread unless annotated with `@RunOnVirtualThread`.
* Methods annotated with `@RunOnVirtualThread` must execute on a virtual thread, each invocation spawns a new virtual thread.
* Blocking methods must execute on a worker thread unless annotated with `@RunOnVirtualThread` or in a class annotated
with `@RunOnVirtualThread`.
* Methods annotated with `@RunOnVirtualThread` or declared in class annotated with `@RunOnVirtualThread` must execute on
a virtual thread, each invocation spawns a new virtual thread.

==== Method parameters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1613,12 +1613,15 @@ private static Callback findCallback(Target target, IndexView index, BeanInfo be
private static ExecutionModel executionModel(MethodInfo method, TransformedAnnotationsBuildItem transformedAnnotations) {
if (KotlinUtils.isKotlinSuspendMethod(method)
&& (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)
|| transformedAnnotations.hasAnnotation(method.declaringClass(),
WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)
|| transformedAnnotations.hasAnnotation(method, WebSocketDotNames.BLOCKING)
|| transformedAnnotations.hasAnnotation(method, WebSocketDotNames.NON_BLOCKING))) {
throw new WebSocketException("Kotlin `suspend` functions in WebSockets Next endpoints may not be "
+ "annotated @Blocking, @NonBlocking or @RunOnVirtualThread: " + method);
}
if (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)) {
if (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)
|| transformedAnnotations.hasAnnotation(method.declaringClass(), WebSocketDotNames.RUN_ON_VIRTUAL_THREAD)) {
return ExecutionModel.VIRTUAL_THREAD;
} else if (transformedAnnotations.hasAnnotation(method, WebSocketDotNames.BLOCKING)) {
return ExecutionModel.WORKER_THREAD;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.test.vertx.VirtualThreadsAssertions;
import io.quarkus.websockets.next.OnError;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
import io.quarkus.websockets.next.test.utils.WSClient;
Expand All @@ -38,6 +39,9 @@ public class RunOnVirtualThreadTest {
@TestHTTPResource("end")
URI endUri;

@TestHTTPResource("virt-on-class")
URI onClassUri;

@Test
void testVirtualThreads() {
try (WSClient client = new WSClient(vertx).connect(endUri)) {
Expand All @@ -52,6 +56,22 @@ void testVirtualThreads() {
}
}

@Test
void testVirtualThreadsOnClass() {
try (WSClient client = new WSClient(vertx).connect(onClassUri)) {
client.sendAndAwait("foo");
client.sendAndAwait("bar");
client.waitForMessages(3);
String open = client.getMessages().get(0).toString();
String message1 = client.getMessages().get(1).toString();
String message2 = client.getMessages().get(2).toString();
assertNotEquals(open, message1, message2);
assertTrue(open.startsWith("wsnext-virtual-thread-"));
assertTrue(message1.startsWith("wsnext-virtual-thread-"));
assertTrue(message2.startsWith("wsnext-virtual-thread-"));
}
}

@WebSocket(path = "/end")
public static class Endpoint {

Expand All @@ -71,7 +91,27 @@ String error(Throwable t) {
}

}


@RunOnVirtualThread
@WebSocket(path = "/virt-on-class")
public static class EndpointVirtOnClass {

@Inject
RequestScopedBean bean;

@OnOpen
String open() {
VirtualThreadsAssertions.assertEverything();
return Thread.currentThread().getName();
}

@OnTextMessage
String text(String ignored) {
VirtualThreadsAssertions.assertEverything();
return Thread.currentThread().getName();
}
}

@RequestScoped
public static class RequestScopedBean {

Expand Down

0 comments on commit c326bec

Please sign in to comment.