Skip to content

Commit b390f39

Browse files
committed
gRPC runtime declarations
1 parent 4dfc7e2 commit b390f39

33 files changed

+805
-6
lines changed

core/src/jvmMain/kotlin/kotlinx/rpc/internal/internalServiceDescriptorOf.jvm.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.rpc.internal
@@ -13,8 +13,12 @@ private const val RPC_SERVICE_STUB_SIMPLE_NAME = "\$rpcServiceStub"
1313
internal actual fun <@Rpc T : Any> internalServiceDescriptorOf(kClass: KClass<T>): Any? {
1414
val className = "${kClass.qualifiedName}\$$RPC_SERVICE_STUB_SIMPLE_NAME"
1515

16-
return kClass.java.classLoader
17-
.loadClass(className)
18-
?.kotlin
19-
?.companionObjectInstance
16+
return try {
17+
kClass.java.classLoader
18+
.loadClass(className)
19+
?.kotlin
20+
?.companionObjectInstance
21+
} catch (_ : ClassNotFoundException) {
22+
null
23+
}
2024
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.grpc
6+
7+
import kotlinx.coroutines.CoroutineScope
8+
import kotlinx.coroutines.Deferred
9+
import kotlinx.coroutines.SupervisorJob
10+
import kotlinx.coroutines.job
11+
import kotlinx.rpc.RpcCall
12+
import kotlinx.rpc.RpcClient
13+
import kotlinx.rpc.grpc.descriptor.GrpcClientDelegate
14+
import kotlinx.rpc.grpc.descriptor.GrpcServiceDescriptor
15+
import kotlinx.rpc.internal.utils.map.ConcurrentHashMap
16+
import kotlin.coroutines.CoroutineContext
17+
18+
public class GrpcClient(private val channel: ManagedChannel) : RpcClient {
19+
override val coroutineContext: CoroutineContext = SupervisorJob()
20+
21+
private val stubs = ConcurrentHashMap<Long, GrpcClientDelegate>()
22+
23+
override suspend fun <T> call(call: RpcCall): T {
24+
return call.delegate().call(call)
25+
}
26+
27+
override fun <T> callAsync(serviceScope: CoroutineScope, call: RpcCall): Deferred<T> {
28+
return call.delegate().callAsync(call)
29+
}
30+
31+
private fun RpcCall.delegate(): GrpcClientDelegate {
32+
val grpc = (descriptor as? GrpcServiceDescriptor<*>)
33+
?: error("Service ${descriptor.fqName} is not a gRPC service")
34+
35+
return stubs.computeIfAbsent(serviceId) { grpc.delegate.clientProvider(channel) }
36+
}
37+
38+
override fun provideStubContext(serviceId: Long): CoroutineContext {
39+
// todo create lifetime hierarchy if possible
40+
return SupervisorJob(coroutineContext.job)
41+
}
42+
}
43+
44+
public fun GrpcClient(
45+
name: String,
46+
port: Int,
47+
configure: ManagedChannelBuilder<*>.() -> Unit = {},
48+
): GrpcClient {
49+
val channel = ManagedChannelBuilder(name, port).apply(configure).buildChannel()
50+
return GrpcClient(channel)
51+
}
52+
53+
public fun GrpcClient(
54+
target: String,
55+
configure: ManagedChannelBuilder<*>.() -> Unit = {},
56+
): GrpcClient {
57+
val channel = ManagedChannelBuilder(target).apply(configure).buildChannel()
58+
return GrpcClient(channel)
59+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.grpc
6+
7+
import kotlinx.coroutines.SupervisorJob
8+
import kotlinx.rpc.RpcServer
9+
import kotlinx.rpc.descriptor.serviceDescriptorOf
10+
import kotlinx.rpc.grpc.annotations.Grpc
11+
import kotlinx.rpc.grpc.descriptor.GrpcServiceDescriptor
12+
import kotlin.coroutines.CoroutineContext
13+
import kotlin.reflect.KClass
14+
import kotlin.time.Duration
15+
16+
public class GrpcServer internal constructor(
17+
override val port: Int = 8080,
18+
builder: ServerBuilder<*>.() -> Unit,
19+
) : RpcServer, Server {
20+
private var isBuilt = false
21+
private lateinit var internalServer: Server
22+
23+
private val serverBuilder: ServerBuilder<*> = ServerBuilder(port).apply(builder)
24+
private val registry: MutableHandlerRegistry by lazy {
25+
MutableHandlerRegistry().apply { serverBuilder.fallbackHandlerRegistry(this) }
26+
}
27+
28+
override val coroutineContext: CoroutineContext
29+
get() = error("coroutineContext is not available for gRPC server builder")
30+
31+
override fun <@Grpc Service : Any> registerService(
32+
serviceKClass: KClass<Service>,
33+
serviceFactory: (CoroutineContext) -> Service,
34+
) {
35+
val childJob = SupervisorJob()
36+
val service = serviceFactory(childJob)
37+
38+
val definition: ServerServiceDefinition = getDefinition(service, serviceKClass)
39+
40+
if (isBuilt) {
41+
registry.addService(definition)
42+
} else {
43+
serverBuilder.addService(definition)
44+
}
45+
}
46+
47+
private fun <@Grpc Service : Any> getDefinition(
48+
service: Service,
49+
serviceKClass: KClass<Service>,
50+
): ServerServiceDefinition {
51+
val descriptor = serviceDescriptorOf<Service>(serviceKClass)
52+
val grpc = (descriptor as? GrpcServiceDescriptor<Service>)
53+
?: error("Service ${descriptor.fqName} is not a gRPC service")
54+
55+
return grpc.delegate.definitionFor(service)
56+
}
57+
58+
internal fun build() {
59+
internalServer = Server(serverBuilder)
60+
isBuilt = true
61+
}
62+
63+
override val isShutdown: Boolean
64+
get() = internalServer.isShutdown
65+
66+
override val isTerminated: Boolean
67+
get() = internalServer.isTerminated
68+
69+
override fun start(): GrpcServer {
70+
internalServer.start()
71+
return this
72+
}
73+
74+
override fun shutdown(): GrpcServer {
75+
internalServer.shutdown()
76+
return this
77+
}
78+
79+
override fun shutdownNow(): GrpcServer {
80+
internalServer.shutdownNow()
81+
return this
82+
}
83+
84+
override suspend fun awaitTermination(duration: Duration): GrpcServer {
85+
internalServer.awaitTermination(duration)
86+
return this
87+
}
88+
}
89+
90+
public fun GrpcServer(
91+
port: Int,
92+
configure: ServerBuilder<*>.() -> Unit = {},
93+
builder: RpcServer.() -> Unit = {},
94+
): GrpcServer {
95+
return GrpcServer(port, configure).apply(builder).apply { build() }
96+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
6+
7+
package kotlinx.rpc.grpc
8+
9+
import kotlin.time.Duration
10+
11+
public expect abstract class ManagedChannelPlatform
12+
13+
public interface ManagedChannel {
14+
public val isShutdown: Boolean
15+
public val isTerminated: Boolean
16+
17+
public suspend fun awaitTermination(duration: Duration): Boolean
18+
19+
public fun shutdown(): ManagedChannel
20+
public fun shutdownNow(): ManagedChannel
21+
22+
public val platformApi: ManagedChannelPlatform
23+
}
24+
25+
public expect abstract class ManagedChannelBuilder<T : ManagedChannelBuilder<T>>
26+
27+
public expect fun ManagedChannelBuilder(name: String, port: Int): ManagedChannelBuilder<*>
28+
public expect fun ManagedChannelBuilder(target: String): ManagedChannelBuilder<*>
29+
30+
public expect fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
6+
7+
package kotlinx.rpc.grpc
8+
9+
public expect abstract class HandlerRegistry
10+
11+
@Suppress("RedundantConstructorKeyword")
12+
public expect class MutableHandlerRegistry constructor() : HandlerRegistry {
13+
internal fun addService(@Suppress("unused") service: ServerServiceDefinition): ServerServiceDefinition?
14+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
6+
7+
package kotlinx.rpc.grpc
8+
9+
import kotlin.time.Duration
10+
11+
public expect abstract class ServerBuilder<T : ServerBuilder<T>> {
12+
public abstract fun addService(service: ServerServiceDefinition): T
13+
14+
public abstract fun fallbackHandlerRegistry(registry: HandlerRegistry?): T
15+
}
16+
17+
internal expect fun ServerBuilder(port: Int): ServerBuilder<*>
18+
19+
public interface Server {
20+
public val port: Int
21+
public val isShutdown: Boolean
22+
public val isTerminated: Boolean
23+
24+
public fun start() : Server
25+
public fun shutdown() : Server
26+
public fun shutdownNow() : Server
27+
public suspend fun awaitTermination(duration: Duration = Duration.INFINITE) : Server
28+
}
29+
30+
internal expect fun Server(builder: ServerBuilder<*>): Server
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
6+
7+
package kotlinx.rpc.grpc
8+
9+
public expect class ServerServiceDefinition
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
@file:Suppress("MemberVisibilityCanBePrivate")
6+
7+
package kotlinx.rpc.grpc
8+
9+
public interface Status {
10+
public val code: Code
11+
public val description: String?
12+
public val cause: Throwable?
13+
14+
public enum class Code(public val value: Int) {
15+
OK(0),
16+
CANCELLED(1),
17+
UNKNOWN(2),
18+
INVALID_ARGUMENT(3),
19+
DEADLINE_EXCEEDED(4),
20+
NOT_FOUND(5),
21+
ALREADY_EXISTS(6),
22+
PERMISSION_DENIED(7),
23+
RESOURCE_EXHAUSTED(8),
24+
FAILED_PRECONDITION(9),
25+
ABORTED(10),
26+
OUT_OF_RANGE(11),
27+
UNIMPLEMENTED(12),
28+
INTERNAL(13),
29+
UNAVAILABLE(14),
30+
DATA_LOSS(15),
31+
UNAUTHENTICATED(16);
32+
33+
public val valueAscii: ByteArray = value.toString().encodeToByteArray()
34+
}
35+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.grpc
6+
7+
public interface StatusRuntimeException {
8+
public val status: Status
9+
}
10+
11+
public expect fun StatusRuntimeException(status: Status) : StatusRuntimeException
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.grpc.annotations
6+
7+
import kotlinx.rpc.annotations.Rpc
8+
9+
@Target(AnnotationTarget.CLASS, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.TYPE_PARAMETER)
10+
@Rpc
11+
public annotation class Grpc

0 commit comments

Comments
 (0)