Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support of @RunOnVirtualThread on class for websockets next #44925

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading