@@ -212,7 +212,7 @@ class BrokerConnection(object):
212
212
'ssl_ciphers' : None ,
213
213
'api_version' : (0 , 8 , 2 ), # default to most restrictive
214
214
'selector' : selectors .DefaultSelector ,
215
- 'state_change_callback' : lambda conn : True ,
215
+ 'state_change_callback' : lambda node_id , sock , conn : True ,
216
216
'metrics' : None ,
217
217
'metric_group_prefix' : '' ,
218
218
'sasl_mechanism' : None ,
@@ -357,6 +357,7 @@ def connect(self):
357
357
return self .state
358
358
else :
359
359
log .debug ('%s: creating new socket' , self )
360
+ assert self ._sock is None
360
361
self ._sock_afi , self ._sock_addr = next_lookup
361
362
self ._sock = socket .socket (self ._sock_afi , socket .SOCK_STREAM )
362
363
@@ -366,7 +367,7 @@ def connect(self):
366
367
367
368
self ._sock .setblocking (False )
368
369
self .state = ConnectionStates .CONNECTING
369
- self .config ['state_change_callback' ](self )
370
+ self .config ['state_change_callback' ](self . node_id , self . _sock , self )
370
371
log .info ('%s: connecting to %s:%d [%s %s]' , self , self .host ,
371
372
self .port , self ._sock_addr , AFI_NAMES [self ._sock_afi ])
372
373
@@ -386,21 +387,21 @@ def connect(self):
386
387
if self .config ['security_protocol' ] in ('SSL' , 'SASL_SSL' ):
387
388
log .debug ('%s: initiating SSL handshake' , self )
388
389
self .state = ConnectionStates .HANDSHAKE
389
- self .config ['state_change_callback' ](self )
390
+ self .config ['state_change_callback' ](self . node_id , self . _sock , self )
390
391
# _wrap_ssl can alter the connection state -- disconnects on failure
391
392
self ._wrap_ssl ()
392
393
393
394
elif self .config ['security_protocol' ] == 'SASL_PLAINTEXT' :
394
395
log .debug ('%s: initiating SASL authentication' , self )
395
396
self .state = ConnectionStates .AUTHENTICATING
396
- self .config ['state_change_callback' ](self )
397
+ self .config ['state_change_callback' ](self . node_id , self . _sock , self )
397
398
398
399
else :
399
400
# security_protocol PLAINTEXT
400
401
log .info ('%s: Connection complete.' , self )
401
402
self .state = ConnectionStates .CONNECTED
402
403
self ._reset_reconnect_backoff ()
403
- self .config ['state_change_callback' ](self )
404
+ self .config ['state_change_callback' ](self . node_id , self . _sock , self )
404
405
405
406
# Connection failed
406
407
# WSAEINVAL == 10022, but errno.WSAEINVAL is not available on non-win systems
@@ -425,7 +426,7 @@ def connect(self):
425
426
log .info ('%s: Connection complete.' , self )
426
427
self .state = ConnectionStates .CONNECTED
427
428
self ._reset_reconnect_backoff ()
428
- self .config ['state_change_callback' ](self )
429
+ self .config ['state_change_callback' ](self . node_id , self . _sock , self )
429
430
430
431
if self .state is ConnectionStates .AUTHENTICATING :
431
432
assert self .config ['security_protocol' ] in ('SASL_PLAINTEXT' , 'SASL_SSL' )
@@ -435,7 +436,7 @@ def connect(self):
435
436
log .info ('%s: Connection complete.' , self )
436
437
self .state = ConnectionStates .CONNECTED
437
438
self ._reset_reconnect_backoff ()
438
- self .config ['state_change_callback' ](self )
439
+ self .config ['state_change_callback' ](self . node_id , self . _sock , self )
439
440
440
441
if self .state not in (ConnectionStates .CONNECTED ,
441
442
ConnectionStates .DISCONNECTED ):
@@ -806,11 +807,7 @@ def close(self, error=None):
806
807
if self .state is ConnectionStates .DISCONNECTED :
807
808
return
808
809
log .info ('%s: Closing connection. %s' , self , error or '' )
809
- self .state = ConnectionStates .DISCONNECTING
810
- self .config ['state_change_callback' ](self )
811
810
self ._update_reconnect_backoff ()
812
- self ._close_socket ()
813
- self .state = ConnectionStates .DISCONNECTED
814
811
self ._sasl_auth_future = None
815
812
self ._protocol = KafkaProtocol (
816
813
client_id = self .config ['client_id' ],
@@ -819,9 +816,18 @@ def close(self, error=None):
819
816
error = Errors .Cancelled (str (self ))
820
817
ifrs = list (self .in_flight_requests .items ())
821
818
self .in_flight_requests .clear ()
822
- self .config ['state_change_callback' ](self )
819
+ self .state = ConnectionStates .DISCONNECTED
820
+ # To avoid race conditions and/or deadlocks
821
+ # keep a reference to the socket but leave it
822
+ # open until after the state_change_callback
823
+ # This should give clients a change to deregister
824
+ # the socket fd from selectors cleanly.
825
+ sock = self ._sock
826
+ self ._sock = None
823
827
824
- # drop lock before processing futures
828
+ # drop lock before state change callback and processing futures
829
+ self .config ['state_change_callback' ](self .node_id , sock , self )
830
+ sock .close ()
825
831
for (_correlation_id , (future , _timestamp )) in ifrs :
826
832
future .failure (error )
827
833
0 commit comments