From 84251261b28d089d78f4dea3104add0e795ee028 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 3 Feb 2026 15:05:37 -0300 Subject: [PATCH 1/3] fix: implement a retry mechanism for poolUntil --- .../to/bitkit/viewmodels/TransferViewModel.kt | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) 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 From 1b080057e4da938ddf747c040beac7aacbe45cf0 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 6 Feb 2026 07:50:16 -0300 Subject: [PATCH 2/3] chore: orders refresh error spamming logs --- app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt index a36f14bb8..9a82635fa 100644 --- a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt @@ -173,7 +173,7 @@ class BlocktankRepo @Inject constructor( ) openChannelWithPaidOrders() }.onFailure { - Logger.error("Failed to refresh orders", it, context = TAG) + Logger.warn("Failed to refresh orders", it, context = TAG) } isRefreshing = false From 6ced9f00ad3a9b6a827329e77a2d9fef88f7dbd7 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 6 Feb 2026 08:49:26 -0300 Subject: [PATCH 3/3] fix: rethrow refresh errors --- .../to/bitkit/repositories/BlocktankRepo.kt | 12 +++++------ .../bitkit/repositories/BlocktankRepoTest.kt | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt index 9a82635fa..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.warn("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/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) + } }