hedera-sdk-java icon indicating copy to clipboard operation
hedera-sdk-java copied to clipboard

Client.close() times out if subscribing to mirror gRPC API

Open steven-sheehy opened this issue 3 years ago • 2 comments

Description

Attempting to close a Client in the middle of a mirror node gRPC API streaming query will block for closeTimeout then throw a timeout exception. Since you can't have a subscription without the Channel that the Client manages for it, it's expected that the SDK would register any subscriptions and close them gracefully on close(). Requiring the user to manually unsubscribe from the gRPC query is awkward and error-prone since it can only unsubscribe during the happy path or if close is called manually. In the below, it can't just unsubscribe once in a finally block.

Steps to reproduce

try (Client client = Client.forMainnet()) {
  ...
  new TopicMessageQuery().setTopicId(topicId).subscribe(client, r -> {});
}

Additional context

2022-10-15T19:12:06.518-0600 ERROR ForkJoinPool-2-worker-9 i.g.i.ManagedChannelImpl [Channel<13>: (previewnet.mirrornode.hedera.com:443)] Uncaught exception in the SynchronizationContext. Panic! io.grpc.StatusRuntimeException: UNKNOWN: Uncaught exception in the SynchronizationContext. Re-thrown.
	at io.grpc.Status.asRuntimeException(Status.java:530)
	at io.grpc.internal.RetriableStream$1.uncaughtException(RetriableStream.java:75)
	at io.grpc.SynchronizationContext.drain(SynchronizationContext.java:97)
	at io.grpc.SynchronizationContext.execute(SynchronizationContext.java:127)
2022-10-15T19:12:06.522-0600 ERROR ForkJoinPool-2-worker-9 c.h.m.t.e.a.c.SDKClient startup probe:  java.util.concurrent.TimeoutException: Failed to properly shutdown all channels
	at com.hedera.hashgraph.sdk.BaseNetwork.awaitClose(BaseNetwork.java:605)
	at com.hedera.hashgraph.sdk.Client.close(Client.java:1305)
	at com.hedera.hashgraph.sdk.Client.close(Client.java:1271)

Hedera network

mainnet

Version

v2.18.0

Operating system

No response

steven-sheehy avatar Oct 16 '22 03:10 steven-sheehy

It's even worse than originally believed because SubscriptionHandle.unsubscribe() is asynchronous. So even manually unsubscribing first doesn't guarantee that it completes before Client.close(). In general, Client.close() seems to have multiple issues and can hang until timeout is reached.

steven-sheehy avatar Oct 18 '22 15:10 steven-sheehy

I am using latest version of the library (v2.27.0) and getting this error sometimes. What should be the possible solution? I am using below code.

fun getTransactionReceiptInfo(txnIdValue: ByteArray, onSuccess: (String?) -> Unit) {
        val client = getClient()
        try {
            val txnId = TransactionId.fromBytes(txnIdValue)
            TransactionReceiptQuery().setTransactionId(txnId).executeAsync(client).thenAccept {
                onSuccess(it.toBytes().toBase64String())
            }
        } catch (e: Exception) {
            onSuccess(null)
        } finally {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                client.close(Duration.ofMillis(10000))
            } else {
                client.close()
            }
        }
    }

FATAL EXCEPTION: DefaultDispatcher-worker-9 Process: cc.dropp.wallet, PID: 11111 java.util.concurrent.TimeoutException: Failed to properly shutdown all channels at com.hedera.hashgraph.sdk.BaseNetwork.awaitClose(BaseNetwork.java:573) at com.hedera.hashgraph.sdk.Client.close(Client.java:1359) at com.opencrowd.drop.common.HederaHashgraphManager.getTransactionReceiptInfo(HederaHashgraphManager.kt:94) at com.opencrowd.drop.ui.MainActivity.handleQueryRequest(MainActivity.kt:414) at com.opencrowd.drop.ui.MainActivity.access$handleQueryRequest(MainActivity.kt:55) at com.opencrowd.drop.ui.MainActivity$onStart$walletDelegate$1.onSessionRequest(MainActivity.kt:271) at com.walletconnect.web3.wallet.client.Web3Wallet$setWalletDelegate$signWalletDelegate$1.onSessionRequest(Web3Wallet.kt:43) at com.walletconnect.sign.client.SignProtocol$setWalletDelegate$1.invokeSuspend(SignProtocol.kt:56) at com.walletconnect.sign.client.SignProtocol$setWalletDelegate$1.invoke(Unknown Source:8) at com.walletconnect.sign.client.SignProtocol$setWalletDelegate$1.invoke(Unknown Source:4) at kotlinx.coroutines.flow.FlowKt__TransformKt$onEach$$inlined$unsafeTransform$1$2.emit(Emitters.kt:223) at kotlinx.coroutines.flow.SharedFlowImpl.collect$suspendImpl(SharedFlow.kt:383) at kotlinx.coroutines.flow.SharedFlowImpl$collect$1.invokeSuspend(Unknown Source:15) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42) at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)

aadityapaliwal avatar Aug 29 '23 10:08 aadityapaliwal