Line data Source code
1 : #include "NetworkStateMonitor.h"
2 : #include "../utilities/FangLogging.h"
3 : #include <QVariant>
4 :
5 : // Circuit breaker config.
6 : static const int DEFAULT_FAILURE_THRESHOLD = 5; // # of failures to trip circuit
7 : static const int DEFAULT_OPEN_DURATION = 30000;
8 : static const int DEFAULT_WINDOW_SIZE = 60000;
9 : static const int DEFAULT_HALF_OPEN_REQUESTS = 3; // # of successful requests to close breaker
10 :
11 276 : NetworkStateMonitor& NetworkStateMonitor::instance()
12 : {
13 276 : static NetworkStateMonitor instance;
14 276 : return instance;
15 : }
16 :
17 8 : NetworkStateMonitor::NetworkStateMonitor()
18 : : QObject(nullptr)
19 8 : , isCurrentlyOnline(true)
20 8 : , currentCircuitState(Closed)
21 8 : , successCountMetric(0)
22 8 : , failureCountMetric(0)
23 8 : , halfOpenSuccessesMetric(0)
24 8 : , failureThreshold(DEFAULT_FAILURE_THRESHOLD)
25 8 : , openDuration(DEFAULT_OPEN_DURATION)
26 8 : , windowSize(DEFAULT_WINDOW_SIZE)
27 8 : , halfOpenRequests(DEFAULT_HALF_OPEN_REQUESTS)
28 : {
29 : // Monitor system network state using Qt 6 QNetworkInformation
30 : // Load the default backend (platform-specific)
31 8 : if (!QNetworkInformation::loadDefaultBackend()) {
32 0 : qCWarning(logNetwork) << "Failed to load network information backend, assuming online";
33 0 : isCurrentlyOnline = true;
34 : } else {
35 8 : QNetworkInformation* netInfo = QNetworkInformation::instance();
36 8 : QNetworkInformation::Reachability reachability = netInfo->reachability();
37 8 : isCurrentlyOnline = (reachability == QNetworkInformation::Reachability::Online);
38 :
39 8 : connect(netInfo, &QNetworkInformation::reachabilityChanged,
40 16 : this, &NetworkStateMonitor::onReachabilityChanged);
41 : }
42 :
43 : // Circuit breaker timer.
44 8 : circuitTimer.setSingleShot(true);
45 8 : connect(&circuitTimer, &QTimer::timeout,
46 8 : this, &NetworkStateMonitor::onCircuitTimerTimeout);
47 :
48 : // Metrics window timer
49 8 : windowTimer.setInterval(windowSize);
50 8 : connect(&windowTimer, &QTimer::timeout,
51 8 : this, &NetworkStateMonitor::onWindowTimerTimeout);
52 8 : windowTimer.start();
53 8 : windowElapsed.start();
54 :
55 16 : qCInfo(logNetwork) << "Network state monitor init, online: " << isCurrentlyOnline;
56 8 : }
57 :
58 8 : NetworkStateMonitor::~NetworkStateMonitor()
59 : {
60 8 : }
61 :
62 3 : bool NetworkStateMonitor::shouldAllowRequest() const
63 : {
64 : // Always block when already offline.
65 3 : if (!isCurrentlyOnline) {
66 0 : return false;
67 : }
68 :
69 : // Check/re-check circuit breaker.
70 3 : switch (currentCircuitState) {
71 2 : case Closed:
72 : // Normal operation
73 2 : return true;
74 :
75 1 : case Open:
76 : // Block all when breaker is open.
77 1 : return false;
78 :
79 0 : case HalfOpen:
80 : // Hacky way to check if we want to stay open or close again.
81 0 : return true;
82 : }
83 :
84 0 : return true;
85 : }
86 :
87 25 : void NetworkStateMonitor::recordSuccess()
88 : {
89 25 : successCountMetric++;
90 :
91 25 : switch (currentCircuitState) {
92 13 : case Closed:
93 13 : break;
94 :
95 12 : case HalfOpen:
96 12 : halfOpenSuccessesMetric++;
97 24 : qCDebug(logNetwork) << "Half open success count:" << halfOpenSuccessesMetric
98 12 : << "/ threshold:" << halfOpenRequests;
99 :
100 : // Close the circuit breaker if we seem good to go.
101 12 : if (halfOpenSuccessesMetric >= halfOpenRequests) {
102 4 : closeCircuit();
103 : }
104 12 : break;
105 :
106 0 : case Open:
107 : // Edge case.
108 0 : qCWarning(logNetwork) << "Success while circuit open: closing circuit";
109 0 : closeCircuit();
110 0 : break;
111 : }
112 :
113 : // Update the breaker state.
114 25 : updateCircuitState();
115 25 : }
116 :
117 147 : void NetworkStateMonitor::recordFailure()
118 : {
119 147 : failureCountMetric++;
120 :
121 294 : qCDebug(logNetwork) << "Network failure recorded. Total in window:"
122 147 : << failureCountMetric << "/ threshold:" << failureThreshold;
123 :
124 147 : switch (currentCircuitState) {
125 81 : case Closed:
126 : // Check if we should trip the breaker.
127 81 : if (failureCountMetric >= failureThreshold) {
128 26 : tripCircuit();
129 : }
130 81 : break;
131 :
132 2 : case HalfOpen:
133 : // Trip breaker.
134 4 : qCInfo(logNetwork) << "Failure during half-open test: Reopening circuit breaker";
135 2 : tripCircuit();
136 2 : break;
137 :
138 64 : case Open:
139 : // Already open, add more to timer.
140 64 : circuitTimer.start(openDuration);
141 64 : break;
142 : }
143 147 : }
144 :
145 7 : float NetworkStateMonitor::failureRate() const
146 : {
147 7 : int total = successCountMetric + failureCountMetric;
148 7 : if (total == 0) {
149 2 : return 0.0f;
150 : }
151 :
152 5 : return static_cast<float>(failureCountMetric) / static_cast<float>(total);
153 : }
154 :
155 22 : void NetworkStateMonitor::resetCircuit()
156 : {
157 44 : qCInfo(logNetwork) << "Circuit breaker: Manual reset";
158 22 : closeCircuit();
159 22 : }
160 :
161 4 : void NetworkStateMonitor::configure(int failureThreshold, int openDuration, int windowSize)
162 : {
163 4 : this->failureThreshold = failureThreshold;
164 4 : this->openDuration = openDuration;
165 4 : this->windowSize = windowSize;
166 :
167 : // Restart timer with new config.
168 4 : windowTimer.setInterval(windowSize);
169 :
170 8 : qCInfo(logNetwork) << "Circuit breaker configured:"
171 4 : << "threshold=" << failureThreshold
172 4 : << "openDuration=" << openDuration
173 4 : << "windowSize=" << windowSize;
174 4 : }
175 :
176 0 : void NetworkStateMonitor::onReachabilityChanged(QNetworkInformation::Reachability reachability)
177 : {
178 : // Convert to our boolean state(s)
179 0 : bool isOnline = (reachability == QNetworkInformation::Reachability::Online);
180 :
181 0 : if (isCurrentlyOnline == isOnline) {
182 0 : return; // No change
183 : }
184 :
185 0 : isCurrentlyOnline = isOnline;
186 :
187 0 : if (isOnline) {
188 0 : qCInfo(logNetwork) << "Network came online, reachability: "
189 0 : << QVariant::fromValue(reachability).toString();
190 0 : emit networkAvailable();
191 :
192 : // Enter half-open state as we may be back online!
193 0 : if (currentCircuitState == Open) {
194 0 : currentCircuitState = HalfOpen;
195 0 : halfOpenSuccessesMetric = 0;
196 0 : emit circuitHalfOpen();
197 : }
198 : } else {
199 0 : qCWarning(logNetwork) << "Network went offline, reachability: "
200 0 : << QVariant::fromValue(reachability).toString();
201 0 : emit networkUnavailable();
202 :
203 : // Error, trip breaker open.
204 0 : if (currentCircuitState != Open) {
205 0 : tripCircuit();
206 : }
207 : }
208 : }
209 :
210 6 : void NetworkStateMonitor::onCircuitTimerTimeout()
211 : {
212 6 : if (currentCircuitState == Open) {
213 12 : qCInfo(logNetwork) << "Circuit breaker timeout: switch to half open";
214 6 : currentCircuitState = HalfOpen;
215 6 : halfOpenSuccessesMetric = 0;
216 6 : emit circuitHalfOpen();
217 : }
218 6 : }
219 :
220 2 : void NetworkStateMonitor::onWindowTimerTimeout()
221 : {
222 : // Reset metrics.
223 4 : qCDebug(logNetwork) << "Metrics window reset. Previous window:"
224 2 : << "successes: " << successCountMetric
225 2 : << "failures: " << failureCountMetric
226 2 : << "rate: " << (failureRate() * 100.0f) << "%";
227 :
228 2 : successCountMetric = 0;
229 2 : failureCountMetric = 0;
230 2 : windowElapsed.restart();
231 :
232 : // Check state of breaker with our new metrics.
233 2 : updateCircuitState();
234 2 : }
235 :
236 27 : void NetworkStateMonitor::updateCircuitState()
237 : {
238 27 : if (currentCircuitState == Closed) {
239 : // We gonna trip...?
240 19 : if (failureCountMetric >= failureThreshold) {
241 0 : tripCircuit();
242 : }
243 : }
244 27 : }
245 :
246 28 : void NetworkStateMonitor::tripCircuit()
247 : {
248 28 : if (currentCircuitState == Open) {
249 : // Circuit already open: extend timer.
250 0 : circuitTimer.start(openDuration);
251 0 : return;
252 : }
253 :
254 56 : qCWarning(logNetwork) << "Circuit breaker OPENED due to failures:"
255 28 : << failureCountMetric << "failures in" << windowElapsed.elapsed() << " ms";
256 :
257 28 : currentCircuitState = Open;
258 28 : circuitTimer.start(openDuration);
259 :
260 28 : emit circuitOpened();
261 : }
262 :
263 26 : void NetworkStateMonitor::closeCircuit()
264 : {
265 26 : if (currentCircuitState == Closed) {
266 : // Nothing to do!
267 0 : return;
268 : }
269 :
270 52 : qCInfo(logNetwork) << "Circuit breaker CLOSED: back to normal!";
271 :
272 26 : currentCircuitState = Closed;
273 26 : halfOpenSuccessesMetric = 0;
274 26 : circuitTimer.stop();
275 :
276 : // Reset metrics.
277 26 : successCountMetric = 0;
278 26 : failureCountMetric = 0;
279 :
280 26 : emit circuitClosed();
281 : }
|