@@ -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 ):
@@ -802,15 +803,13 @@ def close(self, error=None):
802
803
will be failed with this exception.
803
804
Default: kafka.errors.KafkaConnectionError.
804
805
"""
806
+ if self .state is ConnectionStates .DISCONNECTED :
807
+ return
805
808
with self ._lock :
806
809
if self .state is ConnectionStates .DISCONNECTED :
807
810
return
808
811
log .info ('%s: Closing connection. %s' , self , error or '' )
809
- self .state = ConnectionStates .DISCONNECTING
810
- self .config ['state_change_callback' ](self )
811
812
self ._update_reconnect_backoff ()
812
- self ._close_socket ()
813
- self .state = ConnectionStates .DISCONNECTED
814
813
self ._sasl_auth_future = None
815
814
self ._protocol = KafkaProtocol (
816
815
client_id = self .config ['client_id' ],
@@ -819,9 +818,18 @@ def close(self, error=None):
819
818
error = Errors .Cancelled (str (self ))
820
819
ifrs = list (self .in_flight_requests .items ())
821
820
self .in_flight_requests .clear ()
822
- self .config ['state_change_callback' ](self )
821
+ self .state = ConnectionStates .DISCONNECTED
822
+ # To avoid race conditions and/or deadlocks
823
+ # keep a reference to the socket but leave it
824
+ # open until after the state_change_callback
825
+ # This should give clients a change to deregister
826
+ # the socket fd from selectors cleanly.
827
+ sock = self ._sock
828
+ self ._sock = None
823
829
824
- # drop lock before processing futures
830
+ # drop lock before state change callback and processing futures
831
+ self .config ['state_change_callback' ](self .node_id , sock , self )
832
+ sock .close ()
825
833
for (_correlation_id , (future , _timestamp )) in ifrs :
826
834
future .failure (error )
827
835
0 commit comments