@@ -65,10 +65,6 @@ class AutoClusterFailoverImpl : public std::enable_shared_from_this<AutoClusterF
6565 client_ = client;
6666 prober_ = prober;
6767
68- // Capture the primary auth and TLS from the client's current config — these are already set
69- // on the client before initialize() is called.
70- // (The design doc says the primary auth is the initial auth of the client configuration.)
71-
7268 executor_ = ExecutorService::create ();
7369 timer_ = executor_->createDeadlineTimer ();
7470 scheduleProbe ();
@@ -97,97 +93,166 @@ class AutoClusterFailoverImpl : public std::enable_shared_from_this<AutoClusterF
9793 timer_->expires_after (checkInterval_);
9894 timer_->async_wait ([self](const ASIO ::error_code& ec) {
9995 if (ec || self->closed_ ) return ;
100- self->probe ();
101- self->scheduleProbe ();
96+ self->startProbeCheck ();
10297 });
10398 }
10499
105- void probe () {
100+ void startProbeCheck () {
106101 std::lock_guard<std::mutex> lock (mutex_);
107- if (!prober_ || !client_ || closed_) return ;
102+ if (!prober_ || !client_ || closed_) {
103+ scheduleProbe ();
104+ return ;
105+ }
108106
109107 if (currentServiceUrl_ == primary_) {
110- // On primary — check if it's down, and if so try secondary
111- probeAndFailover ();
108+ // On primary — probe it to see if it's still up
109+ probeCurrentForFailover ();
112110 } else {
113- // On secondary — check if secondary is still up
114- probeCurrentAndFailover ();
115- // Also check if primary has recovered for switch-back
116- if (currentServiceUrl_ != primary_) {
117- probeAndSwitchBack ();
118- }
111+ // On secondary — probe it to see if it's still up, then check switch-back
112+ probeCurrentOnSecondary ();
119113 }
120114 }
121115
122- void probeAndFailover () {
123- if (prober_->probe (currentServiceUrl_, PROBE_TIMEOUT_MS )) {
124- failedTimestamp_ = -1 ;
116+ // === When on primary: probe current, if down try secondaries ===
117+
118+ void probeCurrentForFailover () {
119+ auto self = shared_from_this ();
120+ std::string url = currentServiceUrl_;
121+ prober_->probeAsync (url, PROBE_TIMEOUT_MS , [self, url](bool success) {
122+ std::lock_guard<std::mutex> lock (self->mutex_ );
123+ if (self->closed_ ) return ;
124+
125+ if (success) {
126+ self->failedTimestamp_ = -1 ;
127+ self->scheduleProbe ();
128+ return ;
129+ }
130+
131+ auto now = currentTimeMs ();
132+ if (self->failedTimestamp_ == -1 ) {
133+ self->failedTimestamp_ = now;
134+ self->scheduleProbe ();
135+ } else if (now - self->failedTimestamp_ >= self->failoverDelay_ .count ()) {
136+ // Delay elapsed — try secondaries
137+ self->trySecondary (0 );
138+ } else {
139+ self->scheduleProbe ();
140+ }
141+ });
142+ }
143+
144+ void trySecondary (size_t index) {
145+ if (index >= secondary_.size ()) {
146+ // No secondaries are available
147+ scheduleProbe ();
125148 return ;
126149 }
127150
128- auto now = currentTimeMs ();
129- if (failedTimestamp_ == -1 ) {
130- failedTimestamp_ = now;
131- } else if (now - failedTimestamp_ >= failoverDelay_.count ()) {
132- for (const auto & target : secondary_) {
133- if (prober_->probe (target, PROBE_TIMEOUT_MS )) {
134- LOG_INFO (" Current service " << currentServiceUrl_ << " has been down for "
135- << (now - failedTimestamp_) << " ms, switching to "
136- << target);
137- performSwitch (target);
138- failedTimestamp_ = -1 ;
151+ auto self = shared_from_this ();
152+ std::string target = secondary_[index];
153+ prober_->probeAsync (target, PROBE_TIMEOUT_MS , [self, target, index](bool success) {
154+ std::lock_guard<std::mutex> lock (self->mutex_ );
155+ if (self->closed_ ) return ;
156+
157+ if (success) {
158+ auto now = currentTimeMs ();
159+ LOG_INFO (" Current service " << self->currentServiceUrl_ << " has been down for "
160+ << (now - self->failedTimestamp_ ) << " ms, switching to "
161+ << target);
162+ self->performSwitch (target);
163+ self->failedTimestamp_ = -1 ;
164+ self->scheduleProbe ();
165+ } else {
166+ auto now = currentTimeMs ();
167+ LOG_WARN (" Current service " << self->currentServiceUrl_ << " has been down for "
168+ << (now - self->failedTimestamp_ )
169+ << " ms. Failed to switch to " << target
170+ << " (not available), trying next." );
171+ self->trySecondary (index + 1 );
172+ }
173+ });
174+ }
175+
176+ // === When on secondary: probe current, then check primary for switch-back ===
177+
178+ void probeCurrentOnSecondary () {
179+ auto self = shared_from_this ();
180+ std::string url = currentServiceUrl_;
181+ prober_->probeAsync (url, PROBE_TIMEOUT_MS , [self, url](bool success) {
182+ std::lock_guard<std::mutex> lock (self->mutex_ );
183+ if (self->closed_ ) return ;
184+
185+ if (success) {
186+ self->failedTimestamp_ = -1 ;
187+ } else {
188+ auto now = currentTimeMs ();
189+ if (self->failedTimestamp_ == -1 ) {
190+ self->failedTimestamp_ = now;
191+ } else if (now - self->failedTimestamp_ >= self->failoverDelay_ .count ()) {
192+ // Secondary is also down — try primary as recovery
193+ self->tryPrimaryRecovery ();
139194 return ;
140- } else {
141- LOG_WARN (" Current service " << currentServiceUrl_ << " has been down for "
142- << (now - failedTimestamp_) << " ms. Failed to switch to "
143- << target << " (not available), trying next." );
144195 }
145196 }
146- }
147- }
148197
149- // When on a secondary, probe it and failover to next secondary if it's also down
150- void probeCurrentAndFailover () {
151- if (prober_->probe (currentServiceUrl_, PROBE_TIMEOUT_MS )) {
152- failedTimestamp_ = -1 ;
153- return ;
154- }
198+ // Check if primary has recovered for switch-back
199+ if (self->currentServiceUrl_ != self->primary_ ) {
200+ self->probePrimaryForSwitchBack ();
201+ } else {
202+ self->scheduleProbe ();
203+ }
204+ });
205+ }
155206
156- auto now = currentTimeMs ();
157- if (failedTimestamp_ == -1 ) {
158- failedTimestamp_ = now;
159- } else if (now - failedTimestamp_ >= failoverDelay_.count ()) {
160- // Try primary first
161- if (prober_->probe (primary_, PROBE_TIMEOUT_MS )) {
162- LOG_INFO (" Secondary " << currentServiceUrl_ << " has been down for "
163- << (now - failedTimestamp_) << " ms, switching back to primary "
164- << primary_);
165- performSwitch (primary_);
166- failedTimestamp_ = -1 ;
167- recoverTimestamp_ = -1 ;
168- return ;
207+ void tryPrimaryRecovery () {
208+ auto self = shared_from_this ();
209+ prober_->probeAsync (primary_, PROBE_TIMEOUT_MS , [self](bool success) {
210+ std::lock_guard<std::mutex> lock (self->mutex_ );
211+ if (self->closed_ ) return ;
212+
213+ if (success) {
214+ auto now = currentTimeMs ();
215+ LOG_INFO (" Secondary " << self->currentServiceUrl_ << " has been down for "
216+ << (now - self->failedTimestamp_ )
217+ << " ms, switching back to primary " << self->primary_ );
218+ self->performSwitch (self->primary_ );
219+ self->failedTimestamp_ = -1 ;
220+ self->recoverTimestamp_ = -1 ;
221+ } else {
222+ LOG_ERROR (" Current service "
223+ << self->currentServiceUrl_ << " has been down for "
224+ << (currentTimeMs () - self->failedTimestamp_ ) << " ms. Primary "
225+ << self->primary_ << " is also not available." );
169226 }
170- LOG_ERROR (" Current service " << currentServiceUrl_ << " has been down for "
171- << (now - failedTimestamp_) << " ms. Primary " << primary_
172- << " is also not available." );
173- }
227+ self->scheduleProbe ();
228+ });
174229 }
175230
176- void probeAndSwitchBack () {
177- if (!prober_-> probe (primary_, PROBE_TIMEOUT_MS )) {
178- recoverTimestamp_ = - 1 ;
179- return ;
180- }
231+ void probePrimaryForSwitchBack () {
232+ auto self = shared_from_this ();
233+ prober_-> probeAsync (primary_, PROBE_TIMEOUT_MS , [self]( bool success) {
234+ std::lock_guard<std::mutex> lock (self-> mutex_ ) ;
235+ if (self-> closed_ ) return ;
181236
182- auto now = currentTimeMs ();
183- if (recoverTimestamp_ == -1 ) {
184- recoverTimestamp_ = now;
185- } else if (now - recoverTimestamp_ >= switchBackDelay_.count ()) {
186- LOG_INFO (" Primary " << primary_ << " has been recovered for " << (now - recoverTimestamp_)
187- << " ms, switching back from secondary " << currentServiceUrl_);
188- performSwitch (primary_);
189- recoverTimestamp_ = -1 ;
190- }
237+ if (!success) {
238+ self->recoverTimestamp_ = -1 ;
239+ self->scheduleProbe ();
240+ return ;
241+ }
242+
243+ auto now = currentTimeMs ();
244+ if (self->recoverTimestamp_ == -1 ) {
245+ self->recoverTimestamp_ = now;
246+ } else if (now - self->recoverTimestamp_ >= self->switchBackDelay_ .count ()) {
247+ LOG_INFO (" Primary " << self->primary_ << " has been recovered for "
248+ << (now - self->recoverTimestamp_ )
249+ << " ms, switching back from secondary "
250+ << self->currentServiceUrl_ );
251+ self->performSwitch (self->primary_ );
252+ self->recoverTimestamp_ = -1 ;
253+ }
254+ self->scheduleProbe ();
255+ });
191256 }
192257
193258 void performSwitch (const std::string& target) {
0 commit comments