Skip to content

Commit 579dbc4

Browse files
committedMar 7, 2024
Optimize Coroutine invocations
KClass instantiation in CoroutinesUtils is suboptimal, and should be replaced by KTypes#isSubtypeOf checks using pre-instantiated types for Flow, Mono and Publisher. This commit impact on performances is significant since a throughput increase between 2x and 3x has been measured on basic endpoints. Closes gh-32390
1 parent 6d9a2eb commit 579dbc4

File tree

2 files changed

+50
-13
lines changed

2 files changed

+50
-13
lines changed
 

‎spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java

+17-13
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@
2525
import kotlin.coroutines.CoroutineContext;
2626
import kotlin.jvm.JvmClassMappingKt;
2727
import kotlin.reflect.KClass;
28-
import kotlin.reflect.KClassifier;
2928
import kotlin.reflect.KFunction;
3029
import kotlin.reflect.KParameter;
3130
import kotlin.reflect.KType;
3231
import kotlin.reflect.full.KCallables;
3332
import kotlin.reflect.full.KClasses;
33+
import kotlin.reflect.full.KClassifiers;
34+
import kotlin.reflect.full.KTypes;
3435
import kotlin.reflect.jvm.KCallablesJvm;
3536
import kotlin.reflect.jvm.KTypesJvm;
3637
import kotlin.reflect.jvm.ReflectJvmMapping;
@@ -58,6 +59,12 @@
5859
*/
5960
public abstract class CoroutinesUtils {
6061

62+
private static final KType flowType = KClassifiers.getStarProjectedType(JvmClassMappingKt.getKotlinClass(Flow.class));
63+
64+
private static final KType monoType = KClassifiers.getStarProjectedType(JvmClassMappingKt.getKotlinClass(Mono.class));
65+
66+
private static final KType publisherType = KClassifiers.getStarProjectedType(JvmClassMappingKt.getKotlinClass(Publisher.class));
67+
6168
/**
6269
* Convert a {@link Deferred} instance to a {@link Mono}.
6370
*/
@@ -137,18 +144,15 @@ public static Publisher<?> invokeSuspendingFunction(CoroutineContext context, Me
137144
.filter(result -> result != Unit.INSTANCE)
138145
.onErrorMap(InvocationTargetException.class, InvocationTargetException::getTargetException);
139146

140-
KClassifier returnType = function.getReturnType().getClassifier();
141-
if (returnType != null) {
142-
if (returnType.equals(JvmClassMappingKt.getKotlinClass(Flow.class))) {
143-
return mono.flatMapMany(CoroutinesUtils::asFlux);
144-
}
145-
else if (returnType.equals(JvmClassMappingKt.getKotlinClass(Mono.class))) {
146-
return mono.flatMap(o -> ((Mono<?>)o));
147-
}
148-
else if (returnType instanceof KClass<?> kClass &&
149-
Publisher.class.isAssignableFrom(JvmClassMappingKt.getJavaClass(kClass))) {
150-
return mono.flatMapMany(o -> ((Publisher<?>)o));
151-
}
147+
KType returnType = function.getReturnType();
148+
if (KTypes.isSubtypeOf(returnType, flowType)) {
149+
return mono.flatMapMany(CoroutinesUtils::asFlux);
150+
}
151+
else if (KTypes.isSubtypeOf(returnType, monoType)) {
152+
return mono.flatMap(o -> ((Mono<?>)o));
153+
}
154+
else if (KTypes.isSubtypeOf(returnType, publisherType)) {
155+
return mono.flatMapMany(o -> ((Publisher<?>)o));
152156
}
153157
return mono;
154158
}

‎spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt

+33
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,29 @@ class CoroutinesUtilsTests {
9797
Assertions.assertThatIllegalArgumentException().isThrownBy { CoroutinesUtils.invokeSuspendingFunction(method, this, "foo") }
9898
}
9999

100+
@Test
101+
fun invokeSuspendingFunctionWithMono() {
102+
val method = CoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithMono", Continuation::class.java)
103+
val publisher = CoroutinesUtils.invokeSuspendingFunction(method, this)
104+
Assertions.assertThat(publisher).isInstanceOf(Mono::class.java)
105+
StepVerifier.create(publisher)
106+
.expectNext("foo")
107+
.expectComplete()
108+
.verify()
109+
}
110+
111+
@Test
112+
fun invokeSuspendingFunctionWithFlux() {
113+
val method = CoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithFlux", Continuation::class.java)
114+
val publisher = CoroutinesUtils.invokeSuspendingFunction(method, this)
115+
Assertions.assertThat(publisher).isInstanceOf(Flux::class.java)
116+
StepVerifier.create(publisher)
117+
.expectNext("foo")
118+
.expectNext("bar")
119+
.expectComplete()
120+
.verify()
121+
}
122+
100123
@Test
101124
fun invokeSuspendingFunctionWithFlow() {
102125
val method = CoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithFlow", Continuation::class.java)
@@ -213,6 +236,16 @@ class CoroutinesUtilsTests {
213236
return value
214237
}
215238

239+
suspend fun suspendingFunctionWithMono(): Mono<String> {
240+
delay(1)
241+
return Mono.just("foo")
242+
}
243+
244+
suspend fun suspendingFunctionWithFlux(): Flux<String> {
245+
delay(1)
246+
return Flux.just("foo", "bar")
247+
}
248+
216249
suspend fun suspendingFunctionWithFlow(): Flow<String> {
217250
delay(1)
218251
return flowOf("foo", "bar")

0 commit comments

Comments
 (0)