Repository: dpkp/kafka-python

Issue:
Invalid bootstrap servers list blocks during startup
```python
>>> kafka.KafkaProducer(bootstrap_servers='can-not-resolve:9092')
# never returns
```

Comments:

Patch:
diff --git a/kafka/cluster.py b/kafka/cluster.py
index 45f25ad..8078eb7 100644
--- a/kafka/cluster.py
+++ b/kafka/cluster.py
@@ -214,7 +214,8 @@ class ClusterMetadata(object):
             return self.failed_update(error)
 
         if not metadata.brokers:
-            log.warning("No broker metadata found in MetadataResponse")
+            log.warning("No broker metadata found in MetadataResponse -- ignoring.")
+            return self.failed_update(Errors.MetadataEmptyBrokerList(metadata))
 
         _new_brokers = {}
         for broker in metadata.brokers:
diff --git a/kafka/conn.py b/kafka/conn.py
index f67edfb..a2d5ee6 100644
--- a/kafka/conn.py
+++ b/kafka/conn.py
@@ -292,11 +292,7 @@ class BrokerConnection(object):
         # First attempt to perform dns lookup
         # note that the underlying interface, socket.getaddrinfo,
         # has no explicit timeout so we may exceed the user-specified timeout
-        while time.time() < timeout:
-            if self._dns_lookup():
-                break
-        else:
-            return False
+        self._dns_lookup()
 
         # Loop once over all returned dns entries
         selector = None
@@ -903,6 +899,7 @@ class BrokerConnection(object):
 
         Returns: version tuple, i.e. (0, 10), (0, 9), (0, 8, 2), ...
         """
+        timeout_at = time.time() + timeout
         log.info('Probing node %s broker version', self.node_id)
         # Monkeypatch some connection configurations to avoid timeouts
         override_config = {
@@ -932,7 +929,7 @@ class BrokerConnection(object):
         ]
 
         for version, request in test_cases:
-            if not self.connect_blocking(timeout):
+            if not self.connect_blocking(timeout_at - time.time()):
                 raise Errors.NodeNotReadyError()
             f = self.send(request)
             # HACK: sleeping to wait for socket to send bytes
diff --git a/kafka/errors.py b/kafka/errors.py
index f4c8740..93a9f40 100644
--- a/kafka/errors.py
+++ b/kafka/errors.py
@@ -54,6 +54,10 @@ class StaleMetadata(KafkaError):
     invalid_metadata = True
 
 
+class MetadataEmptyBrokerList(KafkaError):
+    retriable = True
+
+
 class UnrecognizedBrokerVersion(KafkaError):
     pass
 


Test patch:
diff --git a/test/test_cluster.py b/test/test_cluster.py
new file mode 100644
index 0000000..f010c4f
--- /dev/null
+++ b/test/test_cluster.py
@@ -0,0 +1,22 @@
+# pylint: skip-file
+from __future__ import absolute_import
+
+import pytest
+
+from kafka.cluster import ClusterMetadata
+from kafka.protocol.metadata import MetadataResponse
+
+
+def test_empty_broker_list():
+    cluster = ClusterMetadata()
+    assert len(cluster.brokers()) == 0
+
+    cluster.update_metadata(MetadataResponse[0](
+        [(0, 'foo', 12), (1, 'bar', 34)], []))
+    assert len(cluster.brokers()) == 2
+
+    # empty broker list response should be ignored
+    cluster.update_metadata(MetadataResponse[0](
+        [],  # empty brokers
+        [(17, 'foo', []), (17, 'bar', [])]))  # topics w/ error
+    assert len(cluster.brokers()) == 2