diff --git a/docs/pages/kotlinx-rpc/topics/platforms.topic b/docs/pages/kotlinx-rpc/topics/platforms.topic
index f846b1dfe..e04453ca3 100644
--- a/docs/pages/kotlinx-rpc/topics/platforms.topic
+++ b/docs/pages/kotlinx-rpc/topics/platforms.topic
@@ -96,6 +96,14 @@
appleiosiosArm64iosSimulatorArm64iosX64 macosmacosArm64macosX64 watchoswatchosArm32watchosArm64watchosDeviceArm64watchosSimulatorArm64watchosX64 tvostvosArm64tvosSimulatorArm64tvosX64 linuxlinuxArm64linuxX64 windowsmingwX64 |
+
+grpc-ktor-server |
+jvm |
+browsernode |
+wasmJsbrowserd8node |
+appleiosiosArm64iosSimulatorArm64iosX64 macosmacosArm64macosX64 watchoswatchosArm32watchosArm64watchosDeviceArm64watchosSimulatorArm64watchosX64 tvostvosArm64tvosSimulatorArm64tvosX64 linuxlinuxArm64linuxX64 windowsmingwX64 |
+
+
krpc-client |
jvm |
diff --git a/grpc/grpc-core/api/grpc-core.api b/grpc/grpc-core/api/grpc-core.api
index 59d28f2b5..52775a07c 100644
--- a/grpc/grpc-core/api/grpc-core.api
+++ b/grpc/grpc-core/api/grpc-core.api
@@ -1,8 +1,9 @@
public final class kotlinx/rpc/grpc/GrpcClient : kotlinx/rpc/RpcClient {
+ public final fun awaitTermination-VtjQ1oo (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun call (Lkotlinx/rpc/RpcCall;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public fun callAsync (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/rpc/RpcCall;)Lkotlinx/coroutines/Deferred;
- public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext;
- public fun provideStubContext (J)Lkotlin/coroutines/CoroutineContext;
+ public fun callServerStreaming (Lkotlinx/rpc/RpcCall;)Lkotlinx/coroutines/flow/Flow;
+ public final fun shutdown ()V
+ public final fun shutdownNow ()V
}
public final class kotlinx/rpc/grpc/GrpcClientKt {
@@ -14,11 +15,11 @@ public final class kotlinx/rpc/grpc/GrpcClientKt {
public final class kotlinx/rpc/grpc/GrpcServer : kotlinx/rpc/RpcServer, kotlinx/rpc/grpc/Server {
public fun awaitTermination-VtjQ1oo (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext;
+ public fun deregisterService (Lkotlin/reflect/KClass;)V
public fun getPort ()I
public fun isShutdown ()Z
public fun isTerminated ()Z
- public fun registerService (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V
+ public fun registerService (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function0;)V
public fun shutdown ()Lkotlinx/rpc/grpc/GrpcServer;
public synthetic fun shutdown ()Lkotlinx/rpc/grpc/Server;
public fun shutdownNow ()Lkotlinx/rpc/grpc/GrpcServer;
@@ -47,6 +48,7 @@ public final class kotlinx/rpc/grpc/ManagedChannel_jvmKt {
public abstract interface class kotlinx/rpc/grpc/Server {
public abstract fun awaitTermination-VtjQ1oo (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static synthetic fun awaitTermination-VtjQ1oo$default (Lkotlinx/rpc/grpc/Server;JLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public abstract fun getPort ()I
public abstract fun isShutdown ()Z
public abstract fun isTerminated ()Z
@@ -102,7 +104,7 @@ public final class kotlinx/rpc/grpc/StatusRuntimeException_jvmKt {
public abstract interface class kotlinx/rpc/grpc/descriptor/GrpcClientDelegate {
public abstract fun call (Lkotlinx/rpc/RpcCall;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public abstract fun callAsync (Lkotlinx/rpc/RpcCall;)Lkotlinx/coroutines/Deferred;
+ public abstract fun callServerStreaming (Lkotlinx/rpc/RpcCall;)Lkotlinx/coroutines/flow/Flow;
}
public abstract interface class kotlinx/rpc/grpc/descriptor/GrpcDelegate {
diff --git a/grpc/grpc-core/api/grpc-core.klib.api b/grpc/grpc-core/api/grpc-core.klib.api
new file mode 100644
index 000000000..730a3f7d5
--- /dev/null
+++ b/grpc/grpc-core/api/grpc-core.klib.api
@@ -0,0 +1,145 @@
+// Klib ABI Dump
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
+// Rendering settings:
+// - Signature version: 2
+// - Show manifest properties: true
+// - Show declarations: true
+
+// Library unique name:
+abstract interface <#A: kotlin/Any> kotlinx.rpc.grpc.descriptor/GrpcDelegate { // kotlinx.rpc.grpc.descriptor/GrpcDelegate|null[0]
+ abstract fun clientProvider(kotlinx.rpc.grpc/ManagedChannel): kotlinx.rpc.grpc.descriptor/GrpcClientDelegate // kotlinx.rpc.grpc.descriptor/GrpcDelegate.clientProvider|clientProvider(kotlinx.rpc.grpc.ManagedChannel){}[0]
+ abstract fun definitionFor(#A): kotlinx.rpc.grpc/ServerServiceDefinition // kotlinx.rpc.grpc.descriptor/GrpcDelegate.definitionFor|definitionFor(1:0){}[0]
+}
+
+abstract interface <#A: kotlin/Any> kotlinx.rpc.grpc.descriptor/GrpcServiceDescriptor : kotlinx.rpc.descriptor/RpcServiceDescriptor<#A> { // kotlinx.rpc.grpc.descriptor/GrpcServiceDescriptor|null[0]
+ abstract val delegate // kotlinx.rpc.grpc.descriptor/GrpcServiceDescriptor.delegate|{}delegate[0]
+ abstract fun (): kotlinx.rpc.grpc.descriptor/GrpcDelegate<#A> // kotlinx.rpc.grpc.descriptor/GrpcServiceDescriptor.delegate.|(){}[0]
+}
+
+abstract interface kotlinx.rpc.grpc.descriptor/GrpcClientDelegate { // kotlinx.rpc.grpc.descriptor/GrpcClientDelegate|null[0]
+ abstract fun <#A1: kotlin/Any?> callServerStreaming(kotlinx.rpc/RpcCall): kotlinx.coroutines.flow/Flow<#A1> // kotlinx.rpc.grpc.descriptor/GrpcClientDelegate.callServerStreaming|callServerStreaming(kotlinx.rpc.RpcCall){0§}[0]
+ abstract suspend fun <#A1: kotlin/Any?> call(kotlinx.rpc/RpcCall): #A1 // kotlinx.rpc.grpc.descriptor/GrpcClientDelegate.call|call(kotlinx.rpc.RpcCall){0§}[0]
+}
+
+abstract interface kotlinx.rpc.grpc/ManagedChannel { // kotlinx.rpc.grpc/ManagedChannel|null[0]
+ abstract val isShutdown // kotlinx.rpc.grpc/ManagedChannel.isShutdown|{}isShutdown[0]
+ abstract fun (): kotlin/Boolean // kotlinx.rpc.grpc/ManagedChannel.isShutdown.|(){}[0]
+ abstract val isTerminated // kotlinx.rpc.grpc/ManagedChannel.isTerminated|{}isTerminated[0]
+ abstract fun (): kotlin/Boolean // kotlinx.rpc.grpc/ManagedChannel.isTerminated.|(){}[0]
+ abstract val platformApi // kotlinx.rpc.grpc/ManagedChannel.platformApi|{}platformApi[0]
+ abstract fun (): kotlinx.rpc.grpc/ManagedChannelPlatform // kotlinx.rpc.grpc/ManagedChannel.platformApi.|(){}[0]
+
+ abstract fun shutdown(): kotlinx.rpc.grpc/ManagedChannel // kotlinx.rpc.grpc/ManagedChannel.shutdown|shutdown(){}[0]
+ abstract fun shutdownNow(): kotlinx.rpc.grpc/ManagedChannel // kotlinx.rpc.grpc/ManagedChannel.shutdownNow|shutdownNow(){}[0]
+ abstract suspend fun awaitTermination(kotlin.time/Duration): kotlin/Boolean // kotlinx.rpc.grpc/ManagedChannel.awaitTermination|awaitTermination(kotlin.time.Duration){}[0]
+}
+
+abstract interface kotlinx.rpc.grpc/Server { // kotlinx.rpc.grpc/Server|null[0]
+ abstract val isShutdown // kotlinx.rpc.grpc/Server.isShutdown|{}isShutdown[0]
+ abstract fun (): kotlin/Boolean // kotlinx.rpc.grpc/Server.isShutdown.|(){}[0]
+ abstract val isTerminated // kotlinx.rpc.grpc/Server.isTerminated|{}isTerminated[0]
+ abstract fun (): kotlin/Boolean // kotlinx.rpc.grpc/Server.isTerminated.|(){}[0]
+ abstract val port // kotlinx.rpc.grpc/Server.port|{}port[0]
+ abstract fun (): kotlin/Int // kotlinx.rpc.grpc/Server.port.|(){}[0]
+
+ abstract fun shutdown(): kotlinx.rpc.grpc/Server // kotlinx.rpc.grpc/Server.shutdown|shutdown(){}[0]
+ abstract fun shutdownNow(): kotlinx.rpc.grpc/Server // kotlinx.rpc.grpc/Server.shutdownNow|shutdownNow(){}[0]
+ abstract fun start(): kotlinx.rpc.grpc/Server // kotlinx.rpc.grpc/Server.start|start(){}[0]
+ abstract suspend fun awaitTermination(kotlin.time/Duration = ...): kotlinx.rpc.grpc/Server // kotlinx.rpc.grpc/Server.awaitTermination|awaitTermination(kotlin.time.Duration){}[0]
+}
+
+abstract interface kotlinx.rpc.grpc/Status { // kotlinx.rpc.grpc/Status|null[0]
+ abstract val cause // kotlinx.rpc.grpc/Status.cause|{}cause[0]
+ abstract fun (): kotlin/Throwable? // kotlinx.rpc.grpc/Status.cause.|(){}[0]
+ abstract val code // kotlinx.rpc.grpc/Status.code|{}code[0]
+ abstract fun (): kotlinx.rpc.grpc/Status.Code // kotlinx.rpc.grpc/Status.code.|(){}[0]
+ abstract val description // kotlinx.rpc.grpc/Status.description|{}description[0]
+ abstract fun (): kotlin/String? // kotlinx.rpc.grpc/Status.description.|(){}[0]
+
+ final enum class Code : kotlin/Enum { // kotlinx.rpc.grpc/Status.Code|null[0]
+ enum entry ABORTED // kotlinx.rpc.grpc/Status.Code.ABORTED|null[0]
+ enum entry ALREADY_EXISTS // kotlinx.rpc.grpc/Status.Code.ALREADY_EXISTS|null[0]
+ enum entry CANCELLED // kotlinx.rpc.grpc/Status.Code.CANCELLED|null[0]
+ enum entry DATA_LOSS // kotlinx.rpc.grpc/Status.Code.DATA_LOSS|null[0]
+ enum entry DEADLINE_EXCEEDED // kotlinx.rpc.grpc/Status.Code.DEADLINE_EXCEEDED|null[0]
+ enum entry FAILED_PRECONDITION // kotlinx.rpc.grpc/Status.Code.FAILED_PRECONDITION|null[0]
+ enum entry INTERNAL // kotlinx.rpc.grpc/Status.Code.INTERNAL|null[0]
+ enum entry INVALID_ARGUMENT // kotlinx.rpc.grpc/Status.Code.INVALID_ARGUMENT|null[0]
+ enum entry NOT_FOUND // kotlinx.rpc.grpc/Status.Code.NOT_FOUND|null[0]
+ enum entry OK // kotlinx.rpc.grpc/Status.Code.OK|null[0]
+ enum entry OUT_OF_RANGE // kotlinx.rpc.grpc/Status.Code.OUT_OF_RANGE|null[0]
+ enum entry PERMISSION_DENIED // kotlinx.rpc.grpc/Status.Code.PERMISSION_DENIED|null[0]
+ enum entry RESOURCE_EXHAUSTED // kotlinx.rpc.grpc/Status.Code.RESOURCE_EXHAUSTED|null[0]
+ enum entry UNAUTHENTICATED // kotlinx.rpc.grpc/Status.Code.UNAUTHENTICATED|null[0]
+ enum entry UNAVAILABLE // kotlinx.rpc.grpc/Status.Code.UNAVAILABLE|null[0]
+ enum entry UNIMPLEMENTED // kotlinx.rpc.grpc/Status.Code.UNIMPLEMENTED|null[0]
+ enum entry UNKNOWN // kotlinx.rpc.grpc/Status.Code.UNKNOWN|null[0]
+
+ final val entries // kotlinx.rpc.grpc/Status.Code.entries|#static{}entries[0]
+ final fun (): kotlin.enums/EnumEntries // kotlinx.rpc.grpc/Status.Code.entries.|#static(){}[0]
+ final val value // kotlinx.rpc.grpc/Status.Code.value|{}value[0]
+ final fun (): kotlin/Int // kotlinx.rpc.grpc/Status.Code.value.|(){}[0]
+ final val valueAscii // kotlinx.rpc.grpc/Status.Code.valueAscii|{}valueAscii[0]
+ final fun (): kotlin/ByteArray // kotlinx.rpc.grpc/Status.Code.valueAscii.|(){}[0]
+
+ final fun valueOf(kotlin/String): kotlinx.rpc.grpc/Status.Code // kotlinx.rpc.grpc/Status.Code.valueOf|valueOf#static(kotlin.String){}[0]
+ final fun values(): kotlin/Array // kotlinx.rpc.grpc/Status.Code.values|values#static(){}[0]
+ }
+}
+
+abstract interface kotlinx.rpc.grpc/StatusRuntimeException { // kotlinx.rpc.grpc/StatusRuntimeException|null[0]
+ abstract val status // kotlinx.rpc.grpc/StatusRuntimeException.status|{}status[0]
+ abstract fun (): kotlinx.rpc.grpc/Status // kotlinx.rpc.grpc/StatusRuntimeException.status.|(){}[0]
+}
+
+abstract class <#A: kotlinx.rpc.grpc/ManagedChannelBuilder<#A>> kotlinx.rpc.grpc/ManagedChannelBuilder { // kotlinx.rpc.grpc/ManagedChannelBuilder|null[0]
+ constructor () // kotlinx.rpc.grpc/ManagedChannelBuilder.|(){}[0]
+}
+
+abstract class <#A: kotlinx.rpc.grpc/ServerBuilder<#A>> kotlinx.rpc.grpc/ServerBuilder { // kotlinx.rpc.grpc/ServerBuilder|null[0]
+ constructor () // kotlinx.rpc.grpc/ServerBuilder.|(){}[0]
+
+ abstract fun addService(kotlinx.rpc.grpc/ServerServiceDefinition): #A // kotlinx.rpc.grpc/ServerBuilder.addService|addService(kotlinx.rpc.grpc.ServerServiceDefinition){}[0]
+ abstract fun fallbackHandlerRegistry(kotlinx.rpc.grpc/HandlerRegistry?): #A // kotlinx.rpc.grpc/ServerBuilder.fallbackHandlerRegistry|fallbackHandlerRegistry(kotlinx.rpc.grpc.HandlerRegistry?){}[0]
+}
+
+abstract class kotlinx.rpc.grpc/HandlerRegistry { // kotlinx.rpc.grpc/HandlerRegistry|null[0]
+ constructor () // kotlinx.rpc.grpc/HandlerRegistry.|(){}[0]
+}
+
+abstract class kotlinx.rpc.grpc/ManagedChannelPlatform { // kotlinx.rpc.grpc/ManagedChannelPlatform|null[0]
+ constructor () // kotlinx.rpc.grpc/ManagedChannelPlatform.|(){}[0]
+}
+
+final class kotlinx.rpc.grpc/GrpcClient : kotlinx.rpc/RpcClient { // kotlinx.rpc.grpc/GrpcClient|null[0]
+ final fun <#A1: kotlin/Any?> callServerStreaming(kotlinx.rpc/RpcCall): kotlinx.coroutines.flow/Flow<#A1> // kotlinx.rpc.grpc/GrpcClient.callServerStreaming|callServerStreaming(kotlinx.rpc.RpcCall){0§}[0]
+ final fun shutdown() // kotlinx.rpc.grpc/GrpcClient.shutdown|shutdown(){}[0]
+ final fun shutdownNow() // kotlinx.rpc.grpc/GrpcClient.shutdownNow|shutdownNow(){}[0]
+ final suspend fun <#A1: kotlin/Any?> call(kotlinx.rpc/RpcCall): #A1 // kotlinx.rpc.grpc/GrpcClient.call|call(kotlinx.rpc.RpcCall){0§}[0]
+ final suspend fun awaitTermination(kotlin.time/Duration) // kotlinx.rpc.grpc/GrpcClient.awaitTermination|awaitTermination(kotlin.time.Duration){}[0]
+}
+
+final class kotlinx.rpc.grpc/GrpcServer : kotlinx.rpc.grpc/Server, kotlinx.rpc/RpcServer { // kotlinx.rpc.grpc/GrpcServer|null[0]
+ final val isShutdown // kotlinx.rpc.grpc/GrpcServer.isShutdown|{}isShutdown[0]
+ final fun (): kotlin/Boolean // kotlinx.rpc.grpc/GrpcServer.isShutdown.|(){}[0]
+ final val isTerminated // kotlinx.rpc.grpc/GrpcServer.isTerminated|{}isTerminated[0]
+ final fun (): kotlin/Boolean // kotlinx.rpc.grpc/GrpcServer.isTerminated.|(){}[0]
+ final val port // kotlinx.rpc.grpc/GrpcServer.port|{}port[0]
+ final fun (): kotlin/Int // kotlinx.rpc.grpc/GrpcServer.port.|(){}[0]
+
+ final fun <#A1: kotlin/Any> deregisterService(kotlin.reflect/KClass<#A1>) // kotlinx.rpc.grpc/GrpcServer.deregisterService|deregisterService(kotlin.reflect.KClass<0:0>){0§}[0]
+ final fun <#A1: kotlin/Any> registerService(kotlin.reflect/KClass<#A1>, kotlin/Function0<#A1>) // kotlinx.rpc.grpc/GrpcServer.registerService|registerService(kotlin.reflect.KClass<0:0>;kotlin.Function0<0:0>){0§}[0]
+ final fun shutdown(): kotlinx.rpc.grpc/GrpcServer // kotlinx.rpc.grpc/GrpcServer.shutdown|shutdown(){}[0]
+ final fun shutdownNow(): kotlinx.rpc.grpc/GrpcServer // kotlinx.rpc.grpc/GrpcServer.shutdownNow|shutdownNow(){}[0]
+ final fun start(): kotlinx.rpc.grpc/GrpcServer // kotlinx.rpc.grpc/GrpcServer.start|start(){}[0]
+ final suspend fun awaitTermination(kotlin.time/Duration): kotlinx.rpc.grpc/GrpcServer // kotlinx.rpc.grpc/GrpcServer.awaitTermination|awaitTermination(kotlin.time.Duration){}[0]
+}
+
+final class kotlinx.rpc.grpc/ServerServiceDefinition { // kotlinx.rpc.grpc/ServerServiceDefinition|null[0]
+ constructor () // kotlinx.rpc.grpc/ServerServiceDefinition.|(){}[0]
+}
+
+final fun kotlinx.rpc.grpc/GrpcClient(kotlin/String, kotlin/Function1, kotlin/Unit> = ...): kotlinx.rpc.grpc/GrpcClient // kotlinx.rpc.grpc/GrpcClient|GrpcClient(kotlin.String;kotlin.Function1,kotlin.Unit>){}[0]
+final fun kotlinx.rpc.grpc/GrpcClient(kotlin/String, kotlin/Int, kotlin/Function1, kotlin/Unit> = ...): kotlinx.rpc.grpc/GrpcClient // kotlinx.rpc.grpc/GrpcClient|GrpcClient(kotlin.String;kotlin.Int;kotlin.Function1,kotlin.Unit>){}[0]
+final fun kotlinx.rpc.grpc/GrpcServer(kotlin/Int, kotlin/Function1, kotlin/Unit> = ..., kotlin/Function1 = ...): kotlinx.rpc.grpc/GrpcServer // kotlinx.rpc.grpc/GrpcServer|GrpcServer(kotlin.Int;kotlin.Function1,kotlin.Unit>;kotlin.Function1){}[0]
+final fun kotlinx.rpc.grpc/StatusRuntimeException(kotlinx.rpc.grpc/Status): kotlinx.rpc.grpc/StatusRuntimeException // kotlinx.rpc.grpc/StatusRuntimeException|StatusRuntimeException(kotlinx.rpc.grpc.Status){}[0]
diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt
index 8d10243a2..7ba5c588e 100644
--- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt
+++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt
@@ -10,6 +10,7 @@ import kotlinx.rpc.RpcClient
import kotlinx.rpc.grpc.descriptor.GrpcClientDelegate
import kotlinx.rpc.grpc.descriptor.GrpcServiceDescriptor
import kotlinx.rpc.internal.utils.map.RpcInternalConcurrentHashMap
+import kotlin.time.Duration
/**
* GrpcClient manages gRPC communication by providing implementation for making asynchronous RPC calls.
@@ -19,6 +20,20 @@ import kotlinx.rpc.internal.utils.map.RpcInternalConcurrentHashMap
public class GrpcClient internal constructor(private val channel: ManagedChannel) : RpcClient {
private val stubs = RpcInternalConcurrentHashMap()
+ public fun shutdown() {
+ stubs.clear()
+ channel.shutdown()
+ }
+
+ public fun shutdownNow() {
+ stubs.clear()
+ channel.shutdownNow()
+ }
+
+ public suspend fun awaitTermination(duration: Duration) {
+ channel.awaitTermination(duration)
+ }
+
override suspend fun call(call: RpcCall): T {
return call.delegate().call(call)
}
@@ -39,11 +54,11 @@ public class GrpcClient internal constructor(private val channel: ManagedChannel
* Constructor function for the [GrpcClient] class.
*/
public fun GrpcClient(
- name: String,
+ hostname: String,
port: Int,
configure: ManagedChannelBuilder<*>.() -> Unit = {},
): GrpcClient {
- val channel = ManagedChannelBuilder(name, port).apply(configure).buildChannel()
+ val channel = ManagedChannelBuilder(hostname, port).apply(configure).buildChannel()
return GrpcClient(channel)
}
diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcServer.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcServer.kt
index 4600ea17c..f69865b9b 100644
--- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcServer.kt
+++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcServer.kt
@@ -4,6 +4,7 @@
package kotlinx.rpc.grpc
+import kotlinx.atomicfu.atomic
import kotlinx.rpc.RpcServer
import kotlinx.rpc.descriptor.serviceDescriptorOf
import kotlinx.rpc.grpc.annotations.Grpc
@@ -65,9 +66,13 @@ public class GrpcServer internal constructor(
return grpc.delegate.definitionFor(service)
}
+ private val buildLock = atomic(false)
+
internal fun build() {
- internalServer = Server(serverBuilder)
- isBuilt = true
+ if (buildLock.compareAndSet(expect = false, update = true)) {
+ internalServer = Server(serverBuilder)
+ isBuilt = true
+ }
}
override val isShutdown: Boolean
diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt
index 5368b683b..c7ea10b53 100644
--- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt
+++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt
@@ -68,7 +68,7 @@ public interface ManagedChannel {
*/
public expect abstract class ManagedChannelBuilder>
-internal expect fun ManagedChannelBuilder(name: String, port: Int): ManagedChannelBuilder<*>
+internal expect fun ManagedChannelBuilder(hostname: String, port: Int): ManagedChannelBuilder<*>
internal expect fun ManagedChannelBuilder(target: String): ManagedChannelBuilder<*>
internal expect fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel
diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.js.kt
index b7330da46..2218a5078 100644
--- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.js.kt
+++ b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.js.kt
@@ -20,7 +20,7 @@ internal actual fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel {
error("JS target is not supported in gRPC")
}
-internal actual fun ManagedChannelBuilder(name: String, port: Int): ManagedChannelBuilder<*> {
+internal actual fun ManagedChannelBuilder(hostname: String, port: Int): ManagedChannelBuilder<*> {
error("JS target is not supported in gRPC")
}
diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt
index 602f57c82..559007568 100644
--- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt
+++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt
@@ -24,8 +24,8 @@ internal actual fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel {
return build().toKotlin()
}
-internal actual fun ManagedChannelBuilder(name: String, port: Int): ManagedChannelBuilder<*> {
- return io.grpc.ManagedChannelBuilder.forAddress(name, port)
+internal actual fun ManagedChannelBuilder(hostname: String, port: Int): ManagedChannelBuilder<*> {
+ return io.grpc.ManagedChannelBuilder.forAddress(hostname, port)
}
internal actual fun ManagedChannelBuilder(target: String): ManagedChannelBuilder<*> {
diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt
index 69d4d7c93..41c05518d 100644
--- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt
+++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt
@@ -20,7 +20,7 @@ internal actual fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel {
error("Native target is not supported in gRPC")
}
-internal actual fun ManagedChannelBuilder(name: String, port: Int): ManagedChannelBuilder<*> {
+internal actual fun ManagedChannelBuilder(hostname: String, port: Int): ManagedChannelBuilder<*> {
error("Native target is not supported in gRPC")
}
diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.wasmJs.kt
index 0fbd98458..8e9101a03 100644
--- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.wasmJs.kt
+++ b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.wasmJs.kt
@@ -20,7 +20,7 @@ internal actual fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel {
error("WasmJS target is not supported in gRPC")
}
-internal actual fun ManagedChannelBuilder(name: String, port: Int): ManagedChannelBuilder<*> {
+internal actual fun ManagedChannelBuilder(hostname: String, port: Int): ManagedChannelBuilder<*> {
error("WasmJS target is not supported in gRPC")
}
diff --git a/grpc/grpc-ktor-server-test/api/grpc-ktor-server-test.api b/grpc/grpc-ktor-server-test/api/grpc-ktor-server-test.api
new file mode 100644
index 000000000..e69de29bb
diff --git a/grpc/grpc-ktor-server-test/build.gradle.kts b/grpc/grpc-ktor-server-test/build.gradle.kts
new file mode 100644
index 000000000..c2d5de339
--- /dev/null
+++ b/grpc/grpc-ktor-server-test/build.gradle.kts
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+plugins {
+ alias(libs.plugins.conventions.jvm)
+ alias(libs.plugins.kotlinx.rpc)
+ alias(libs.plugins.protobuf)
+}
+
+dependencies {
+ // for the jar dependency
+ testImplementation(kotlin("test"))
+ testImplementation(projects.grpc.grpcCore)
+ testImplementation(projects.grpc.grpcKtorServer)
+
+ testImplementation(libs.grpc.kotlin.stub)
+ testImplementation(libs.grpc.netty)
+
+ testImplementation(libs.ktor.server.core)
+ testImplementation(libs.ktor.server.test.host)
+ testRuntimeOnly(libs.logback.classic)
+}
+
+rpc {
+ grpc {
+ enabled = true
+
+ val globalRootDir: String by extra
+
+ plugin {
+ locator {
+ path = "$globalRootDir/protobuf-plugin/build/libs/protobuf-plugin-$version-all.jar"
+ }
+ }
+
+ tasksMatching { it.isTest }.all {
+ dependsOn(project(":protobuf-plugin").tasks.jar)
+ }
+ }
+}
diff --git a/grpc/grpc-ktor-server-test/gradle.properties b/grpc/grpc-ktor-server-test/gradle.properties
new file mode 100644
index 000000000..b68c20f8d
--- /dev/null
+++ b/grpc/grpc-ktor-server-test/gradle.properties
@@ -0,0 +1,5 @@
+#
+# Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
+#
+
+kotlinx.rpc.exclude.wasmWasi=true
diff --git a/grpc/grpc-ktor-server-test/src/test/kotlin/kotlinx/rpc/grpc/ktor/server/test/TestServer.kt b/grpc/grpc-ktor-server-test/src/test/kotlin/kotlinx/rpc/grpc/ktor/server/test/TestServer.kt
new file mode 100644
index 000000000..f299fd649
--- /dev/null
+++ b/grpc/grpc-ktor-server-test/src/test/kotlin/kotlinx/rpc/grpc/ktor/server/test/TestServer.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.rpc.grpc.ktor.server.test
+
+import io.ktor.server.testing.testApplication
+import kotlinx.rpc.grpc.GrpcClient
+import kotlin.test.Test
+import kotlinx.rpc.grpc.ktor.server.grpc
+import kotlinx.rpc.registerService
+import kotlinx.rpc.withService
+import kotlin.test.assertEquals
+import kotlin.time.Duration.Companion.minutes
+
+class KtorTestServiceImpl : KtorTestService {
+ override suspend fun sayHello(message: Hello): Hello {
+ return message
+ }
+}
+
+const val PORT = 8085
+
+class TestServer {
+ @Test
+ fun testPlainRequests() = testApplication {
+ application {
+ grpc(PORT) {
+ registerService { KtorTestServiceImpl() }
+ }
+ }
+
+ startApplication()
+
+ val client = GrpcClient("localhost", PORT) {
+ usePlaintext()
+ }
+
+ val response = client.withService().sayHello(Hello { message = "Hello" })
+ assertEquals("Hello", response.message, "Wrong response message")
+
+ client.shutdown()
+ client.awaitTermination(1.minutes)
+ }
+}
diff --git a/grpc/grpc-ktor-server-test/src/test/proto/ktor-test-service.proto b/grpc/grpc-ktor-server-test/src/test/proto/ktor-test-service.proto
new file mode 100644
index 000000000..2dfd01e0b
--- /dev/null
+++ b/grpc/grpc-ktor-server-test/src/test/proto/ktor-test-service.proto
@@ -0,0 +1,11 @@
+syntax = "proto3";
+
+package kotlinx.rpc.grpc.ktor.server.test;
+
+message Hello {
+ string message = 1;
+}
+
+service KtorTestService {
+ rpc sayHello(Hello) returns (Hello);
+}
diff --git a/grpc/grpc-ktor-server-test/src/test/resources/logback.xml b/grpc/grpc-ktor-server-test/src/test/resources/logback.xml
new file mode 100644
index 000000000..85ffa5cfa
--- /dev/null
+++ b/grpc/grpc-ktor-server-test/src/test/resources/logback.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
diff --git a/grpc/grpc-ktor-server/api/grpc-ktor-server.api b/grpc/grpc-ktor-server/api/grpc-ktor-server.api
new file mode 100644
index 000000000..6c4a53d5d
--- /dev/null
+++ b/grpc/grpc-ktor-server/api/grpc-ktor-server.api
@@ -0,0 +1,11 @@
+public final class kotlinx/rpc/grpc/ktor/server/GrpcConfigKeys {
+ public static final field INSTANCE Lkotlinx/rpc/grpc/ktor/server/GrpcConfigKeys;
+ public static final field grpcHostPortPath Ljava/lang/String;
+}
+
+public final class kotlinx/rpc/grpc/ktor/server/ServerKt {
+ public static final fun getGrpcServerKey ()Lio/ktor/util/AttributeKey;
+ public static final fun grpc (Lio/ktor/server/application/Application;ILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lkotlinx/rpc/grpc/GrpcServer;
+ public static synthetic fun grpc$default (Lio/ktor/server/application/Application;ILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/rpc/grpc/GrpcServer;
+}
+
diff --git a/grpc/grpc-ktor-server/api/grpc-ktor-server.klib.api b/grpc/grpc-ktor-server/api/grpc-ktor-server.klib.api
new file mode 100644
index 000000000..03c79bc15
--- /dev/null
+++ b/grpc/grpc-ktor-server/api/grpc-ktor-server.klib.api
@@ -0,0 +1,17 @@
+// Klib ABI Dump
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
+// Rendering settings:
+// - Signature version: 2
+// - Show manifest properties: true
+// - Show declarations: true
+
+// Library unique name:
+final object kotlinx.rpc.grpc.ktor.server/GrpcConfigKeys { // kotlinx.rpc.grpc.ktor.server/GrpcConfigKeys|null[0]
+ final const val grpcHostPortPath // kotlinx.rpc.grpc.ktor.server/GrpcConfigKeys.grpcHostPortPath|{}grpcHostPortPath[0]
+ final fun (): kotlin/String // kotlinx.rpc.grpc.ktor.server/GrpcConfigKeys.grpcHostPortPath.|(){}[0]
+}
+
+final val kotlinx.rpc.grpc.ktor.server/GrpcServerKey // kotlinx.rpc.grpc.ktor.server/GrpcServerKey|{}GrpcServerKey[0]
+ final fun (): io.ktor.util/AttributeKey // kotlinx.rpc.grpc.ktor.server/GrpcServerKey.|(){}[0]
+
+final fun (io.ktor.server.application/Application).kotlinx.rpc.grpc.ktor.server/grpc(kotlin/Int = ..., kotlin/Function1, kotlin/Unit> = ..., kotlin/Function1): kotlinx.rpc.grpc/GrpcServer // kotlinx.rpc.grpc.ktor.server/grpc|grpc@io.ktor.server.application.Application(kotlin.Int;kotlin.Function1,kotlin.Unit>;kotlin.Function1){}[0]
diff --git a/grpc/grpc-ktor-server/build.gradle.kts b/grpc/grpc-ktor-server/build.gradle.kts
new file mode 100644
index 000000000..e71d8343e
--- /dev/null
+++ b/grpc/grpc-ktor-server/build.gradle.kts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+plugins {
+ alias(libs.plugins.conventions.kmp)
+ alias(libs.plugins.kotlinx.rpc)
+}
+
+kotlin {
+ sourceSets {
+ commonMain {
+ dependencies {
+ api(projects.grpc.grpcCore)
+ implementation(libs.ktor.server.core)
+ }
+ }
+ }
+}
diff --git a/grpc/grpc-ktor-server/gradle.properties b/grpc/grpc-ktor-server/gradle.properties
new file mode 100644
index 000000000..b68c20f8d
--- /dev/null
+++ b/grpc/grpc-ktor-server/gradle.properties
@@ -0,0 +1,5 @@
+#
+# Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
+#
+
+kotlinx.rpc.exclude.wasmWasi=true
diff --git a/grpc/grpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/grpc/ktor/server/Server.kt b/grpc/grpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/grpc/ktor/server/Server.kt
new file mode 100644
index 000000000..c14155668
--- /dev/null
+++ b/grpc/grpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/grpc/ktor/server/Server.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.rpc.grpc.ktor.server
+
+import io.ktor.server.application.Application
+import io.ktor.server.application.ApplicationStopped
+import io.ktor.server.application.ApplicationStopping
+import io.ktor.server.application.log
+import io.ktor.server.config.getAs
+import io.ktor.util.AttributeKey
+import kotlinx.rpc.RpcServer
+import kotlinx.rpc.grpc.GrpcServer
+import kotlinx.rpc.grpc.ServerBuilder
+
+@Suppress("ConstPropertyName")
+public object GrpcConfigKeys {
+ public const val grpcHostPortPath: String = "ktor.deployment.grpcPort"
+}
+
+/**
+ * Key used to store and retrieve the [GrpcServer] instance within the application's attributes.
+ */
+public val GrpcServerKey: AttributeKey = AttributeKey("GrpcServerPluginAttributesKey")
+
+/**
+ * Configures and starts a gRPC server within the Ktor application.
+ * This function integrates with the Ktor lifecycle and manages the lifecycle of the gRPC server
+ * by subscribing to [ApplicationStopping] and [ApplicationStopped] events.
+ * It ensures that a gRPC server is properly initialized, started, and shutdown when the application stops.
+ *
+ * Usage:
+ * ```kotlin
+ * fun Application.module() {
+ * grpc(port = PORT, configure= { /* ... */ }) {
+ * registerService { MyServiceImpl() }
+ * }
+ * }
+ * ```
+ *
+ * @param port The port on which the gRPC server will listen for incoming connections.
+ * Defaults to the value specified in the `ktor.deployment.grpcPort` configuration, or 8001 if not configured.
+ * @param configure Allows additional configuration of the gRPC server using a platform-specific [ServerBuilder].
+ * @param builder A block used to define and register gRPC services for the gRPC server.
+ * @return The instance of the initialized and running [GrpcServer].
+ * @throws IllegalStateException if a gRPC server is already installed or the specified port conflicts with
+ * an existing HTTP/HTTPS server port.
+ */
+public fun Application.grpc(
+ port: Int = environment.config.propertyOrNull(GrpcConfigKeys.grpcHostPortPath)?.getAs() ?: 8001,
+ configure: ServerBuilder<*>.() -> Unit = {},
+ builder: RpcServer.() -> Unit,
+): GrpcServer {
+ if (attributes.contains(GrpcServerKey)) {
+ error("gRPC Server is already installed, second call to grpc() is not allowed")
+ }
+
+ var newServer = false
+ val server = attributes.computeIfAbsent(GrpcServerKey) {
+ newServer = true
+ GrpcServer(port, configure, builder)
+ }
+
+ if (!newServer) {
+ error("A race detected while installing gRPC Server, second call to grpc() is not allowed")
+ }
+
+ server.start()
+ log.debug("Started gRPC server on port $port")
+
+ val stoppingHandle = monitor.subscribe(ApplicationStopping) {
+ log.debug("Stopping gRPC server")
+ attributes.getOrNull(GrpcServerKey)?.shutdown()
+ }
+
+ monitor.subscribe(ApplicationStopped) {
+ log.debug("gRPC server complete shutdown")
+ attributes.getOrNull(GrpcServerKey)?.shutdownNow()
+
+ stoppingHandle.dispose()
+ }
+
+ return server
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2f84c7fa7..255cff875 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -31,6 +31,9 @@ includePublic(":protobuf-plugin")
include(":grpc")
includePublic(":grpc:grpc-core")
+includePublic(":grpc:grpc-ktor-server")
+// temporary module until KMP project structure support in Protobuf plugin
+include(":grpc:grpc-ktor-server-test")
includePublic(":bom")