@@ -3531,6 +3531,7 @@ def __init__(self, cluster, timeout,
35313531 # shutdown) since implementing __del__ disables the cycle detector
35323532 self ._cluster = weakref .proxy (cluster )
35333533 self ._connection = None
3534+ self ._last_connection_endpoint = None
35343535 self ._timeout = timeout
35353536
35363537 self ._schema_event_refresh_window = schema_event_refresh_window
@@ -3564,6 +3565,7 @@ def _set_new_connection(self, conn):
35643565 with self ._lock :
35653566 old = self ._connection
35663567 self ._connection = conn
3568+ self ._last_connection_endpoint = conn .endpoint
35673569
35683570 if old :
35693571 log .debug ("[control connection] Closing old connection %r, replacing with %r" , old , conn )
@@ -4094,33 +4096,28 @@ def wait_for_schema_agreement(self, connection=None, preloaded_results=None, wai
40944096
40954097 if not connection :
40964098 connection = self ._connection
4099+ local_address = self ._schema_agreement_endpoint (connection )
40974100
40984101 if preloaded_results :
40994102 log .debug ("[control connection] Attempting to use preloaded results for schema agreement" )
41004103
41014104 peers_result = preloaded_results [0 ]
41024105 local_result = preloaded_results [1 ]
4103- schema_mismatches = self ._get_schema_mismatches (peers_result , local_result , connection . endpoint )
4106+ schema_mismatches = self ._get_schema_mismatches (peers_result , local_result , local_address )
41044107 if schema_mismatches is None :
41054108 return True
41064109
41074110 log .debug ("[control connection] Waiting for schema agreement" )
41084111 start = self ._time .time ()
41094112 elapsed = 0
4110- cl = ConsistencyLevel .ONE
41114113 schema_mismatches = None
4112- select_peers_query = self ._get_peers_query (self .PeersQueryType .PEERS_SCHEMA , connection )
41134114
41144115 while elapsed < total_timeout :
4115- peers_query = QueryMessage (query = maybe_add_timeout_to_query (select_peers_query , self ._metadata_request_timeout ),
4116- consistency_level = cl )
4117- local_query = QueryMessage (query = maybe_add_timeout_to_query (self ._SELECT_SCHEMA_LOCAL , self ._metadata_request_timeout ),
4118- consistency_level = cl )
41194116 try :
41204117 remaining = total_timeout - elapsed
41214118 timeout = min (self ._timeout , remaining ) if self ._timeout is not None else remaining
4122- peers_result , local_result = connection . wait_for_responses (
4123- peers_query , local_query , timeout = timeout )
4119+ peers_result , local_result , local_address = self . _get_schema_agreement_results (
4120+ connection , timeout )
41244121 except OperationTimedOut as timeout :
41254122 log .debug ("[control connection] Timed out waiting for "
41264123 "response during schema agreement check: %s" , timeout )
@@ -4131,9 +4128,13 @@ def wait_for_schema_agreement(self, connection=None, preloaded_results=None, wai
41314128 log .debug ("[control connection] Aborting wait for schema match due to shutdown" )
41324129 return None
41334130 else :
4134- raise
4131+ log .debug ("[control connection] Connection lost during schema agreement check" )
4132+ return False
4133+ except (ConnectionBusy , ConnectionException , NoConnectionsAvailable ) as exc :
4134+ log .debug ("[control connection] Unable to check schema agreement: %s" , exc )
4135+ return False
41354136
4136- schema_mismatches = self ._get_schema_mismatches (peers_result , local_result , connection . endpoint )
4137+ schema_mismatches = self ._get_schema_mismatches (peers_result , local_result , local_address )
41374138 if schema_mismatches is None :
41384139 return True
41394140
@@ -4142,9 +4143,150 @@ def wait_for_schema_agreement(self, connection=None, preloaded_results=None, wai
41424143 elapsed = self ._time .time () - start
41434144
41444145 log .warning ("Node %s is reporting a schema disagreement: %s" ,
4145- connection . endpoint , schema_mismatches )
4146+ local_address , schema_mismatches )
41464147 return False
41474148
4149+ def _get_schema_agreement_results (self , connection , timeout ):
4150+ if self ._is_connection_unusable (connection ):
4151+ return self ._get_schema_agreement_results_from_session (connection , timeout )
4152+
4153+ try :
4154+ return self ._get_schema_agreement_results_from_connection (connection , timeout )
4155+ except OperationTimedOut :
4156+ raise
4157+ except (ConnectionBusy , ConnectionException ):
4158+ if self ._is_shutdown :
4159+ raise
4160+ self .return_connection (connection )
4161+ log .debug ("[control connection] Falling back to session connection for schema agreement" )
4162+ return self ._get_schema_agreement_results_from_session (connection , timeout )
4163+
4164+ def _get_schema_agreement_results_from_connection (self , connection , timeout ):
4165+ peers_query , local_query = self ._schema_agreement_queries (connection )
4166+ peers_result , local_result = connection .wait_for_responses (peers_query , local_query , timeout = timeout )
4167+ return peers_result , local_result , connection .endpoint
4168+
4169+ def _get_schema_agreement_results_from_session (self , connection , timeout ):
4170+ endpoint = self ._schema_agreement_endpoint (connection )
4171+ deadline = None if timeout is None else time .time () + timeout
4172+
4173+ pool , borrowed_connection , request_id = self ._borrow_schema_agreement_connection (endpoint , timeout )
4174+ peers_query , _ = self ._schema_agreement_queries (borrowed_connection )
4175+ local_address = borrowed_connection .endpoint
4176+ peers_result = self ._wait_for_borrowed_schema_response (
4177+ pool , borrowed_connection , request_id , peers_query , self ._remaining_timeout (deadline ))
4178+
4179+ pool , borrowed_connection , request_id = self ._borrow_schema_agreement_connection (
4180+ endpoint , self ._remaining_timeout (deadline ))
4181+ _ , local_query = self ._schema_agreement_queries (borrowed_connection )
4182+ local_result = self ._wait_for_borrowed_schema_response (
4183+ pool , borrowed_connection , request_id , local_query , self ._remaining_timeout (deadline ))
4184+
4185+ return peers_result , local_result , local_address
4186+
4187+ def _schema_agreement_queries (self , connection ):
4188+ cl = ConsistencyLevel .ONE
4189+ select_peers_query = self ._get_peers_query (self .PeersQueryType .PEERS_SCHEMA , connection )
4190+ peers_query = QueryMessage (query = maybe_add_timeout_to_query (select_peers_query , self ._metadata_request_timeout ),
4191+ consistency_level = cl )
4192+ local_query = QueryMessage (query = maybe_add_timeout_to_query (self ._SELECT_SCHEMA_LOCAL , self ._metadata_request_timeout ),
4193+ consistency_level = cl )
4194+ return peers_query , local_query
4195+
4196+ def _remaining_timeout (self , deadline ):
4197+ if deadline is None :
4198+ return None
4199+ return max (0 , deadline - time .time ())
4200+
4201+ def _borrow_schema_agreement_connection (self , endpoint , timeout ):
4202+ last_error = None
4203+ for pool in self ._schema_agreement_pools (endpoint ):
4204+ try :
4205+ return (pool ,) + pool .borrow_connection (timeout = timeout )
4206+ except (NoConnectionsAvailable , ConnectionBusy , ConnectionException ) as exc :
4207+ last_error = exc
4208+ log .debug ("[control connection] Unable to borrow connection for schema agreement: %s" , exc )
4209+
4210+ if last_error :
4211+ raise last_error
4212+ raise NoConnectionsAvailable ("No session connection available for schema agreement on %s" % (endpoint ,))
4213+
4214+ def _schema_agreement_pools (self , endpoint ):
4215+ if endpoint is None :
4216+ return
4217+
4218+ host = self ._cluster .metadata .get_host (endpoint )
4219+ for session in tuple (getattr (self ._cluster , 'sessions' , ())):
4220+ pools = getattr (session , '_pools' , {})
4221+ pool = pools .get (host ) if host else None
4222+ if pool is None :
4223+ for pool_host , candidate_pool in tuple (pools .items ()):
4224+ if getattr (pool_host , 'endpoint' , None ) == endpoint :
4225+ pool = candidate_pool
4226+ break
4227+ if pool and not pool .is_shutdown :
4228+ yield pool
4229+
4230+ def _wait_for_borrowed_schema_response (self , pool , connection , request_id , message , timeout ):
4231+ event = Event ()
4232+ responses = []
4233+
4234+ def callback (response ):
4235+ responses .append (response )
4236+ event .set ()
4237+
4238+ sent = False
4239+ orphaned = False
4240+ try :
4241+ connection .send_msg (message , request_id , callback )
4242+ sent = True
4243+ if not event .wait (timeout ):
4244+ orphaned = self ._orphan_borrowed_request (pool , connection , request_id )
4245+ raise OperationTimedOut (timeout = timeout , in_flight = getattr (connection , 'in_flight' , None ))
4246+
4247+ response = responses [0 ]
4248+ if isinstance (response , Exception ):
4249+ if hasattr (response , 'to_exception' ):
4250+ response = response .to_exception ()
4251+ raise response
4252+ return response
4253+ except Exception :
4254+ if not sent :
4255+ self ._return_request_id_if_unused (connection , request_id )
4256+ raise
4257+ finally :
4258+ if not orphaned :
4259+ pool .return_connection (connection )
4260+
4261+ def _orphan_borrowed_request (self , pool , connection , request_id ):
4262+ try :
4263+ connection ._requests .pop (request_id )
4264+ except KeyError :
4265+ return False
4266+
4267+ with connection .lock :
4268+ connection .orphaned_request_ids .add (request_id )
4269+ if len (connection .orphaned_request_ids ) >= connection .orphaned_threshold :
4270+ connection .orphaned_threshold_reached = True
4271+
4272+ pool .return_connection (connection , stream_was_orphaned = True )
4273+ return True
4274+
4275+ def _return_request_id_if_unused (self , connection , request_id ):
4276+ with connection .lock :
4277+ if request_id in connection ._requests or request_id in connection .orphaned_request_ids :
4278+ return
4279+ if request_id not in connection .request_ids :
4280+ connection .request_ids .append (request_id )
4281+
4282+ def _schema_agreement_endpoint (self , connection ):
4283+ return getattr (connection , 'endpoint' , None ) or self ._last_connection_endpoint
4284+
4285+ def _is_connection_unusable (self , connection ):
4286+ return (connection is None or
4287+ getattr (connection , 'is_closed' , False ) or
4288+ getattr (connection , 'is_defunct' , False ))
4289+
41484290 def _get_schema_mismatches (self , peers_result , local_result , local_address ):
41494291 peers_result = dict_factory (peers_result .column_names , peers_result .parsed_rows )
41504292
@@ -4265,7 +4407,8 @@ def get_connections(self):
42654407 return [c ] if c else []
42664408
42674409 def return_connection (self , connection ):
4268- if connection is self ._connection and (connection .is_defunct or connection .is_closed ):
4410+ if (connection is self ._connection and
4411+ (getattr (connection , 'is_defunct' , False ) or getattr (connection , 'is_closed' , False ))):
42694412 self .reconnect ()
42704413
42714414
0 commit comments