diff --git a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt index a36f14bb8..6107e60d9 100644 --- a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt @@ -134,8 +134,8 @@ class BlocktankRepo @Inject constructor( } } - suspend fun refreshOrders() = withContext(bgDispatcher) { - if (isRefreshing) return@withContext + suspend fun refreshOrders(): Result = withContext(bgDispatcher) { + if (isRefreshing) return@withContext Result.success(Unit) isRefreshing = true runCatching { @@ -172,11 +172,9 @@ class BlocktankRepo @Inject constructor( context = TAG ) openChannelWithPaidOrders() - }.onFailure { - Logger.error("Failed to refresh orders", it, context = TAG) + }.also { + isRefreshing = false } - - isRefreshing = false } suspend fun refreshMinCjitSats() = withContext(bgDispatcher) { @@ -303,7 +301,7 @@ class BlocktankRepo @Inject constructor( ): Result = withContext(bgDispatcher) { runCatching { if (refresh) { - refreshOrders() + refreshOrders().getOrThrow() } val order = _blocktankState.value.orders.find { it.id == orderId } return@runCatching order diff --git a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt index 9a83e0472..d93c9aac6 100644 --- a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt @@ -263,19 +263,40 @@ class TransferViewModel @Inject constructor( } private suspend fun pollUntil(orderId: String, condition: (IBtOrder) -> Boolean): IBtOrder? { + var consecutiveErrors = 0 + while (true) { - val order = blocktankRepo.getOrder(orderId, refresh = true).getOrNull() - if (order == null) { - Logger.error("Order not found: '$orderId'", context = TAG) - return null - } - if (order.state2 == BtOrderState2.EXPIRED) { - Logger.error("Order expired: '$orderId'", context = TAG) - return null - } - if (condition(order)) { - return order - } + blocktankRepo.getOrder(orderId, refresh = true).fold( + onSuccess = { order -> + consecutiveErrors = 0 + + if (order == null) { + Logger.error("Order not found: '$orderId'", context = TAG) + return null + } + if (order.state2 == BtOrderState2.EXPIRED) { + Logger.error("Order expired: '$orderId'", context = TAG) + return null + } + if (condition(order)) { + return order + } + }, + onFailure = { + consecutiveErrors++ + Logger.warn( + "Failed to fetch order '$orderId' (attempt $consecutiveErrors/$MAX_CONSECUTIVE_ERRORS)", + e = it, + context = TAG + ) + + if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) { + Logger.error("Too many consecutive errors polling order '$orderId', giving up", context = TAG) + return null + } + } + ) + delay(POLL_INTERVAL_MS) } } @@ -539,6 +560,7 @@ class TransferViewModel @Inject constructor( private const val TAG = "TransferViewModel" private const val MIN_STEP_DELAY_MS = 500L private const val POLL_INTERVAL_MS = 2_500L + private const val MAX_CONSECUTIVE_ERRORS = 5 const val LN_SETUP_STEP_0 = 0 const val LN_SETUP_STEP_1 = 1 const val LN_SETUP_STEP_2 = 2 diff --git a/app/src/test/java/to/bitkit/repositories/BlocktankRepoTest.kt b/app/src/test/java/to/bitkit/repositories/BlocktankRepoTest.kt index 3f5a879b2..6ab2625df 100644 --- a/app/src/test/java/to/bitkit/repositories/BlocktankRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/BlocktankRepoTest.kt @@ -172,4 +172,24 @@ class BlocktankRepoTest : BaseUnitTest() { assertTrue(result.isSuccess) assertNull(result.getOrThrow()) } + + @Test + fun `refreshOrders returns failure when server throws`() = test { + sut = createSut() + wheneverBlocking { coreService.blocktank.orders(refresh = true) }.thenThrow(RuntimeException("Network error")) + + val result = sut.refreshOrders() + + assertTrue(result.isFailure) + } + + @Test + fun `getOrder returns failure when refresh fails`() = test { + sut = createSut() + wheneverBlocking { coreService.blocktank.orders(refresh = true) }.thenThrow(RuntimeException("Network error")) + + val result = sut.getOrder(testOrder1.id, refresh = true) + + assertTrue(result.isFailure) + } }