Conversation
…s without a Continuation parameter. A Kotlin suspend function always compiles to a JVM method whose last parameter is kotlin.coroutines.Continuation. Check for that parameter with KotlinDetector.isSuspendingFunction(…) before resolving the KFunction, so that regular Kotlin methods no longer materialize all members of their declaring class through Kotlin reflection. Methods that declare a trailing Continuation parameter are still verified via KFunction.isSuspend(). Also let RepositoryMethodInvoker.CoroutineAdapterInformation use isSuspend(…) instead of resolving both KFunctions itself only to read isSuspend(). Closes #3544 Signed-off-by: Greg Taube <gregjotau@gmail.com>
gregjotau
force-pushed
the
avoid-kotlin-reflection-in-is-suspend
branch
from
September 19, 2026 05:32
1c32dda to
c2b4082
Compare
Author
|
Handing this over to the team: please feel free to take the change from here in whatever form suits you (team commit, different shape, or a different guard). We don't need authorship or ownership of the code; what we care about is the startup cost, so use it as you see fit. We have tested it thoroughly against reai.no, a large Spring Boot Kotlin accounting application, with the profiles and numbers in the description above. The signed-off commit stays reachable at |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
KotlinReflectionUtils.isSuspend(Method)resolves theKFunctionfor every Kotlin method throughReflectJvmMapping.getKotlinFunction(method), which materializes all members of the declaringKClassvia Kotlin reflection, only to readKFunction.isSuspend().AbstractRepositoryMetadata.getReturnType(Method)calls it for every query method during repository initialization, andRepositoryMethodInvokercalls it again per method.A
suspendfunction always compiles to a JVM method whose last parameter iskotlin.coroutines.Continuation, soKotlinDetector.isSuspendingFunction(method)(already used byQueryExecutionResultHandlerin this module) can rule out regular methods without loading Kotlin reflection. Methods that do declare a trailingContinuationparameter are still verified throughKFunction.isSuspend(), so a non-suspending function that takes aContinuationargument explicitly keeps returningfalse.RepositoryMethodInvoker.CoroutineAdapterInformation.create(...)had the same pattern (twofindKotlinFunctionlookups used only forisSuspend()); it now delegates toKotlinReflectionUtils.isSuspend(...).Measurements
Cold-JVM micro-benchmark calling
isSuspendfor the 1,418 non-synthetic methods of the 337 Kotlin repository interfaces of a Spring Boot 4.2.0-SNAPSHOT application (JDK 27, Apple M5 Max, 5 runs each):mainisSuspendover 1,418 methodsIn a wall-clock startup profile of the same application (async-profiler, 2 ms sampling, extracted Boot layout, 243 JPA repositories),
KotlinReflectionUtils.isSuspendaccounted for 241 of 8,694 main-thread samples (2.8 % of startup), all belowReflectJvmMapping.getKotlinFunction; with this change the frame no longer appears. Part of theKClassmember computation is shared with other Kotlin reflection users during startup, so the end-to-end saving depends on what else touches those classes.Verification
KotlinReflectionUtilsSuspendUnitTests,KotlinReflectionUtilsUnitTests,RepositoryMethodInvokerUnitTestsand bothCoroutineRepositoryMetadataUnitTestspass locally. I also verified the fragment path with a throwaway MockK-free variant ofCoroutineCrudRepositoryCustomImplementationUnitTests(suspend fragment method, regular fragment method, and suspend base methods bridging to a reactive backing repository includingFlowandUnitresults).CoroutineCrudRepositoryUnitTestsandCoroutineCrudRepositoryCustomImplementationUnitTestscould not run on my machine on eithermainor this branch because the MockK agent fails to self-attach there, so CI will have to cover those.Closes #3544