commit 1da8cdbd06e2c885647ab8da7a0ea9a4b475935f
parent 67c5f6813ae54148622f522202612557fa82b2f2
Author: Iván Ávalos <avalos@disroot.org>
Date: Wed, 12 Aug 2026 10:18:11 +0200
[wallet] timeout wallet-core requests and wait non-blockingly
Diffstat:
3 files changed, 48 insertions(+), 25 deletions(-)
diff --git a/wallet/src/main/java/net/taler/wallet/backend/BackendManager.kt b/wallet/src/main/java/net/taler/wallet/backend/BackendManager.kt
@@ -22,13 +22,16 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.buildJsonObject
import net.taler.qtart.TalerWalletCore
import net.taler.wallet.BuildConfig
import org.json.JSONObject
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
fun interface NotificationReceiver {
@@ -42,6 +45,7 @@ class BackendManager(
companion object {
private const val TAG = "BackendManager"
private const val TAG_CORE = "taler-wallet-embedded"
+ private const val REQUEST_TIMEOUT_MS = 60_000L
val json = Json {
ignoreUnknownKeys = true
coerceInputValues = true
@@ -73,18 +77,35 @@ class BackendManager(
}
}
- suspend fun send(operation: String, args: JSONObject? = null): ApiResponse =
- suspendCoroutine { cont ->
- requestManager.addRequest(cont) { id ->
- val request = JSONObject().apply {
- put("id", id)
- put("operation", operation)
- if (args != null) put("args", args)
+ suspend fun send(operation: String, args: JSONObject? = null): ApiResponse {
+ var requestId = -1
+ val response = withTimeoutOrNull(REQUEST_TIMEOUT_MS) {
+ suspendCancellableCoroutine { cont ->
+ requestManager.addRequest(cont) { id ->
+ requestId = id
+ val request = JSONObject().apply {
+ put("id", id)
+ put("operation", operation)
+ if (args != null) put("args", args)
+ }
+ Log.d(TAG, "sending message:\n${request.toString(2)}")
+ walletCore.sendRequest(request.toString())
+ }
+ cont.invokeOnCancellation {
+ requestManager.getAndRemoveContinuation(requestId)
}
- Log.d(TAG, "sending message:\n${request.toString(2)}")
- walletCore.sendRequest(request.toString())
}
}
+ if (response != null) return response
+ return ApiResponse.Error(
+ id = requestId,
+ operation = operation,
+ error = buildJsonObject {
+ put("hint", JsonPrimitive("wallet-core did not respond"))
+ put("message", JsonPrimitive("request '$operation' timed out"))
+ },
+ )
+ }
private fun onMessageReceived(msg: String) = scope.launch {
Log.d(TAG, "message received: $msg")
diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalanceManager.kt b/wallet/src/main/java/net/taler/wallet/balances/BalanceManager.kt
@@ -23,13 +23,13 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.distinctUntilChanged
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import net.taler.common.Amount
import net.taler.common.CurrencySpecification
import net.taler.wallet.main.TAG
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.backend.WalletBackendApi
+import net.taler.wallet.backend.WalletResponse
import net.taler.wallet.donau.DonauSummaryItem
import net.taler.wallet.exchanges.ExchangeItem
import net.taler.wallet.exchanges.ExchangeManager
@@ -78,14 +78,16 @@ class BalanceManager(
fun loadAssets(loading: Boolean = false) = scope.launch {
if (loading) mState.postValue(BalanceState.Loading)
- api.request("getBalances", BalanceResponse.serializer())
- .onError {
- Log.e(TAG, "Error retrieving balances: $it")
- mState.postValue(BalanceState.Error(it))
- }.onSuccess { res ->
- val balances = res.balances.map { balance ->
- val spec = runBlocking { exchangeManager
- .getCurrencySpecification(balance.scopeInfo) }
+ when (val response = api.request("getBalances", BalanceResponse.serializer())) {
+ is WalletResponse.Error -> {
+ Log.e(TAG, "Error retrieving balances: ${response.error}")
+ mState.postValue(BalanceState.Error(response.error))
+ }
+
+ is WalletResponse.Success -> {
+ val balances = response.result.balances.map { balance ->
+ val spec = exchangeManager
+ .getCurrencySpecification(balance.scopeInfo)
balance.copy(
available = balance.available.withSpec(spec),
pendingIncoming = balance.pendingIncoming.withSpec(spec),
@@ -93,9 +95,9 @@ class BalanceManager(
)
}
- val donauSummary = res.donauSummary?.map { item ->
- val spec = runBlocking { exchangeManager
- .getSpecForCurrency(item.amountReceiptsAvailable.currency) }
+ val donauSummary = response.result.donauSummary?.map { item ->
+ val spec = exchangeManager
+ .getSpecForCurrency(item.amountReceiptsAvailable.currency)
item.copy(
amountReceiptsAvailable = item.amountReceiptsAvailable.withSpec(spec),
amountReceiptsSubmitted = item.amountReceiptsSubmitted.withSpec(spec),
@@ -109,6 +111,7 @@ class BalanceManager(
donauSummary = donauSummary,
))
}
+ }
}
@UiThread
diff --git a/wallet/src/main/java/net/taler/wallet/donau/DonauManager.kt b/wallet/src/main/java/net/taler/wallet/donau/DonauManager.kt
@@ -21,7 +21,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
import net.taler.wallet.main.TAG
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.backend.WalletBackendApi
@@ -79,8 +78,8 @@ class DonauManager(
mDonauStatementsStatus.value = GetDonauStatementsStatus.Error(error)
}.onSuccess { res ->
val statements = res.statements.map { statement ->
- val spec = runBlocking { exchangeManager
- .getSpecForCurrency(statement.total.currency) }
+ val spec = exchangeManager
+ .getSpecForCurrency(statement.total.currency)
statement.copy(
total = statement.total.withSpec(spec)
)