@@ -203,6 +203,7 @@ def __init__(self, host, port, afi, **configs):
203
203
self .afi = afi
204
204
self ._init_host = host
205
205
self ._init_port = port
206
+ self ._init_afi = afi
206
207
self .in_flight_requests = collections .deque ()
207
208
self ._api_versions = None
208
209
@@ -250,67 +251,42 @@ def __init__(self, host, port, afi, **configs):
250
251
self ._sasl_auth_future = None
251
252
self .last_attempt = 0
252
253
self ._gai = None
253
- self ._gai_index = 0
254
254
self ._sensors = None
255
255
if self .config ['metrics' ]:
256
256
self ._sensors = BrokerConnectionMetrics (self .config ['metrics' ],
257
257
self .config ['metric_group_prefix' ],
258
258
self .node_id )
259
259
260
+ def _next_afi_host_port (self ):
261
+ if not self ._gai :
262
+ self ._gai = dns_lookup (self ._init_host , self ._init_port , self ._init_afi )
263
+ if not self ._gai :
264
+ log .error ('DNS lookup failed for {0}:{1} ({2})'
265
+ .format (self ._init_host , self ._init_port , self ._init_afi ))
266
+ return
267
+
268
+ afi , _ , __ , ___ , sockaddr = self ._gai .pop (0 )
269
+ host , port = sockaddr [:2 ]
270
+ return (afi , host , port )
271
+
260
272
def connect (self ):
261
273
"""Attempt to connect and return ConnectionState"""
262
274
if self .state is ConnectionStates .DISCONNECTED :
263
- log .debug ('%s: creating new socket' , self )
264
- # if self.afi is set to AF_UNSPEC, then we need to do a name
265
- # resolution and try all available address families
266
- if self .afi == socket .AF_UNSPEC :
267
- if self ._gai is None :
268
- # XXX: all DNS functions in Python are blocking. If we really
269
- # want to be non-blocking here, we need to use a 3rd-party
270
- # library like python-adns, or move resolution onto its
271
- # own thread. This will be subject to the default libc
272
- # name resolution timeout (5s on most Linux boxes)
273
- try :
274
- self ._gai = socket .getaddrinfo (self ._init_host ,
275
- self ._init_port ,
276
- socket .AF_UNSPEC ,
277
- socket .SOCK_STREAM )
278
- except socket .gaierror as ex :
279
- log .warning ('DNS lookup failed for %s:%d,'
280
- ' exception was %s. Is your'
281
- ' advertised.listeners (called'
282
- ' advertised.host.name before Kafka 9)'
283
- ' correct and resolvable?' ,
284
- self ._init_host , self ._init_port , ex )
285
- self ._gai = []
286
- self ._gai_index = 0
287
- else :
288
- # if self._gai already exists, then we should try the next
289
- # name
290
- self ._gai_index += 1
291
- while True :
292
- if self ._gai_index >= len (self ._gai ):
293
- log .error ('Unable to connect to any of the names for {0}:{1}'
294
- .format (self ._init_host , self ._init_port ))
295
- self ._gai = None
296
- self ._gai_index = 0
297
- return
298
- afi , _ , __ , ___ , sockaddr = self ._gai [self ._gai_index ]
299
- if afi not in (socket .AF_INET , socket .AF_INET6 ):
300
- self ._gai_index += 1
301
- continue
302
- break
303
- self .host , self .port = sockaddr [:2 ]
304
- self ._sock = socket .socket (afi , socket .SOCK_STREAM )
275
+ self .last_attempt = time .time ()
276
+ next_lookup = self ._next_afi_host_port ()
277
+ if not next_lookup :
278
+ self .close (Errors .ConnectionError ('DNS failure' ))
279
+ return
305
280
else :
281
+ log .debug ('%s: creating new socket' , self )
282
+ self .afi , self .host , self .port = next_lookup
306
283
self ._sock = socket .socket (self .afi , socket .SOCK_STREAM )
307
284
308
285
for option in self .config ['socket_options' ]:
309
286
log .debug ('%s: setting socket option %s' , self , option )
310
287
self ._sock .setsockopt (* option )
311
288
312
289
self ._sock .setblocking (False )
313
- self .last_attempt = time .time ()
314
290
self .state = ConnectionStates .CONNECTING
315
291
if self .config ['security_protocol' ] in ('SSL' , 'SASL_SSL' ):
316
292
self ._wrap_ssl ()
@@ -643,23 +619,15 @@ def close(self, error=None):
643
619
will be failed with this exception.
644
620
Default: kafka.errors.ConnectionError.
645
621
"""
646
- if self .state is ConnectionStates .DISCONNECTED :
647
- if error is not None :
648
- if sys .version_info >= (3 , 2 ):
649
- log .warning ('%s: close() called on disconnected connection with error: %s' , self , error , stack_info = True )
650
- else :
651
- log .warning ('%s: close() called on disconnected connection with error: %s' , self , error )
652
- return
653
-
654
622
log .info ('%s: Closing connection. %s' , self , error or '' )
655
- self .state = ConnectionStates .DISCONNECTING
656
- self .config ['state_change_callback' ](self )
623
+ if self .state is not ConnectionStates .DISCONNECTED :
624
+ self .state = ConnectionStates .DISCONNECTING
625
+ self .config ['state_change_callback' ](self )
657
626
self ._update_reconnect_backoff ()
658
627
if self ._sock :
659
628
self ._sock .close ()
660
629
self ._sock = None
661
630
self .state = ConnectionStates .DISCONNECTED
662
- self .last_attempt = time .time ()
663
631
self ._sasl_auth_future = None
664
632
self ._protocol = KafkaProtocol (
665
633
client_id = self .config ['client_id' ],
@@ -1169,3 +1137,29 @@ def collect_hosts(hosts, randomize=True):
1169
1137
shuffle (result )
1170
1138
1171
1139
return result
1140
+
1141
+
1142
+ def is_inet_4_or_6 (gai ):
1143
+ """Given a getaddrinfo struct, return True iff ipv4 or ipv6"""
1144
+ return gai [0 ] in (socket .AF_INET , socket .AF_INET6 )
1145
+
1146
+
1147
+ def dns_lookup (host , port , afi = socket .AF_UNSPEC ):
1148
+ """Returns a list of getaddrinfo structs, optionally filtered to an afi (ipv4 / ipv6)"""
1149
+ # XXX: all DNS functions in Python are blocking. If we really
1150
+ # want to be non-blocking here, we need to use a 3rd-party
1151
+ # library like python-adns, or move resolution onto its
1152
+ # own thread. This will be subject to the default libc
1153
+ # name resolution timeout (5s on most Linux boxes)
1154
+ try :
1155
+ return list (filter (is_inet_4_or_6 ,
1156
+ socket .getaddrinfo (host , port , afi ,
1157
+ socket .SOCK_STREAM )))
1158
+ except socket .gaierror as ex :
1159
+ log .warning ('DNS lookup failed for %s:%d,'
1160
+ ' exception was %s. Is your'
1161
+ ' advertised.listeners (called'
1162
+ ' advertised.host.name before Kafka 9)'
1163
+ ' correct and resolvable?' ,
1164
+ host , port , ex )
1165
+ return []
0 commit comments