hedera-sdk-java
hedera-sdk-java copied to clipboard
Client.close() times out if subscribing to mirror gRPC API
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
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.
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)