@@ -9,8 +9,10 @@ import BitcoinDevKit
99import Foundation
1010
1111extension CbfClient {
12- // Track one monitoring task per client for clean cancellation
12+ // Track monitoring tasks per client for clean cancellation
1313 private static var monitoringTasks : [ ObjectIdentifier : Task < Void , Never > ] = [ : ]
14+ private static var heartbeatTasks : [ ObjectIdentifier : Task < Void , Never > ] = [ : ]
15+ private static var lastInfoAt : [ ObjectIdentifier : Date ] = [ : ]
1416 private static let monitoringTasksQueue = DispatchQueue ( label: " cbf.monitoring.tasks " )
1517
1618 static func createComponents( wallet: Wallet ) -> ( client: CbfClient , node: CbfNode ) {
@@ -23,8 +25,14 @@ extension CbfClient {
2325 . build ( wallet: wallet)
2426
2527 components. node. run ( )
28+ #if DEBUG
29+ print ( " [Kyoto] node started; peers= \( Constants . Networks. Signet. Regular. kyotoPeers. count) " )
30+ #endif
2631
2732 components. client. startBackgroundMonitoring ( )
33+ #if DEBUG
34+ print ( " [Kyoto] background monitoring started " )
35+ #endif
2836
2937 return ( client: components. client, node: components. node)
3038 } catch {
@@ -41,8 +49,12 @@ extension CbfClient {
4149 if Task . isCancelled { break }
4250 do {
4351 let info = try await self . nextInfo ( )
52+ CbfClient . monitoringTasksQueue. sync { Self . lastInfoAt [ id] = Date ( ) }
4453 switch info {
4554 case let . progress( progress) :
55+ #if DEBUG
56+ print ( " [Kyoto] progress: \( progress) " )
57+ #endif
4658 await MainActor . run {
4759 NotificationCenter . default. post (
4860 name: NSNotification . Name ( " KyotoProgressUpdate " ) ,
@@ -51,6 +63,9 @@ extension CbfClient {
5163 )
5264 }
5365 case let . newChainHeight( height) :
66+ #if DEBUG
67+ print ( " [Kyoto] newChainHeight: \( height) " )
68+ #endif
5469 await MainActor . run {
5570 NotificationCenter . default. post (
5671 name: NSNotification . Name ( " KyotoChainHeightUpdate " ) ,
@@ -67,6 +82,9 @@ extension CbfClient {
6782 }
6883 }
6984 case . connectionsMet, . successfulHandshake:
85+ #if DEBUG
86+ print ( " [Kyoto] connections established " )
87+ #endif
7088 await MainActor . run {
7189 if !hasEstablishedConnection {
7290 hasEstablishedConnection = true
@@ -90,6 +108,29 @@ extension CbfClient {
90108
91109 Self . monitoringTasksQueue. sync {
92110 Self . monitoringTasks [ id] = task
111+ Self . lastInfoAt [ id] = Date ( )
112+ }
113+
114+ // Heartbeat task to signal idleness while awaiting Info events
115+ let heartbeat = Task {
116+ while true {
117+ if Task . isCancelled { break }
118+ try ? await Task . sleep ( nanoseconds: 5_000_000_000 )
119+ if Task . isCancelled { break }
120+ var idleFor : TimeInterval = 0
121+ CbfClient . monitoringTasksQueue. sync {
122+ if let last = Self . lastInfoAt [ id] { idleFor = Date ( ) . timeIntervalSince ( last) }
123+ }
124+ #if DEBUG
125+ if idleFor >= 5 {
126+ print ( " [Kyoto] idle: waiting for info… \( Int ( idleFor) ) s " )
127+ }
128+ #endif
129+ }
130+ }
131+
132+ Self . monitoringTasksQueue. sync {
133+ Self . heartbeatTasks [ id] = heartbeat
93134 }
94135 }
95136
@@ -98,13 +139,18 @@ extension CbfClient {
98139 Self . monitoringTasksQueue. sync {
99140 guard let task = Self . monitoringTasks. removeValue ( forKey: id) else { return }
100141 task. cancel ( )
142+ if let hb = Self . heartbeatTasks. removeValue ( forKey: id) { hb. cancel ( ) }
143+ Self . lastInfoAt. removeValue ( forKey: id)
101144 }
102145 }
103146
104147 static func cancelAllMonitoring( ) {
105148 Self . monitoringTasksQueue. sync {
106149 for (_, task) in Self . monitoringTasks { task. cancel ( ) }
150+ for (_, hb) in Self . heartbeatTasks { hb. cancel ( ) }
107151 Self . monitoringTasks. removeAll ( )
152+ Self . heartbeatTasks. removeAll ( )
153+ Self . lastInfoAt. removeAll ( )
108154 }
109155 }
110156}
0 commit comments