Skip to content

Commit

Permalink
WebSockets Next: add support for Kotlin suspend functions
Browse files Browse the repository at this point in the history
Kotlin `suspend` functions are treated like Java methods that return `Uni`.
That is, they are considered non-blocking. The implementation uses CDI
method invokers (to avoid custom bytecode generation), which actually
convert the `suspend` function result into a `Uni` under the hood.

With this commit, only single-shot `suspend` functions are supported;
`suspend` functions returning `Flow` are not supported yet.
  • Loading branch information
Ladicek committed Jul 8, 2024
1 parent ac22bdf commit 5e4b88f
Show file tree
Hide file tree
Showing 23 changed files with 827 additions and 68 deletions.
5 changes: 5 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2166,6 +2166,11 @@
<artifactId>quarkus-websockets-next-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets-next-kotlin</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-undertow-spi</artifactId>
Expand Down
3 changes: 3 additions & 0 deletions docs/src/main/asciidoc/kotlin.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,9 @@ The following extensions provide support for Kotlin Coroutines by allowing the u
|`quarkus-vertx`
|Support is provided for `@ConsumeEvent` methods

|`quarkus-websockets-next`
|Support is provided for server-side and client-side endpoint methods

|===

=== Kotlin coroutines and Mutiny
Expand Down
72 changes: 72 additions & 0 deletions extensions/websockets-next/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,56 @@
<artifactId>smallrye-certificate-generator-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>mutiny-kotlin</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/main/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
Expand All @@ -80,6 +126,32 @@
</path>
</annotationProcessorPaths>
</configuration>
<executions>
<!-- Replacing default-compile as it is treated specially by Maven -->
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<!-- Replacing default-testCompile as it is treated specially by Maven -->
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@

import io.quarkus.arc.deployment.TransformedAnnotationsBuildItem;
import io.quarkus.arc.processor.Annotations;
import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.processor.KotlinDotNames;
import io.quarkus.arc.processor.KotlinUtils;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.FieldDescriptor;
import io.quarkus.gizmo.ResultHandle;
Expand All @@ -35,15 +38,17 @@ public class Callback {
public final Target target;
public final String endpointPath;
public final AnnotationInstance annotation;
public final BeanInfo bean;
public final MethodInfo method;
public final ExecutionModel executionModel;
public final MessageType messageType;
public final List<CallbackArgument> arguments;

public Callback(Target target, AnnotationInstance annotation, MethodInfo method, ExecutionModel executionModel,
CallbackArgumentsBuildItem callbackArguments, TransformedAnnotationsBuildItem transformedAnnotations,
String endpointPath, IndexView index) {
public Callback(Target target, AnnotationInstance annotation, BeanInfo bean, MethodInfo method,
ExecutionModel executionModel, CallbackArgumentsBuildItem callbackArguments,
TransformedAnnotationsBuildItem transformedAnnotations, String endpointPath, IndexView index) {
this.target = target;
this.bean = bean;
this.method = method;
this.annotation = annotation;
this.executionModel = executionModel;
Expand Down Expand Up @@ -104,6 +109,15 @@ public boolean isReturnTypeMulti() {
return WebSocketDotNames.MULTI.equals(returnType().name());
}

public boolean isKotlinSuspendFunction() {
return KotlinUtils.isKotlinSuspendMethod(method);
}

public boolean isKotlinSuspendFunctionReturningUnit() {
return KotlinUtils.isKotlinSuspendMethod(method)
&& KotlinUtils.getKotlinSuspendMethodResult(method).name().equals(KotlinDotNames.UNIT);
}

public boolean acceptsMessage() {
return messageType != MessageType.NONE;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.websockets.next.deployment;

import io.quarkus.arc.processor.KotlinUtils;
import io.quarkus.gizmo.ResultHandle;

public class KotlinContinuationCallbackArgument implements CallbackArgument {
@Override
public boolean matches(ParameterContext context) {
return KotlinUtils.isKotlinContinuationParameter(context.parameter());
}

@Override
public ResultHandle get(InvocationBytecodeContext context) {
// the actual value is provided by the invoker
return context.bytecode().loadNull();
}
}
Loading

0 comments on commit 5e4b88f

Please sign in to comment.