1717from typing import TYPE_CHECKING , Any , Callable , Dict , Optional , Set
1818
1919from aws_advanced_python_wrapper .pep249_methods import DbApiMethod
20- from aws_advanced_python_wrapper .utils .utils import LogUtils
2120
2221if TYPE_CHECKING :
2322 from aws_advanced_python_wrapper .host_list_provider import HostListProviderService
4140 WrapperProperties )
4241from aws_advanced_python_wrapper .utils .rds_url_type import RdsUrlType
4342from aws_advanced_python_wrapper .utils .rds_utils import RdsUtils
43+ from aws_advanced_python_wrapper .utils .retry_util import RetryUtil
4444from aws_advanced_python_wrapper .utils .telemetry .telemetry import \
4545 TelemetryTraceLevel
4646
@@ -326,55 +326,32 @@ def _failover_writer(self) -> None:
326326 "failover to writer host" , TelemetryTraceLevel .NESTED )
327327
328328 failover_start_time = time .time ()
329+ failover_end_time = failover_start_time + self ._failover_timeout_sec
330+ retry_util = RetryUtil ()
331+ result = None
329332 try :
330333 if not self ._plugin_service .force_monitoring_refresh_host_list (True , self ._failover_timeout_sec ):
331334 raise FailoverFailedError (Messages .get ("FailoverPlugin.UnableToRefreshHostList" ))
332335
333- updated_hosts = self . _plugin_service . all_hosts
334- writer_candidate = next (( host for host in updated_hosts if host . role == HostRole . WRITER ), None )
336+ result = retry_util . get_writer_connection (
337+ self . _plugin_service , self . _properties , self , True , failover_end_time )
335338
336- if writer_candidate is None :
337- raise FailoverFailedError (Messages .get_formatted (
338- "FailoverPlugin.NoWriterHostInTopology" ,
339- LogUtils .log_topology (updated_hosts )))
340-
341- logger .info ("FailoverPlugin.FoundWriterCandidate" , writer_candidate )
342-
343- allowed_hosts = self ._plugin_service .hosts
344- if not any (host .host == writer_candidate .host and host .port == writer_candidate .port
345- for host in allowed_hosts ):
346- raise FailoverFailedError (
347- Messages .get_formatted (
348- "FailoverPlugin.NewWriterNotAllowed" ,
349- "<null>" if writer_candidate is None else writer_candidate .host ,
350- LogUtils .log_topology (allowed_hosts )))
351-
352- try :
353- writer_candidate_conn = self ._plugin_service .connect (writer_candidate , self ._properties , self )
354- except Exception as e :
355- raise FailoverFailedError (Messages .get_formatted (
356- "FailoverPlugin.ExceptionConnectingToWriter" , e ))
357-
358- role = self ._plugin_service .get_host_role (writer_candidate_conn )
359- if role != HostRole .WRITER :
360- try :
361- self ._plugin_service .driver_dialect .execute (
362- DbApiMethod .CONNECTION_CLOSE .method_name , lambda : writer_candidate_conn .close ())
363- except Exception :
364- pass
365- raise FailoverFailedError (Messages .get_formatted (
366- "FailoverPlugin.WriterFailoverConnectedToReader" ,
367- writer_candidate .host ))
368-
369- self ._plugin_service .set_current_connection (writer_candidate_conn , writer_candidate )
370- logger .info ("FailoverPlugin.EstablishedConnection" , self ._plugin_service .current_host_info )
371- self ._throw_failover_success_exception ()
339+ if result .connection is not None and result .host_info is not None :
340+ self ._plugin_service .set_current_connection (result .connection , result .host_info )
341+ result = None # Prevents closing the returned connection in the finally block.
342+ logger .info ("FailoverPlugin.EstablishedConnection" , self ._plugin_service .current_host_info )
343+ self ._throw_failover_success_exception ()
372344
373345 except FailoverSuccessError as ex :
374346 if telemetry_context :
375347 telemetry_context .set_success (True )
376348 telemetry_context .set_exception (ex )
377349 raise ex
350+ except TimeoutError as ex :
351+ if telemetry_context :
352+ telemetry_context .set_success (False )
353+ telemetry_context .set_exception (ex )
354+ raise FailoverFailedError (str (ex ))
378355 except Exception as ex :
379356 if telemetry_context :
380357 telemetry_context .set_success (False )
@@ -383,6 +360,8 @@ def _failover_writer(self) -> None:
383360 finally :
384361 elapsed_time = (time .time () - failover_start_time ) * 1000
385362 logger .info ("FailoverPlugin.WriterFailoverTime" , elapsed_time )
363+ if result is not None and result .connection is not self ._plugin_service .current_connection :
364+ RetryUtil .close_connection (self ._plugin_service , result .connection )
386365 if telemetry_context :
387366 telemetry_context .close_context ()
388367 if self ._telemetry_failover_additional_top_trace :
@@ -423,9 +402,12 @@ def _should_exception_trigger_connection_switch(self, exception: Exception) -> b
423402
424403 # For STRICT_WRITER failover mode when connection exception indicate that the connection's in read-only mode,
425404 # initiate a failover by returning true.
426- return self ._failover_mode == FailoverMode . STRICT_WRITER and \
405+ return self ._is_strict_writer_failover_mode () and \
427406 self ._plugin_service .is_read_only_connection_exception (exception )
428407
408+ def _is_strict_writer_failover_mode (self ) -> bool :
409+ return self ._failover_mode == FailoverMode .STRICT_WRITER
410+
429411 def _can_direct_execute (self , method_name : str ) -> bool :
430412 return method_name == DbApiMethod .CONNECTION_CLOSE .method_name or \
431413 method_name == DbApiMethod .CONNECTION_IS_CLOSED .method_name or \
0 commit comments