Skip to content

Commit 894867e

Browse files
authored
Merge pull request #726 from synonymdev/fix/handle-node-connection-errors-review
fix: graceful null handling and idempotent stop
2 parents ecba943 + 65230bc commit 894867e

3 files changed

Lines changed: 49 additions & 27 deletions

File tree

app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,8 @@ class LightningNodeService : Service() {
146146
override fun onDestroy() {
147147
Logger.debug("onDestroy", context = TAG)
148148
serviceScope.launch {
149-
lightningRepo.stop().onSuccess {
150-
serviceScope.cancel()
151-
}
149+
lightningRepo.stop()
150+
serviceScope.cancel()
152151
}
153152
super.onDestroy()
154153
}

app/src/main/java/to/bitkit/repositories/LightningRepo.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,10 @@ class LightningRepo @Inject constructor(
296296
if (getStatus()?.isRunning == true) {
297297
Logger.info("LDK node already running", context = TAG)
298298
_lightningState.update { it.copy(nodeLifecycleState = NodeLifecycleState.Running) }
299-
lightningService.listenForEvents(::onEvent)
299+
lightningService.startEventListener(::onEvent).onFailure {
300+
Logger.warn("Failed to start event listener", it, context = TAG)
301+
return@withContext Result.failure(it)
302+
}
300303
return@withContext Result.success(Unit)
301304
}
302305

@@ -896,11 +899,13 @@ class LightningRepo @Inject constructor(
896899
if (_lightningState.value.nodeLifecycleState.isRunning()) lightningService.balances else null
897900

898901
suspend fun getBalancesAsync(): Result<BalanceDetails> = executeWhenNodeRunning("getBalancesAsync") {
899-
Result.success(checkNotNull(lightningService.balances))
902+
lightningService.balances?.let { Result.success(it) }
903+
?: Result.failure(AppError("Balances not available"))
900904
}
901905

902906
suspend fun getChannelsAsync(): Result<List<ChannelDetails>> = executeWhenNodeRunning("getChannelsAsync") {
903-
Result.success(checkNotNull(lightningService.channels))
907+
lightningService.channels?.let { Result.success(it) }
908+
?: Result.failure(AppError("Channels not available"))
904909
}
905910

906911
fun getStatus(): NodeStatus? =

app/src/main/java/to/bitkit/services/LightningService.kt

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package to.bitkit.services
33
import kotlinx.coroutines.CoroutineDispatcher
44
import kotlinx.coroutines.Job
55
import kotlinx.coroutines.cancelAndJoin
6+
import kotlinx.coroutines.ensureActive
67
import kotlinx.coroutines.flow.MutableSharedFlow
78
import kotlinx.coroutines.flow.SharedFlow
89
import kotlinx.coroutines.flow.asSharedFlow
@@ -55,6 +56,7 @@ import to.bitkit.utils.jsonLogOf
5556
import java.io.File
5657
import javax.inject.Inject
5758
import javax.inject.Singleton
59+
import kotlin.coroutines.cancellation.CancellationException
5860
import kotlin.io.path.Path
5961
import kotlin.time.Duration
6062

@@ -219,12 +221,14 @@ class LightningService @Inject constructor(
219221
runCatching {
220222
Logger.debug("LDK event listener started", context = TAG)
221223
if (timeout != null) {
222-
withTimeout(timeout) { listenForEvents(eventHandler) }
224+
withTimeout(timeout) { listenForEvents(node, eventHandler) }
223225
} else {
224-
listenForEvents(eventHandler)
226+
listenForEvents(node, eventHandler)
225227
}
226228
}.onFailure {
227-
Logger.error("LDK event listener error", it, context = TAG)
229+
if (it !is CancellationException) {
230+
Logger.error("LDK event listener error", it, context = TAG)
231+
}
228232
}
229233
}
230234
}
@@ -236,17 +240,17 @@ class LightningService @Inject constructor(
236240
shouldListenForEvents = false
237241
listenerJob?.cancelAndJoin()
238242
listenerJob = null
239-
val node = this.node ?: throw ServiceError.NodeNotStarted()
243+
244+
val node = this.node ?: run {
245+
Logger.debug("Node already stopped", context = TAG)
246+
return
247+
}
240248

241249
Logger.debug("Stopping node…", context = TAG)
242250
ServiceQueue.LDK.background {
243-
try {
244-
node.stop()
245-
this@LightningService.node = null
246-
} catch (_: NodeException.NotRunning) {
247-
// Node is not running, clear the reference
248-
this@LightningService.node = null
249-
}
251+
runCatching { node.stop() }
252+
.onFailure { if (it !is NodeException.NotRunning) throw it }
253+
this@LightningService.node = null
250254
}
251255
Logger.info("Node stopped", context = TAG)
252256
}
@@ -723,20 +727,34 @@ class LightningService @Inject constructor(
723727
// region events
724728
private var shouldListenForEvents = true
725729

726-
suspend fun listenForEvents(onEvent: NodeEventHandler? = null) = withContext(bgDispatcher) {
730+
fun startEventListener(onEvent: NodeEventHandler? = null): Result<Unit> = runCatching {
731+
val node = this.node ?: throw ServiceError.NodeNotSetup()
732+
shouldListenForEvents = true
733+
listenerJob = launch {
734+
runCatching {
735+
Logger.debug("LDK event listener started", context = TAG)
736+
listenForEvents(node, onEvent)
737+
}.onFailure {
738+
if (it !is CancellationException) {
739+
Logger.error("LDK event listener error", it, context = TAG)
740+
}
741+
}
742+
}
743+
}
744+
745+
private suspend fun listenForEvents(node: Node, onEvent: NodeEventHandler? = null) = withContext(bgDispatcher) {
727746
while (shouldListenForEvents) {
728-
val node = this@LightningService.node ?: let {
729-
Logger.error(ServiceError.NodeNotStarted().message.orEmpty(), context = TAG)
747+
ensureActive()
748+
749+
val event = runCatching { node.nextEventAsync() }.getOrElse {
750+
Logger.warn("Event listener stopping: node stopped", it, context = TAG)
730751
return@withContext
731752
}
732-
val event = node.nextEventAsync()
753+
733754
Logger.debug("LDK event fired: ${jsonLogOf(event)}", context = TAG)
734-
try {
735-
node.eventHandled()
736-
Logger.verbose("LDK eventHandled: '$event'", context = TAG)
737-
} catch (e: NodeException) {
738-
Logger.verbose("LDK eventHandled error: '$event'", LdkError(e), context = TAG)
739-
}
755+
runCatching { node.eventHandled() }
756+
.onSuccess { Logger.verbose("LDK eventHandled: '$event'", context = TAG) }
757+
.onFailure { Logger.verbose("LDK eventHandled error: '$event'", it, context = TAG) }
740758
onEvent?.invoke(event)
741759
}
742760
}

0 commit comments

Comments
 (0)