From 562f44fba5a7e0e265cb64a1eb81df33888842cc Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 28 Jan 2026 09:23:57 -0300 Subject: [PATCH 1/6] fix: perform a last sync before stop to ensure a updated state --- app/src/main/java/to/bitkit/services/LightningService.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/to/bitkit/services/LightningService.kt b/app/src/main/java/to/bitkit/services/LightningService.kt index 84fe835cb..34644d593 100644 --- a/app/src/main/java/to/bitkit/services/LightningService.kt +++ b/app/src/main/java/to/bitkit/services/LightningService.kt @@ -246,6 +246,12 @@ class LightningService @Inject constructor( return } + runCatching { + Logger.debug("Performing final sync before shutdown…", context = TAG) + ServiceQueue.LDK.background { node.syncWallets() } + Logger.debug("Final sync completed", context = TAG) + }.onFailure { Logger.warn("Final sync failed, proceeding with shutdown", it, context = TAG) } + Logger.debug("Stopping node…", context = TAG) ServiceQueue.LDK.background { runCatching { node.stop() } From 27123f17a3ed5cde364d4a3f1a600b793a8ab55d Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 28 Jan 2026 09:28:47 -0300 Subject: [PATCH 2/6] fix: ensure node stops before service destruction --- .../androidServices/LightningNodeService.kt | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt b/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt index 084093db6..cc36d353d 100644 --- a/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt +++ b/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt @@ -15,6 +15,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeoutOrNull import org.lightningdevkit.ldknode.Event import to.bitkit.App import to.bitkit.R @@ -144,21 +146,26 @@ class LightningNodeService : Service() { } override fun onDestroy() { - Logger.debug("onDestroy", context = TAG) - serviceScope.launch { - lightningRepo.stop() - serviceScope.cancel() + Logger.debug("onDestroy started", context = TAG) + runBlocking { + withTimeoutOrNull(NODE_STOP_TIMEOUT_MS) { + lightningRepo.stop() + } ?: Logger.warn("Node stop timed out during onDestroy", context = TAG) } + serviceScope.cancel() + Logger.debug("onDestroy completed", context = TAG) super.onDestroy() } @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) override fun onTimeout(startId: Int, fgsType: Int) { Logger.warn("Foreground service timeout reached", context = TAG) - serviceScope.launch { - lightningRepo.stop() - stopSelf() + runBlocking { + withTimeoutOrNull(FORCE_STOP_TIMEOUT_MS) { + lightningRepo.stop() + } } + stopSelf() super.onTimeout(startId, fgsType) } @@ -168,5 +175,7 @@ class LightningNodeService : Service() { const val CHANNEL_ID_NODE = "bitkit_notification_channel_node" const val TAG = "LightningNodeService" const val ACTION_STOP_SERVICE_AND_APP = "to.bitkit.androidServices.action.STOP_SERVICE_AND_APP" + private const val NODE_STOP_TIMEOUT_MS = 10_000L + private const val FORCE_STOP_TIMEOUT_MS = 3_000L } } From 20e246a5add36d6f916d84b2951faf883da75bb1 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 28 Jan 2026 09:30:49 -0300 Subject: [PATCH 3/6] fix: reduce time out --- .../java/to/bitkit/androidServices/LightningNodeService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt b/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt index cc36d353d..ad8ecfe74 100644 --- a/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt +++ b/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt @@ -175,7 +175,7 @@ class LightningNodeService : Service() { const val CHANNEL_ID_NODE = "bitkit_notification_channel_node" const val TAG = "LightningNodeService" const val ACTION_STOP_SERVICE_AND_APP = "to.bitkit.androidServices.action.STOP_SERVICE_AND_APP" - private const val NODE_STOP_TIMEOUT_MS = 10_000L - private const val FORCE_STOP_TIMEOUT_MS = 3_000L + private const val NODE_STOP_TIMEOUT_MS = 5_000L + private const val FORCE_STOP_TIMEOUT_MS = 2_000L } } From 46724e1fd04d8e256c7dc9a77f70962e0a4cdf99 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 2 Feb 2026 10:02:33 -0300 Subject: [PATCH 4/6] fix: force a stop state on CancellationException, because JNI call will keep running.This prevents the node be stuck on stopping state --- app/src/main/java/to/bitkit/repositories/LightningRepo.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index 5ce6b4694..a36c61c2a 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -396,6 +396,12 @@ class LightningRepo @Inject constructor( lightningService.stop() _lightningState.update { LightningState(nodeLifecycleState = NodeLifecycleState.Stopped) } }.onFailure { + // On cancellation (e.g., timeout), ensure state is recoverable + if (it is CancellationException) { + Logger.warn("Node stop cancelled, forcing Stopped state for recovery", context = TAG) + _lightningState.update { LightningState(nodeLifecycleState = NodeLifecycleState.Stopped) } + return@withLock Result.failure(it) + } Logger.error("Node stop error", it, context = TAG) // On failure, check actual node state and update accordingly // If node is still running, revert to Running state to allow retry From 36fe6d5e901a0d0f6f71d5cb72558daf7e44b03f Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 2 Feb 2026 10:20:22 -0300 Subject: [PATCH 5/6] fix: add defensive recovery for stuck Stopping state --- app/src/main/java/to/bitkit/repositories/LightningRepo.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index a36c61c2a..3d96424c2 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -277,6 +277,14 @@ class LightningRepo @Inject constructor( val result = lifecycleMutex.withLock { initialLifecycleState = _lightningState.value.nodeLifecycleState + + // Recovery: if stuck at Stopping (e.g., previous shutdown interrupted), reset to Stopped + if (initialLifecycleState == NodeLifecycleState.Stopping) { + Logger.warn("Found stuck Stopping state, resetting to Stopped", context = TAG) + _lightningState.update { it.copy(nodeLifecycleState = NodeLifecycleState.Stopped) } + initialLifecycleState = NodeLifecycleState.Stopped + } + if (initialLifecycleState.isRunningOrStarting()) { Logger.info("LDK node start skipped, lifecycle state: $initialLifecycleState", context = TAG) return@withLock Result.success(Unit) From d90e8c03a032b55a65cd920bdc54fdf4ebd6262e Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 2 Feb 2026 10:21:46 -0300 Subject: [PATCH 6/6] chore: log failed stop --- .../to/bitkit/androidServices/LightningNodeService.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt b/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt index ad8ecfe74..a4c3e0a54 100644 --- a/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt +++ b/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt @@ -150,7 +150,11 @@ class LightningNodeService : Service() { runBlocking { withTimeoutOrNull(NODE_STOP_TIMEOUT_MS) { lightningRepo.stop() - } ?: Logger.warn("Node stop timed out during onDestroy", context = TAG) + }.let { result -> + if (result == null || result.isFailure) { + Logger.warn("Node stop timed out or failed during onDestroy", context = TAG) + } + } } serviceScope.cancel() Logger.debug("onDestroy completed", context = TAG) @@ -163,6 +167,10 @@ class LightningNodeService : Service() { runBlocking { withTimeoutOrNull(FORCE_STOP_TIMEOUT_MS) { lightningRepo.stop() + }.let { result -> + if (result == null || result.isFailure) { + Logger.warn("Node stop timed out or failed during onTimeout", context = TAG) + } } } stopSelf()