Skip to content

Commit 30936a8

Browse files
committed
grpc-native: Address PR comments
Signed-off-by: Johannes Zottele <[email protected]>
1 parent c8c62c1 commit 30936a8

File tree

11 files changed

+74
-72
lines changed

11 files changed

+74
-72
lines changed

grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/ClientCall.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ import kotlinx.rpc.internal.utils.InternalRpcApi
2222
* Internally, it buffers the messages.
2323
* - On Native, you can only call [sendMessage] when [isReady] returns true. There is no buffering; therefore,
2424
* only one message can be sent at a time.
25+
*
26+
* Request message number:
27+
* - On JVM, there is no limit on the number of messages you can [request].
28+
* - On Native, you can only call [request] with up to `16`.
2529
*/
2630
@InternalRpcApi
2731
public expect abstract class ClientCall<Request, Response> {

grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/RawClientTest.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
package kotlinx.rpc.grpc.test
66

7-
import grpc.examples.echo.*
87
import kotlinx.coroutines.delay
98
import kotlinx.coroutines.flow.flow
109
import kotlinx.coroutines.test.runTest
@@ -17,6 +16,7 @@ import kotlin.test.assertEquals
1716
/**
1817
* Tests for JVM and Native clients.
1918
*/
19+
// TODO: Start echo service server automatically
2020
class RawClientTest {
2121

2222
@Test
@@ -102,9 +102,5 @@ class RawClientTest {
102102
channel.shutdown()
103103
channel.awaitTermination()
104104
}
105-
106-
107105
}
108-
109-
110106
}

grpc/grpc-core/src/nativeInterop/cinterop/libkgrpc.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
headers = kgrpc.h grpc/grpc.h grpc/credentials.h grpc/byte_buffer_reader.h \
2-
grpc/support/alloc.h
2+
grpc/support/alloc.h grpc/impl/propagation_bits.h
33

44
headerFilter= kgrpc.h grpc/slice.h grpc/byte_buffer.h grpc/grpc.h \
55
grpc/impl/grpc_types.h grpc/credentials.h grpc/support/time.h grpc/byte_buffer_reader.h \
6-
grpc/support/alloc.h
6+
grpc/support/alloc.h grpc/impl/propagation_bits.h
77

88
noStringConversion = grpc_slice_from_copied_buffer my_grpc_slice_from_copied_buffer
99
strictEnums = grpc_status_code grpc_connectivity_state grpc_call_error

grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public actual abstract class ManagedChannelPlatform : GrpcChannel()
2121
*/
2222
public actual abstract class ManagedChannelBuilder<T : ManagedChannelBuilder<T>> {
2323
public actual open fun usePlaintext(): T {
24-
error("Builder does not override usePlaintext()")
24+
error("Builder does not support usePlaintext()")
2525
}
2626
}
2727

grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/CallbackFuture.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import kotlinx.atomicfu.atomic
1111
*/
1212
internal class CallbackFuture<T : Any> {
1313
private sealed interface State<out T> {
14-
data class Pending<T>(val cbs: List<(T) -> Unit> = emptyList()) : State<T>
14+
data class Pending<T>(val callbacks: List<(T) -> Unit> = emptyList()) : State<T>
1515
data class Done<T>(val value: T) : State<T>
1616
}
1717

@@ -22,7 +22,7 @@ internal class CallbackFuture<T : Any> {
2222
while (true) {
2323
when (val s = state.value) {
2424
is State.Pending -> if (state.compareAndSet(s, State.Done(result))) {
25-
toInvoke = s.cbs
25+
toInvoke = s.callbacks
2626
break
2727
}
2828

@@ -40,7 +40,7 @@ internal class CallbackFuture<T : Any> {
4040
}
4141

4242
is State.Pending -> {
43-
val next = State.Pending(s.cbs + callback) // copy-on-write append
43+
val next = State.Pending(s.callbacks + callback) // copy-on-write append
4444
if (state.compareAndSet(s, next)) return
4545
}
4646
}

grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/CompletionQueue.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,18 @@ internal sealed interface BatchResult {
3030
/**
3131
* Happens when the batch couldn't be submitted for some reason.
3232
*/
33-
data class CallError(val error: grpc_call_error) : BatchResult
33+
data class SubmitError(val error: grpc_call_error) : BatchResult
3434

3535
/**
3636
* Happens when the batch was successfully submitted.
3737
* The [future] will be completed with `true` if the batch was successful, `false` otherwise.
3838
* In the case of `false`, the status of the `RECV_STATUS_ON_CLIENT` batch will provide the error details.
3939
*/
40-
data class Called(val future: CallbackFuture<Boolean>) : BatchResult
40+
data class Submitted(val future: CallbackFuture<Boolean>) : BatchResult
4141
}
4242

4343
/**
44-
* The Kotlin wrapper for the native grpc_completion_queue.
44+
* A thread-safe Kotlin wrapper for the native grpc_completion_queue.
4545
* It is based on the "new" callback API; therefore, there are no kotlin-side threads required to poll
4646
* the queue.
4747
* Users can attach to the returned [CallbackFuture] if the batch was successfully submitted (see [BatchResult]).
@@ -50,7 +50,7 @@ internal class CompletionQueue {
5050

5151
internal enum class State { OPEN, SHUTTING_DOWN, CLOSED }
5252

53-
// if the queue was called with forceShutdown = true,
53+
// if the shutdown() was called with forceShutdown = true,
5454
// it will reject all new batches and wait for all current ones to finish.
5555
private var forceShutdown = false
5656

@@ -122,16 +122,20 @@ internal class CompletionQueue {
122122
if (err != grpc_call_error.GRPC_CALL_OK) {
123123
// if the call was not successful, the callback will not be invoked.
124124
deleteCbTag(tag)
125-
return BatchResult.CallError(err)
125+
return BatchResult.SubmitError(err)
126126
}
127127

128-
return BatchResult.Called(completion)
128+
return BatchResult.Submitted(completion)
129129
}
130130

131131
/**
132132
* Shuts down the queue.
133133
* The method returns immediately, but the queue will be shut down asynchronously.
134134
* The returned [CallbackFuture] will be completed with `Unit` when the queue is shut down.
135+
*
136+
* @param force if `true`, the queue will reject all new batches with [BatchResult.CQShutdown].
137+
* Otherwise, the queue allows submitting new batches and shutdown only when there are no more
138+
* ongoing batches.
135139
*/
136140
fun shutdown(force: Boolean = false): CallbackFuture<Unit> {
137141
if (force) {

grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeClientCall.kt

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ internal class NativeClientCall<Request, Response>(
5454
private var closed = atomic(false)
5555

5656
// tracks how many operations are in flight (not yet completed by the listener).
57-
// if 0, there are no more operations (except for the RECV_STATUS_ON_CLIENT op).
57+
// if 0 and we got a closeInfo (containing the status), there are no more ongoing operations.
5858
// in this case, we can safely call onClose on the listener.
5959
// we need this mechanism to ensure that onClose is not called while any other callback is still running
6060
// on the listener.
@@ -64,7 +64,7 @@ internal class NativeClientCall<Request, Response>(
6464
// if null, the call is still in progress. otherwise, the call can be closed as soon as inFlight is 0.
6565
private val closeInfo = atomic<Pair<Status, GrpcTrailers>?>(null)
6666

67-
// we currently don't buffer messages, so after one `sendMessage` call, ready turns false.
67+
// we currently don't buffer messages, so after one `sendMessage` call, ready turns false. (KRPC-192)
6868
private val ready = atomic(true)
6969

7070
/**
@@ -81,11 +81,11 @@ internal class NativeClientCall<Request, Response>(
8181
* AND the corresponding listener callback returned.
8282
*
8383
* If the counter reaches 0, no more listener callbacks are executed, and the call can be closed by
84-
* calling [tryDeliverClose].
84+
* calling [tryToCloseCall].
8585
*/
8686
private fun endOp() {
8787
if (inFlight.decrementAndGet() == 0) {
88-
tryDeliverClose()
88+
tryToCloseCall()
8989
}
9090
}
9191

@@ -97,23 +97,23 @@ internal class NativeClientCall<Request, Response>(
9797
* - If the [inFlight] counter is not 0, this does nothing.
9898
* - Otherwise, the listener's onClose callback is invoked and the call is closed.
9999
*/
100-
private fun tryDeliverClose() {
101-
val s = closeInfo.value ?: return
100+
private fun tryToCloseCall() {
101+
val info = closeInfo.value ?: return
102102
if (inFlight.value == 0 && closed.compareAndSet(expect = false, update = true)) {
103-
val lst = checkNotNull(listener) { "Not yet started" }
103+
val lst = checkNotNull(listener) { internalError("Not yet started") }
104104
// allows the managed channel to join for the call to finish.
105105
callJob.complete()
106-
lst.onClose(s.first, s.second)
106+
lst.onClose(info.first, info.second)
107107
}
108108
}
109109

110110
/**
111-
* Sets the [closeInfo] and calls [tryDeliverClose].
112-
* This is called as soon as the RECV_STATUS_ON_CLIENT batch is finished.
111+
* Sets the [closeInfo] and calls [tryToCloseCall].
112+
* This is called as soon as the RECV_STATUS_ON_CLIENT batch (started with [startRecvStatus]) finished.
113113
*/
114114
private fun markClosePending(status: Status, trailers: GrpcTrailers) {
115115
if (closeInfo.compareAndSet(null, Pair(status, trailers))) {
116-
tryDeliverClose()
116+
tryToCloseCall()
117117
}
118118
}
119119

@@ -132,8 +132,8 @@ internal class NativeClientCall<Request, Response>(
132132
responseListener: Listener<Response>,
133133
headers: GrpcTrailers,
134134
) {
135-
check(listener == null) { "Already started" }
136-
check(!cancelled) { "Already cancelled." }
135+
check(listener == null) { internalError("Already started") }
136+
check(!cancelled) { internalError("Already cancelled.") }
137137

138138
listener = responseListener
139139

@@ -164,7 +164,7 @@ internal class NativeClientCall<Request, Response>(
164164
beginOp()
165165

166166
when (val callResult = cq.runBatch(this@NativeClientCall, ops, nOps)) {
167-
is BatchResult.Called -> {
167+
is BatchResult.Submitted -> {
168168
callResult.future.onComplete { success ->
169169
try {
170170
if (success) {
@@ -184,7 +184,7 @@ internal class NativeClientCall<Request, Response>(
184184
cancelInternal(grpc_status_code.GRPC_STATUS_UNAVAILABLE, "Channel shutdown")
185185
}
186186

187-
is BatchResult.CallError -> {
187+
is BatchResult.SubmitError -> {
188188
cleanup()
189189
endOp()
190190
cancelInternal(
@@ -196,7 +196,7 @@ internal class NativeClientCall<Request, Response>(
196196
}
197197

198198
/**
199-
* Starts a batch operation to receive the status from the completion queue.
199+
* Starts a batch operation to receive the status from the completion queue (RECV_STATUS_ON_CLIENT).
200200
* This operation is bound to the lifetime of the call, so it will finish once all other operations are done.
201201
* If this operation fails, it will call [markClosePending] with the corresponding error, as the entire call
202202
* si considered failed.
@@ -206,7 +206,7 @@ internal class NativeClientCall<Request, Response>(
206206
*/
207207
@OptIn(ExperimentalStdlibApi::class)
208208
private fun startRecvStatus(): Boolean {
209-
checkNotNull(listener) { "Not yet started" }
209+
checkNotNull(listener) { internalError("Not yet started") }
210210
val arena = Arena()
211211
val statusCode = arena.alloc<grpc_status_code.Var>()
212212
val statusDetails = arena.alloc<grpc_slice>()
@@ -221,7 +221,7 @@ internal class NativeClientCall<Request, Response>(
221221
}
222222

223223
when (val callResult = cq.runBatch(this@NativeClientCall, op.ptr, 1u)) {
224-
is BatchResult.Called -> {
224+
is BatchResult.Submitted -> {
225225
callResult.future.onComplete {
226226
val details = statusDetails.toByteArray().toKString()
227227
val status = Status(statusCode.value.toKotlin(), details, null)
@@ -244,7 +244,7 @@ internal class NativeClientCall<Request, Response>(
244244
return false
245245
}
246246

247-
is BatchResult.CallError -> {
247+
is BatchResult.SubmitError -> {
248248
arena.clear()
249249
markClosePending(
250250
Status(StatusCode.INTERNAL, "Failed to start call: ${callResult.error}"),
@@ -285,9 +285,11 @@ internal class NativeClientCall<Request, Response>(
285285
* This must only be called again after [numMessages] were received in the [Listener.onMessage] callback.
286286
*/
287287
override fun request(numMessages: Int) {
288-
check(numMessages > 0) { "numMessages must be > 0" }
289-
val listener = checkNotNull(listener) { "Not yet started" }
290-
check(!cancelled) { "Already cancelled" }
288+
check(numMessages > 0) { internalError("numMessages must be > 0") }
289+
// limit numMessages to prevent potential stack overflows
290+
check(numMessages <= 16) { internalError("numMessages must be <= 16") }
291+
val listener = checkNotNull(listener) { internalError("Not yet started") }
292+
check(!cancelled) { internalError("Already cancelled") }
291293

292294
var remainingMessages = numMessages
293295

@@ -333,8 +335,8 @@ internal class NativeClientCall<Request, Response>(
333335
}
334336

335337
override fun halfClose() {
336-
check(!halfClosed) { "Already half closed." }
337-
check(!cancelled) { "Already cancelled." }
338+
check(!halfClosed) { internalError("Already half closed.") }
339+
check(!cancelled) { internalError("Already cancelled.") }
338340
halfClosed = true
339341

340342
val arena = Arena()
@@ -350,10 +352,10 @@ internal class NativeClientCall<Request, Response>(
350352
override fun isReady(): Boolean = ready.value
351353

352354
override fun sendMessage(message: Request) {
353-
checkNotNull(listener) { "Not yet started" }
354-
check(!halfClosed) { "Already half closed." }
355-
check(!cancelled) { "Already cancelled." }
356-
check(isReady()) { "Not yet ready." }
355+
checkNotNull(listener) { internalError("Not yet started") }
356+
check(!halfClosed) { internalError("Already half closed.") }
357+
check(!cancelled) { internalError("Already cancelled.") }
358+
check(isReady()) { internalError("Not yet ready.") }
357359

358360
// set ready false, as only one message can be sent at a time.
359361
ready.value = false
@@ -368,14 +370,12 @@ internal class NativeClientCall<Request, Response>(
368370
}
369371

370372
runBatch(op.ptr, 1u, cleanup = {
371-
// no mater what happens, we need to set ready to true again.
372-
turnReady()
373-
374373
// actual cleanup
375374
grpc_byte_buffer_destroy(byteBuffer)
376375
arena.clear()
377376
}) {
378-
// Nothing to do here
377+
// set ready true, as we can now send another message.
378+
turnReady()
379379
}
380380
}
381381
}

grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeGrpcLibrary.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,16 @@ internal object GrpcRuntime {
2121
/** Acquire a runtime reference. Must be closed exactly once. */
2222
fun acquire(): AutoCloseable {
2323
refLock.withLock {
24-
val prev = 0
25-
refs++
24+
val prev = refs++
2625
if (prev == 0) grpc_init()
2726
}
2827
return object : AutoCloseable {
29-
private var done = atomic(false)
28+
private val done = atomic(false)
3029
override fun close() {
3130
if (!done.compareAndSet(expect = false, update = true)) return
3231
refLock.withLock {
3332
val now = --refs
34-
require(now >= 0) { "release() without matching acquire()" }
33+
require(now >= 0) { internalError("release() without matching acquire()") }
3534
if (now == 0) {
3635
grpc_shutdown()
3736
}

0 commit comments

Comments
 (0)