diff --git a/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt b/app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt index 084093db6..a4c3e0a54 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,34 @@ 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() + }.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) 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() + }.let { result -> + if (result == null || result.isFailure) { + Logger.warn("Node stop timed out or failed during onTimeout", context = TAG) + } + } } + stopSelf() super.onTimeout(startId, fgsType) } @@ -168,5 +183,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 = 5_000L + private const val FORCE_STOP_TIMEOUT_MS = 2_000L } } diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index 5ce6b4694..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) @@ -396,6 +404,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 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() }