1
votes

I have a grpc-js server and a Kotlin for Android client that makes a server streaming call. This is the GRPCService class.

class GRPCService {
    private val mChannel = ManagedChannelBuilder
        .forAddress(GRPC_HOST_ADDRESS, GRPC_HOST_PORT)
        .usePlaintext()
        .keepAliveTime(10, TimeUnit.SECONDS)
        .keepAliveWithoutCalls(true)
        .build()
    val asyncStub : ResponderServiceGrpc.ResponderServiceStub = 
        ResponderServiceGrpc.newStub(mChannel)
}

And the method is called from a foreground service.

override fun onCreate() {
    super.onCreate()
    ...
    startForeground(MyNotificationBuilder.SERVICE_NOTIFICATION_ID, notificationBuilder.getServiceNotification())
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    val userId = sharedPreferencesManager.getInt(SharedPreferencesManager.USER_ID)
    val taskRequest = Responder.TaskRequest.newBuilder()
        .setUserId(userId)
        .build()

    grpcService.asyncStub.getTasks(taskRequest, object :
        StreamObserver<Responder.TaskResponse> {
        override fun onCompleted() {
            Log.d("grpc Tasks", "Completed")
        }

        override fun onError(t: Throwable?) {
            Log.d("grpc error cause", t?.cause.toString())
            t?.cause?.printStackTrace()
            Log.d("grpc error", "AFTER CAUSE")
            t!!.printStackTrace()
        }

        override fun onNext(value: Responder.TaskResponse?) {
            if (value != null) {
            
                when (value.command) {
                   ...
                }
            }
        }
    })
    return super.onStartCommand(intent, flags, startId)
}

The connection opens and stays open for about a minute of no communication and then fails with the following error.

D/grpc error cause: null
D/grpc error: AFTER CAUSE
io.grpc.StatusRuntimeException: INTERNAL: Internal error
io.grpc.Status.asRuntimeException(Status.java:533)
io.grpc.stub.ClientCalls$StreamObserverToCallListenerAdapter.onClose(ClientCalls.java:460)
io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:426)
io.grpc.internal.ClientCallImpl.access$500(ClientCallImpl.java:66)
io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.close(ClientCallImpl.java:689)
io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.access$900(ClientCallImpl.java:577)
io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:751)
io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:740)
io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123)

The grpc-js server is created with the following options.

var server = new grpc.Server({
    "grpc.http2.min_ping_interval_without_data_ms" : 10000,
    "grpc.keepalive_permit_without_calls" : true,
    "grpc.http2.min_time_between_pings_ms" : 10000,
    "grpc.keepalive_time_ms" : 10000,
    "grpc.http2.max_pings_without_data" : 0,
    'grpc.http2.min_ping_interval_without_data_ms':  5000
});

I never received the too many pings error either.

I noticed that if there is periodic communication (like the server pinging the client with a small amount of data every 30s or so) through this connection then I don't get the error and the connection stays open for as long as the pinging continues (tested for 2 days).

How do I keep the connection open without resorting to periodically pinging the client?

2

2 Answers

3
votes

The managed channel has a property called keepAliveWithoutCalls which has a default value of false as seen here. If this is not set to true then the keepAlive will not happen if there are no current active calls happening. You would need to set this like so:

private val mChannel = ManagedChannelBuilder
    .forAddress(GRPC_HOST_ADDRESS, GRPC_HOST_PORT)
    .usePlaintext()
    .keepAliveTime(30, TimeUnit.SECONDS)
    .keepAliveWithoutCalls(true)
    .build()

There is a possibility that you will have to do some other settings on the server as well to have the connection stay open without any data passing. You might get an error on the server saying "too many pings". This happens because there are some other settings GRPC needs. I am not sure exactly how to achieve this with a JS server but it shouldn't be too difficult. These settings include:

GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS
Minimum allowed time between a server receiving successive ping frames without sending any data/header/window_update frame.

And this one:

GRPC_ARG_HTTP2_MIN_SENT_PING_INTERVAL_WITHOUT_DATA_MS
Minimum time between sending successive ping frames without receiving any data/header/window_update frame, Int valued, milliseconds.

And this one:

GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS
Is it permissible to send keepalive pings without any outstanding streams.

There is a Keepalive User Guide for gRPC which I suggest you read through to understand how gRPC should keep connections open. This is the core standard that all server and client implementations should follow, but I have noticed this is not always the case. You can have a look at a previous but similar question I asked a while back here.

1
votes

Have you tried the ManagedChannelBuilder.keepAliveTime setting (https://github.com/grpc/grpc-java/blob/master/api/src/main/java/io/grpc/ManagedChannelBuilder.java#L357) ? I am assuming it will work in the middle of a server streaming call.