hashgraph / hedera-sdk-java

Hedera™ Hashgraph SDK for Java
https://docs.hedera.com/guides/docs/sdks
Apache License 2.0
205 stars 114 forks source link

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

Open steven-sheehy opened 1 year ago

steven-sheehy commented 1 year ago

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 commented 1 year ago

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.

aadityapaliwal commented 1 year ago

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)