@@ -9,8 +9,10 @@ import BitcoinDevKit
9
9
import Foundation
10
10
11
11
extension CbfClient {
12
- // Track one monitoring task per client for clean cancellation
12
+ // Track monitoring tasks per client for clean cancellation
13
13
private static var monitoringTasks : [ ObjectIdentifier : Task < Void , Never > ] = [ : ]
14
+ private static var heartbeatTasks : [ ObjectIdentifier : Task < Void , Never > ] = [ : ]
15
+ private static var lastInfoAt : [ ObjectIdentifier : Date ] = [ : ]
14
16
private static let monitoringTasksQueue = DispatchQueue ( label: " cbf.monitoring.tasks " )
15
17
16
18
static func createComponents( wallet: Wallet ) -> ( client: CbfClient , node: CbfNode ) {
@@ -23,8 +25,14 @@ extension CbfClient {
23
25
. build ( wallet: wallet)
24
26
25
27
components. node. run ( )
28
+ #if DEBUG
29
+ print ( " [Kyoto] node started; peers= \( Constants . Networks. Signet. Regular. kyotoPeers. count) " )
30
+ #endif
26
31
27
32
components. client. startBackgroundMonitoring ( )
33
+ #if DEBUG
34
+ print ( " [Kyoto] background monitoring started " )
35
+ #endif
28
36
29
37
return ( client: components. client, node: components. node)
30
38
} catch {
@@ -41,8 +49,12 @@ extension CbfClient {
41
49
if Task . isCancelled { break }
42
50
do {
43
51
let info = try await self . nextInfo ( )
52
+ CbfClient . monitoringTasksQueue. sync { Self . lastInfoAt [ id] = Date ( ) }
44
53
switch info {
45
54
case let . progress( progress) :
55
+ #if DEBUG
56
+ print ( " [Kyoto] progress: \( progress) " )
57
+ #endif
46
58
await MainActor . run {
47
59
NotificationCenter . default. post (
48
60
name: NSNotification . Name ( " KyotoProgressUpdate " ) ,
@@ -51,6 +63,9 @@ extension CbfClient {
51
63
)
52
64
}
53
65
case let . newChainHeight( height) :
66
+ #if DEBUG
67
+ print ( " [Kyoto] newChainHeight: \( height) " )
68
+ #endif
54
69
await MainActor . run {
55
70
NotificationCenter . default. post (
56
71
name: NSNotification . Name ( " KyotoChainHeightUpdate " ) ,
@@ -67,6 +82,9 @@ extension CbfClient {
67
82
}
68
83
}
69
84
case . connectionsMet, . successfulHandshake:
85
+ #if DEBUG
86
+ print ( " [Kyoto] connections established " )
87
+ #endif
70
88
await MainActor . run {
71
89
if !hasEstablishedConnection {
72
90
hasEstablishedConnection = true
@@ -90,6 +108,29 @@ extension CbfClient {
90
108
91
109
Self . monitoringTasksQueue. sync {
92
110
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
93
134
}
94
135
}
95
136
@@ -98,13 +139,18 @@ extension CbfClient {
98
139
Self . monitoringTasksQueue. sync {
99
140
guard let task = Self . monitoringTasks. removeValue ( forKey: id) else { return }
100
141
task. cancel ( )
142
+ if let hb = Self . heartbeatTasks. removeValue ( forKey: id) { hb. cancel ( ) }
143
+ Self . lastInfoAt. removeValue ( forKey: id)
101
144
}
102
145
}
103
146
104
147
static func cancelAllMonitoring( ) {
105
148
Self . monitoringTasksQueue. sync {
106
149
for (_, task) in Self . monitoringTasks { task. cancel ( ) }
150
+ for (_, hb) in Self . heartbeatTasks { hb. cancel ( ) }
107
151
Self . monitoringTasks. removeAll ( )
152
+ Self . heartbeatTasks. removeAll ( )
153
+ Self . lastInfoAt. removeAll ( )
108
154
}
109
155
}
110
156
}
0 commit comments