Kraken’s nightly architectural scan flagged that DefaultNodeObserver handles all errors via logging only, potentially leaving node processing silently broken.
Three issues in DefaultNodeObserver:
CancellationException swallowed: The FlowCollector caught Exception without re-throwing CancellationException. In Kotlin coroutines, CancellationException is the cooperative-cancellation signal — swallowing it inside a collector means the collection loop continues even when the coroutine scope is being cancelled, defeating structured concurrency.
Typo in log message: "multuiple" in the multiple-observers warning.
Multiple-observers path permanently orphaned nodes: When subscriptionCount > 1 at observer setup, the inner job logged an error and exited without calling node.collect(). The node ID was added to jobs (preventing future observe() calls from re-registering), but the job never collected — so the node was permanently unobserved. This could be triggered in production by rapid UI navigation causing transient multi-subscription before the observer job started.
catch (e: CancellationException) { throw e } before the catch (e: Exception) clause in the FlowCollector, so cooperative cancellation propagates correctly.node.collect(collector) unconditionally. The jobs map guard already prevents DefaultNodeObserver from double-subscribing; extra subscribers from the UI side are legitimate and should not block observation.try-catch (e: Exception) inside a coroutine context, always check: “does this catch block need to rethrow CancellationException?” If the block logs-and-continues rather than re-throwing, add an explicit catch (e: CancellationException) { throw e } above it.subscriptionCount > 1) leads to an early exit that still writes to a map/set used as a “seen” registry, verify the guard actually reflects a situation where skipping is safe permanently — not just transient.DefaultNodeObserverDispatchTest).