-
Notifications
You must be signed in to change notification settings - Fork 899
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
Support Kotlin-gRPC client CoroutineStub #2669
Conversation
/cc @gary-lo |
grpc/src/main/java/com/linecorp/armeria/internal/client/grpc/GrpcClientFactory.java
Outdated
Show resolved
Hide resolved
/cc @gary-lo |
examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/HelloServiceImpl.kt
Outdated
Show resolved
Hide resolved
examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/HelloServiceImpl.kt
Outdated
Show resolved
Hide resolved
Wow thanks for doing this. It's surprising and awesome that so much of our code looks the same 😅 even though we worked independently. I'll close my PR. |
Codecov Report
@@ Coverage Diff @@
## master #2669 +/- ##
============================================
- Coverage 73.40% 72.94% -0.47%
- Complexity 11274 11293 +19
============================================
Files 993 997 +4
Lines 43329 44057 +728
Branches 5392 5463 +71
============================================
+ Hits 31807 32136 +329
- Misses 8760 9127 +367
- Partials 2762 2794 +32 Continue to review full report at Codecov.
|
To make a test code under gRPC-Kotlin coroutine stub, I refer to your PR airtasker#3 Let me add you as a co-author. 🙇♂️ |
examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/HelloServiceImpl.kt
Outdated
Show resolved
Hide resolved
examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/HelloServiceImpl.kt
Outdated
Show resolved
Hide resolved
examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/HelloServiceImpl.kt
Show resolved
Hide resolved
grpc/src/main/java/com/linecorp/armeria/internal/client/grpc/GrpcClientFactory.java
Outdated
Show resolved
Hide resolved
grpc/src/main/java/com/linecorp/armeria/internal/client/grpc/GrpcClientFactory.java
Outdated
Show resolved
Hide resolved
This gRPC-Kotlin is really awesome. ❤️ Motivation: Armeria is going to early adopt gRPC-Kotlin by our users' requests. line/armeria#2669 Armeria gRPC-Java client access `ServiceDescriptor` via reflection. I think we can access to the orignal gRPC-Java stub via `StubFor` annotation in coroutine stub. Modifications: - Change AnnotationRetention from `BINARY` to `RUNTIME` Result: Better interop between Armeria and gRPC-Kotlin 😀
examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/HelloServiceImpl.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm - propagating armeria context into grpc context and kotlin context is pretty wild and it'd be nice if we can keep on playing with the Dispatcher, it should work somehow in theory. It's generally a code smell that we have to add to armeria-grpc
only to support kotlin
I tried playing with this branch to look at the generated code, but probably since I'm on Windows, even if I run with JAVA_HOME set to a JDK 11, the compiler just shows a GUI prompt saying it requires a JDK 11 and doesn't generate any protos... Maybe we should wait a little more for the project to mature?
Found the kotlin compiler is this shell script https://github.com/grpc/grpc-kotlin/blob/master/compiler/stub.sh Indeed seems to have way too many implicit dependencies on the run environment 😓 |
I updated the generated gRPC-Kotlin files to a different branch.😉 |
I came up with this very hacky approach that seems to work. It relies on the implementation detail that the grpc-kotlin stub calls It's not great to rely on the implementation detail, and only really works for gRPC singe other usages aren't guaranteed to call package example.armeria.grpc.kotlin
import com.linecorp.armeria.common.RequestContext
import kotlinx.coroutines.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.ContinuationInterceptor
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
internal object ArmeriaContext: CoroutineContext {
override fun <R> fold(initial: R, operation: (R, CoroutineContext.Element) -> R): R =
EmptyCoroutineContext.fold(initial, operation)
override fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? =
EmptyCoroutineContext.get(key)
override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext =
EmptyCoroutineContext.minusKey(key)
override fun plus(context: CoroutineContext): CoroutineContext {
val requestCtx: RequestContext = RequestContext.current()
return requestCtx.contextAwareExecutor().asCoroutineDispatcher() + context
}
}
val Dispatchers.Armeria: CoroutineContext
get() = ArmeriaContext |
Yes, I think we need to file two issues
|
Do we need anything special for point 1? My hack already works ok. What matters is that the context provider method of point 2) is called in the caller frame, and I guess it always would be since the context is needed to start the coroutine. |
Ah... now I understood how your hack works exactly. Your hack looks awesome. 👍 |
Filed the issue. grpc/grpc-kotlin#66 |
Thanks! As long as the hack is kept to the example and not a published artifact, I guess it's ok. For this, can we also go ahead and check in the generated code? The proto compiler plugin is very much not ready for wide use right now and should leave it out of our build until it's more mature. |
examples/grpc-kotlin/src/test/kotlin/example/armeria/grpc/kotlin/HelloServiceTest.kt
Outdated
Show resolved
Hide resolved
Motivation: gRPC for Kotlin is actively developed and [0.1.1](https://github.com/grpc/grpc-kotlin/releases/tag/v0.1.1) has been released last week. Modifications: - Allow GrpcClientFactory creating client from CoroutineStub - Migrate Kotlin example to gRPC-Kotlin Result: - (Partially) Fixes line#2662 - You can now run Armeria gRPC client with `gproto` protocol and gRPC-Kotline CoroutinStub Co-authored-by: Gary Lo <gary.lo@airtasker.com>
Generate kotlin stub if a project has `kotlin-grpc` flag and `io.grpc:grpc-kotlin-stub` dependency See line/armeria#2669
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot, @ikhoon !
examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/ArmeriaContext.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM thank you
@gary-lo We built this PR together! 😀 |
} | ||
|
||
/** | ||
* Sends 5 [HelloReply] responses when receiving a request. | ||
* | ||
* @see lazyHello(HelloRequest, StreamObserver) | ||
*/ | ||
override fun lotsOfReplies(request: HelloRequest, responseObserver: StreamObserver<HelloReply>) { | ||
override fun lotsOfReplies(request: HelloRequest): Flow<HelloReply> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be flow? I wouldn't expect a for loop in a coroutine to cause any differences compared to our normal withArmeriaContext
methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, it doesn't matter so much whether it's a flow or not but was hoping withArmeriaContext
applies to all the methods without having yet another pattern of flowOn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’ve tried but it was impossible to apply because withContext
only could be applied to suspend function. This function is just return flow and not suspend :-(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or we can wrap withArmeriaContext
with the inside of flow block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see - does that mean we can have
flow {
withArmeriaContext {
for (i...)
?
Definitely not great but I guess a bit better than a new concept flowOn
. And probably good evidence to add to our issue about getting better support for customizing the context globally :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see - does that mean we can have
Yes, flow
takes suspend function.
https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/flow/Builders.kt#L49
And probably good evidence to add to our issue about getting better support for customizing the context globally :)
That sounds good. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test failed, the nested block does not work...
flow {
withArmeriaContext {
for (i...)
emit()
should be called from dispatchers of flow block unless it throws ISE.
https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/flow/Builders.kt#L37-L45
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too bad :( Let's stick with flowOn then since best we can do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@anuraaga Shall we merge this and revisit when |
Motivation: gRPC for Kotlin is actively developed and [0.1.1](https://github.com/grpc/grpc-kotlin/releases/tag/v0.1.1) has been released last week. Modifications: - Allow GrpcClientFactory creating client from CoroutineStub - Migrate Kotlin example to gRPC-Kotlin Result: - Partially fixes line#2662 - You can now run Armeria gRPC client with `gproto*` and gRPC-Kotlin CoroutinStub Co-authored-by: Gary Lo <gary.lo@airtasker.com>
Motivation:
gRPC for Kotlin is actively developed and
0.1.1 has been released last week.
Modifications:
Result:
gproto*
and gRPC-Kotlin CoroutinStub