Problem
CoroutineUtils
and InvocableHandlerMethod
will throw exception when value class whose constructor is private is given like
@JvmInline
value class ValueClassWithPrivateConstructor private constructor(val value: String) {
companion object {
fun from(value: String) = ValueClassWithPrivateConstructor(value)
}
}
stacktrace
java.lang.IllegalAccessException: class kotlin.reflect.jvm.internal.calls.CallerImpl$Method cannot access a member of class org.springframework.core.CoroutinesUtilsTests$ValueClassWithPrivateConstructor with modifiers "private static"
kotlin.reflect.full.IllegalCallableAccessException: java.lang.IllegalAccessException: class kotlin.reflect.jvm.internal.calls.CallerImpl$Method cannot access a member of class org.springframework.core.CoroutinesUtilsTests$ValueClassWithPrivateConstructor with modifiers "private static"
at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:280)
at org.springframework.core.CoroutinesUtils.lambda$invokeSuspendingFunction$3(CoroutinesUtils.java:135)
at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$4.invokeSuspend(IntrinsicsJvm.kt:270)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
at kotlinx.coroutines.reactor.MonoKt.monoInternal$lambda$2(Mono.kt:92)
at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:61)
at reactor.core.publisher.Mono.subscribe(Mono.java:4568)
at kotlinx.coroutines.reactor.MonoKt.awaitSingleOrNull(Mono.kt:47)
at org.springframework.core.CoroutinesUtilsTests$invokeSuspendingFunctionWithValueClassWithPrivateConstructorParameter$1.invokeSuspend(CoroutinesUtilsTests.kt:216)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:280)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at org.springframework.core.CoroutinesUtilsTests.invokeSuspendingFunctionWithValueClassWithPrivateConstructorParameter(CoroutinesUtilsTests.kt:215)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: java.lang.IllegalAccessException: class kotlin.reflect.jvm.internal.calls.CallerImpl$Method cannot access a member of class org.springframework.core.CoroutinesUtilsTests$ValueClassWithPrivateConstructor with modifiers "private static"
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:392)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:674)
at java.base/java.lang.reflect.Method.invoke(Method.java:560)
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method.callMethod(CallerImpl.kt:97)
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method$Static.call(CallerImpl.kt:106)
at kotlin.reflect.jvm.internal.calls.ValueClassAwareCaller.call(ValueClassAwareCaller.kt:199)
at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:108)
... 25 more
Solution
use KCallablesJvm.setAccessible
before creating object to avoid the exception
Concern
in this PR, i modified three places. those of them are same. is it better to extract them into a single method?
Comment From: quaff
Does it add overhead and is it worthy if it does?
Comment From: T45K
hmm, maybe so but i don't know how heavy it is... let me measure it. is there any benchmark?
anyway, i think this private constructor issue is regression and should be fixed
Comment From: T45K
let me share my initial and rough investigation
i wrote the following test code to measure invoking time by using Kotlin measureNanoTime
@Test
fun measure() {
val method =
CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithValueClass") }
println(measureNanoTime {
repeat(1_000_000) {
CoroutinesUtils.invokeSuspendingFunction(method, this, "foo", null)
}
})
}
in CoroutinesUtilsTests
.
and then, i executed the test five times on each of w/o setAccessible
(i.e., comment out KCallablesJvm.setAccessible(valueClassConstructor, true);
) and w/ setAccessible
this is the results (unit: nano sec)
1st | 2nd | 3rd | 4th | 5th | |
---|---|---|---|---|---|
w/o setAccessible | 2,855,264,875 | 2,724,373,625 | 1,360,412,625 | 1,330,190,917 | 2,604,268,917 |
w/ setAccessible | 1,290,038,333 | 2,573,693,666 | 2,516,409,792 | 2,744,773,333 | 1,369,282,958 |
both execution time is around 1.3 sec or 2.7 sec, and there seems no big performance issue