kafka-python 2.2.7__tar.gz → 2.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kafka_python-2.2.7 → kafka_python-2.2.8}/CHANGES.md +12 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/PKG-INFO +1 -1
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/base.py +96 -47
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/consumer.py +21 -5
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/errors.py +1 -8
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/producer/kafka.py +2 -2
- kafka_python-2.2.8/kafka/version.py +1 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka_python.egg-info/PKG-INFO +1 -1
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/integration/test_consumer_group.py +26 -2
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_coordinator.py +1 -0
- kafka_python-2.2.7/kafka/version.py +0 -1
- {kafka_python-2.2.7 → kafka_python-2.2.8}/AUTHORS.md +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/LICENSE +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/MANIFEST.in +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/README.rst +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/admin/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/admin/acl_resource.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/admin/client.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/admin/config_resource.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/admin/new_partitions.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/admin/new_topic.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/benchmarks/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/benchmarks/consumer_performance.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/benchmarks/load_example.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/benchmarks/producer_performance.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/benchmarks/record_batch_compose.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/benchmarks/record_batch_read.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/benchmarks/varint_speed.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/client_async.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/cluster.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/codec.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/conn.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/consumer/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/consumer/fetcher.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/consumer/group.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/consumer/subscription_state.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/assignors/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/assignors/abstract.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/assignors/range.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/assignors/roundrobin.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/assignors/sticky/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/assignors/sticky/partition_movements.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/assignors/sticky/sorted_set.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/assignors/sticky/sticky_assignor.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/heartbeat.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/coordinator/protocol.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/future.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/compound_stat.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/dict_reporter.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/kafka_metric.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/measurable.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/measurable_stat.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/metric_config.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/metric_name.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/metrics.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/metrics_reporter.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/quota.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stat.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stats/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stats/avg.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stats/count.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stats/histogram.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stats/max_stat.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stats/min_stat.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stats/percentile.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stats/percentiles.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stats/rate.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stats/sampled_stat.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stats/sensor.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/metrics/stats/total.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/partitioner/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/partitioner/default.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/producer/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/producer/future.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/producer/record_accumulator.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/producer/sender.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/producer/transaction_manager.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/abstract.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/add_offsets_to_txn.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/add_partitions_to_txn.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/admin.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/api.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/api_versions.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/broker_api_versions.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/commit.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/end_txn.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/fetch.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/find_coordinator.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/frame.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/group.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/init_producer_id.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/list_offsets.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/message.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/metadata.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/offset_for_leader_epoch.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/parser.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/pickle.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/produce.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/sasl_authenticate.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/sasl_handshake.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/struct.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/txn_offset_commit.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/protocol/types.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/record/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/record/_crc32c.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/record/abc.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/record/default_records.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/record/legacy_records.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/record/memory_records.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/record/util.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/sasl/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/sasl/abc.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/sasl/gssapi.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/sasl/msk.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/sasl/oauth.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/sasl/plain.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/sasl/scram.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/sasl/sspi.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/serializer/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/serializer/abstract.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/socks5_wrapper.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/structs.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/util.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/vendor/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/vendor/enum34.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/vendor/selectors34.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/vendor/six.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka/vendor/socketpair.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka_python.egg-info/SOURCES.txt +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka_python.egg-info/dependency_links.txt +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka_python.egg-info/requires.txt +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/kafka_python.egg-info/top_level.txt +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/pyproject.toml +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/setup.cfg +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/setup.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/integration/__init__.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/integration/conftest.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/integration/fixtures.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/integration/test_admin_integration.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/integration/test_consumer_integration.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/integration/test_producer_integration.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/integration/test_sasl_integration.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_acl_comparisons.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_admin.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_api_object_implementation.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_assignors.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_client_async.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_cluster.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_codec.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_conn.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_consumer.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_fetcher.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_metrics.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_object_conversion.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_package.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_partition_movements.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_partitioner.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_producer.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_protocol.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_record_accumulator.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_sender.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_subscription_state.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/test_util.py +0 -0
- {kafka_python-2.2.7 → kafka_python-2.2.8}/test/testutil.py +0 -0
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# 2.2.8 (May 20, 2025)
|
|
2
|
+
|
|
3
|
+
Fixes
|
|
4
|
+
* Wait for next heartbeat in thread loop; check for connected coordinator (#2622)
|
|
5
|
+
* Acquire client lock in heartbeat thread before sending requests (#2620)
|
|
6
|
+
|
|
7
|
+
Logging / Error Messages
|
|
8
|
+
* Log all SyncGroupResponse errors as info+
|
|
9
|
+
* More coordinator / heartbeat logging (#2621)
|
|
10
|
+
* Fix timeout seconds error message in KafkaProducer (#2627)
|
|
11
|
+
* Update offset commit error handling; use RebalanceInProgressError if applicable (#2623)
|
|
12
|
+
|
|
1
13
|
# 2.2.7 (May 13, 2025)
|
|
2
14
|
|
|
3
15
|
Fixes
|
|
@@ -5,6 +5,7 @@ import copy
|
|
|
5
5
|
import logging
|
|
6
6
|
import threading
|
|
7
7
|
import time
|
|
8
|
+
import warnings
|
|
8
9
|
import weakref
|
|
9
10
|
|
|
10
11
|
from kafka.vendor import six
|
|
@@ -43,6 +44,9 @@ class Generation(object):
|
|
|
43
44
|
self.member_id == other.member_id and
|
|
44
45
|
self.protocol == other.protocol)
|
|
45
46
|
|
|
47
|
+
def __str__(self):
|
|
48
|
+
return "<Generation %s (member_id: %s, protocol: %s)>" % (self.generation_id, self.member_id, self.protocol)
|
|
49
|
+
|
|
46
50
|
|
|
47
51
|
Generation.NO_GENERATION = Generation(DEFAULT_GENERATION_ID, UNKNOWN_MEMBER_ID, None)
|
|
48
52
|
|
|
@@ -250,6 +254,11 @@ class BaseCoordinator(object):
|
|
|
250
254
|
else:
|
|
251
255
|
return self.coordinator_id
|
|
252
256
|
|
|
257
|
+
def connected(self):
|
|
258
|
+
"""Return True iff the coordinator node is connected"""
|
|
259
|
+
with self._lock:
|
|
260
|
+
return self.coordinator_id is not None and self._client.connected(self.coordinator_id)
|
|
261
|
+
|
|
253
262
|
def ensure_coordinator_ready(self, timeout_ms=None):
|
|
254
263
|
"""Block until the coordinator for this group is known.
|
|
255
264
|
|
|
@@ -309,7 +318,7 @@ class BaseCoordinator(object):
|
|
|
309
318
|
self._find_coordinator_future = None
|
|
310
319
|
|
|
311
320
|
def lookup_coordinator(self):
|
|
312
|
-
with self._lock:
|
|
321
|
+
with self._client._lock, self._lock:
|
|
313
322
|
if self._find_coordinator_future is not None:
|
|
314
323
|
return self._find_coordinator_future
|
|
315
324
|
|
|
@@ -398,17 +407,16 @@ class BaseCoordinator(object):
|
|
|
398
407
|
# will be invoked even if the consumer is woken up before
|
|
399
408
|
# finishing the rebalance
|
|
400
409
|
with self._lock:
|
|
401
|
-
log.info("Successfully joined group %s with generation %s",
|
|
402
|
-
self.group_id, self._generation.generation_id)
|
|
403
410
|
self.state = MemberState.STABLE
|
|
404
411
|
if self._heartbeat_thread:
|
|
405
412
|
self._heartbeat_thread.enable()
|
|
406
413
|
|
|
407
|
-
def _handle_join_failure(self,
|
|
414
|
+
def _handle_join_failure(self, exception):
|
|
408
415
|
# we handle failures below after the request finishes.
|
|
409
416
|
# if the join completes after having been woken up,
|
|
410
417
|
# the exception is ignored and we will rejoin
|
|
411
418
|
with self._lock:
|
|
419
|
+
log.info("Failed to join group %s: %s", self.group_id, exception)
|
|
412
420
|
self.state = MemberState.UNJOINED
|
|
413
421
|
|
|
414
422
|
def ensure_active_group(self, timeout_ms=None):
|
|
@@ -554,8 +562,9 @@ class BaseCoordinator(object):
|
|
|
554
562
|
|
|
555
563
|
def _failed_request(self, node_id, request, future, error):
|
|
556
564
|
# Marking coordinator dead
|
|
557
|
-
# unless the error is caused by internal client pipelining
|
|
565
|
+
# unless the error is caused by internal client pipelining or throttling
|
|
558
566
|
if not isinstance(error, (Errors.NodeNotReadyError,
|
|
567
|
+
Errors.ThrottlingQuotaExceededError,
|
|
559
568
|
Errors.TooManyInFlightRequests)):
|
|
560
569
|
log.error('Error sending %s to node %s [%s]',
|
|
561
570
|
request.__class__.__name__, node_id, error)
|
|
@@ -566,10 +575,9 @@ class BaseCoordinator(object):
|
|
|
566
575
|
future.failure(error)
|
|
567
576
|
|
|
568
577
|
def _handle_join_group_response(self, future, send_time, response):
|
|
578
|
+
log.debug("Received JoinGroup response: %s", response)
|
|
569
579
|
error_type = Errors.for_code(response.error_code)
|
|
570
580
|
if error_type is Errors.NoError:
|
|
571
|
-
log.debug("Received successful JoinGroup response for group %s: %s",
|
|
572
|
-
self.group_id, response)
|
|
573
581
|
if self._sensors:
|
|
574
582
|
self._sensors.join_latency.record((time.time() - send_time) * 1000)
|
|
575
583
|
with self._lock:
|
|
@@ -583,6 +591,7 @@ class BaseCoordinator(object):
|
|
|
583
591
|
response.member_id,
|
|
584
592
|
response.group_protocol)
|
|
585
593
|
|
|
594
|
+
log.info("Successfully joined group %s %s", self.group_id, self._generation)
|
|
586
595
|
if response.leader_id == response.member_id:
|
|
587
596
|
log.info("Elected group leader -- performing partition"
|
|
588
597
|
" assignments using %s", self._generation.protocol)
|
|
@@ -591,24 +600,24 @@ class BaseCoordinator(object):
|
|
|
591
600
|
self._on_join_follower().chain(future)
|
|
592
601
|
|
|
593
602
|
elif error_type is Errors.CoordinatorLoadInProgressError:
|
|
594
|
-
log.
|
|
595
|
-
|
|
603
|
+
log.info("Attempt to join group %s rejected since coordinator %s"
|
|
604
|
+
" is loading the group.", self.group_id, self.coordinator_id)
|
|
596
605
|
# backoff and retry
|
|
597
606
|
future.failure(error_type(response))
|
|
598
607
|
elif error_type is Errors.UnknownMemberIdError:
|
|
599
608
|
# reset the member id and retry immediately
|
|
600
609
|
error = error_type(self._generation.member_id)
|
|
601
610
|
self.reset_generation()
|
|
602
|
-
log.
|
|
603
|
-
|
|
611
|
+
log.info("Attempt to join group %s failed due to unknown member id",
|
|
612
|
+
self.group_id)
|
|
604
613
|
future.failure(error)
|
|
605
614
|
elif error_type in (Errors.CoordinatorNotAvailableError,
|
|
606
615
|
Errors.NotCoordinatorError):
|
|
607
616
|
# re-discover the coordinator and retry with backoff
|
|
608
617
|
self.coordinator_dead(error_type())
|
|
609
|
-
log.
|
|
610
|
-
|
|
611
|
-
|
|
618
|
+
log.info("Attempt to join group %s failed due to obsolete "
|
|
619
|
+
"coordinator information: %s", self.group_id,
|
|
620
|
+
error_type.__name__)
|
|
612
621
|
future.failure(error_type())
|
|
613
622
|
elif error_type in (Errors.InconsistentGroupProtocolError,
|
|
614
623
|
Errors.InvalidSessionTimeoutError,
|
|
@@ -619,12 +628,21 @@ class BaseCoordinator(object):
|
|
|
619
628
|
self.group_id, error)
|
|
620
629
|
future.failure(error)
|
|
621
630
|
elif error_type is Errors.GroupAuthorizationFailedError:
|
|
631
|
+
log.error("Attempt to join group %s failed due to group authorization error",
|
|
632
|
+
self.group_id)
|
|
622
633
|
future.failure(error_type(self.group_id))
|
|
623
634
|
elif error_type is Errors.MemberIdRequiredError:
|
|
624
635
|
# Broker requires a concrete member id to be allowed to join the group. Update member id
|
|
625
636
|
# and send another join group request in next cycle.
|
|
637
|
+
log.info("Received member id %s for group %s; will retry join-group",
|
|
638
|
+
response.member_id, self.group_id)
|
|
626
639
|
self.reset_generation(response.member_id)
|
|
627
640
|
future.failure(error_type())
|
|
641
|
+
elif error_type is Errors.RebalanceInProgressError:
|
|
642
|
+
log.info("Attempt to join group %s failed due to RebalanceInProgressError,"
|
|
643
|
+
" which could indicate a replication timeout on the broker. Will retry.",
|
|
644
|
+
self.group_id)
|
|
645
|
+
future.failure(error_type())
|
|
628
646
|
else:
|
|
629
647
|
# unexpected error, throw the exception
|
|
630
648
|
error = error_type()
|
|
@@ -693,6 +711,7 @@ class BaseCoordinator(object):
|
|
|
693
711
|
return future
|
|
694
712
|
|
|
695
713
|
def _handle_sync_group_response(self, future, send_time, response):
|
|
714
|
+
log.debug("Received SyncGroup response: %s", response)
|
|
696
715
|
error_type = Errors.for_code(response.error_code)
|
|
697
716
|
if error_type is Errors.NoError:
|
|
698
717
|
if self._sensors:
|
|
@@ -705,19 +724,19 @@ class BaseCoordinator(object):
|
|
|
705
724
|
if error_type is Errors.GroupAuthorizationFailedError:
|
|
706
725
|
future.failure(error_type(self.group_id))
|
|
707
726
|
elif error_type is Errors.RebalanceInProgressError:
|
|
708
|
-
log.
|
|
709
|
-
|
|
727
|
+
log.info("SyncGroup for group %s failed due to coordinator"
|
|
728
|
+
" rebalance", self.group_id)
|
|
710
729
|
future.failure(error_type(self.group_id))
|
|
711
730
|
elif error_type in (Errors.UnknownMemberIdError,
|
|
712
731
|
Errors.IllegalGenerationError):
|
|
713
732
|
error = error_type()
|
|
714
|
-
log.
|
|
733
|
+
log.info("SyncGroup for group %s failed due to %s", self.group_id, error)
|
|
715
734
|
self.reset_generation()
|
|
716
735
|
future.failure(error)
|
|
717
736
|
elif error_type in (Errors.CoordinatorNotAvailableError,
|
|
718
737
|
Errors.NotCoordinatorError):
|
|
719
738
|
error = error_type()
|
|
720
|
-
log.
|
|
739
|
+
log.info("SyncGroup for group %s failed due to %s", self.group_id, error)
|
|
721
740
|
self.coordinator_dead(error)
|
|
722
741
|
future.failure(error)
|
|
723
742
|
else:
|
|
@@ -739,13 +758,13 @@ class BaseCoordinator(object):
|
|
|
739
758
|
e = Errors.NodeNotReadyError(node_id)
|
|
740
759
|
return Future().failure(e)
|
|
741
760
|
|
|
742
|
-
log.debug("Sending group coordinator request for group %s to broker %s",
|
|
743
|
-
self.group_id, node_id)
|
|
744
761
|
version = self._client.api_version(FindCoordinatorRequest, max_version=2)
|
|
745
762
|
if version == 0:
|
|
746
763
|
request = FindCoordinatorRequest[version](self.group_id)
|
|
747
764
|
else:
|
|
748
765
|
request = FindCoordinatorRequest[version](self.group_id, 0)
|
|
766
|
+
log.debug("Sending group coordinator request for group %s to broker %s: %s",
|
|
767
|
+
self.group_id, node_id, request)
|
|
749
768
|
future = Future()
|
|
750
769
|
_f = self._client.send(node_id, request)
|
|
751
770
|
_f.add_callback(self._handle_group_coordinator_response, future)
|
|
@@ -792,7 +811,7 @@ class BaseCoordinator(object):
|
|
|
792
811
|
self.coordinator_id, self.group_id, error)
|
|
793
812
|
self.coordinator_id = None
|
|
794
813
|
|
|
795
|
-
def
|
|
814
|
+
def generation_if_stable(self):
|
|
796
815
|
"""Get the current generation state if the group is stable.
|
|
797
816
|
|
|
798
817
|
Returns: the current generation or None if the group is unjoined/rebalancing
|
|
@@ -802,6 +821,15 @@ class BaseCoordinator(object):
|
|
|
802
821
|
return None
|
|
803
822
|
return self._generation
|
|
804
823
|
|
|
824
|
+
# deprecated
|
|
825
|
+
def generation(self):
|
|
826
|
+
warnings.warn("Function coordinator.generation() has been renamed to generation_if_stable()",
|
|
827
|
+
DeprecationWarning, stacklevel=2)
|
|
828
|
+
return self.generation_if_stable()
|
|
829
|
+
|
|
830
|
+
def rebalance_in_progress(self):
|
|
831
|
+
return self.state is MemberState.REBALANCING
|
|
832
|
+
|
|
805
833
|
def reset_generation(self, member_id=UNKNOWN_MEMBER_ID):
|
|
806
834
|
"""Reset the generation and member_id because we have fallen out of the group."""
|
|
807
835
|
with self._lock:
|
|
@@ -865,6 +893,7 @@ class BaseCoordinator(object):
|
|
|
865
893
|
log.info('Leaving consumer group (%s).', self.group_id)
|
|
866
894
|
version = self._client.api_version(LeaveGroupRequest, max_version=2)
|
|
867
895
|
request = LeaveGroupRequest[version](self.group_id, self._generation.member_id)
|
|
896
|
+
log.debug('Sending LeaveGroupRequest to %s: %s', self.coordinator_id, request)
|
|
868
897
|
future = self._client.send(self.coordinator_id, request)
|
|
869
898
|
future.add_callback(self._handle_leave_group_response)
|
|
870
899
|
future.add_errback(log.error, "LeaveGroup request failed: %s")
|
|
@@ -873,16 +902,18 @@ class BaseCoordinator(object):
|
|
|
873
902
|
self.reset_generation()
|
|
874
903
|
|
|
875
904
|
def _handle_leave_group_response(self, response):
|
|
905
|
+
log.debug("Received LeaveGroupResponse: %s", response)
|
|
876
906
|
error_type = Errors.for_code(response.error_code)
|
|
877
907
|
if error_type is Errors.NoError:
|
|
878
|
-
log.
|
|
879
|
-
|
|
908
|
+
log.info("LeaveGroup request for group %s returned successfully",
|
|
909
|
+
self.group_id)
|
|
880
910
|
else:
|
|
881
911
|
log.error("LeaveGroup request for group %s failed with error: %s",
|
|
882
912
|
self.group_id, error_type())
|
|
883
913
|
|
|
884
914
|
def _send_heartbeat_request(self):
|
|
885
915
|
"""Send a heartbeat request"""
|
|
916
|
+
# Note: acquire both client + coordinator lock before calling
|
|
886
917
|
if self.coordinator_unknown():
|
|
887
918
|
e = Errors.CoordinatorNotAvailableError(self.coordinator_id)
|
|
888
919
|
return Future().failure(e)
|
|
@@ -895,7 +926,7 @@ class BaseCoordinator(object):
|
|
|
895
926
|
request = HeartbeatRequest[version](self.group_id,
|
|
896
927
|
self._generation.generation_id,
|
|
897
928
|
self._generation.member_id)
|
|
898
|
-
heartbeat_log.debug("
|
|
929
|
+
heartbeat_log.debug("Sending HeartbeatRequest to %s: %s", self.coordinator_id, request)
|
|
899
930
|
future = Future()
|
|
900
931
|
_f = self._client.send(self.coordinator_id, request)
|
|
901
932
|
_f.add_callback(self._handle_heartbeat_response, future, time.time())
|
|
@@ -906,10 +937,10 @@ class BaseCoordinator(object):
|
|
|
906
937
|
def _handle_heartbeat_response(self, future, send_time, response):
|
|
907
938
|
if self._sensors:
|
|
908
939
|
self._sensors.heartbeat_latency.record((time.time() - send_time) * 1000)
|
|
940
|
+
heartbeat_log.debug("Received heartbeat response for group %s: %s",
|
|
941
|
+
self.group_id, response)
|
|
909
942
|
error_type = Errors.for_code(response.error_code)
|
|
910
943
|
if error_type is Errors.NoError:
|
|
911
|
-
heartbeat_log.debug("Received successful heartbeat response for group %s",
|
|
912
|
-
self.group_id)
|
|
913
944
|
future.success(None)
|
|
914
945
|
elif error_type in (Errors.CoordinatorNotAvailableError,
|
|
915
946
|
Errors.NotCoordinatorError):
|
|
@@ -1054,20 +1085,15 @@ class HeartbeatThread(threading.Thread):
|
|
|
1054
1085
|
heartbeat_log.debug('Heartbeat thread closed')
|
|
1055
1086
|
|
|
1056
1087
|
def _run_once(self):
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
# disable here to prevent propagating an exception to this
|
|
1061
|
-
# heartbeat thread
|
|
1062
|
-
# must get client._lock, or maybe deadlock at heartbeat
|
|
1063
|
-
# failure callback in consumer poll
|
|
1064
|
-
self.coordinator._client.poll(timeout_ms=0)
|
|
1065
|
-
|
|
1066
|
-
with self.coordinator._lock:
|
|
1088
|
+
self.coordinator._client._lock.acquire()
|
|
1089
|
+
self.coordinator._lock.acquire()
|
|
1090
|
+
try:
|
|
1067
1091
|
if not self.enabled:
|
|
1068
1092
|
heartbeat_log.debug('Heartbeat disabled. Waiting')
|
|
1093
|
+
self.coordinator._client._lock.release()
|
|
1069
1094
|
self.coordinator._lock.wait()
|
|
1070
|
-
|
|
1095
|
+
if self.enabled:
|
|
1096
|
+
heartbeat_log.debug('Heartbeat re-enabled.')
|
|
1071
1097
|
return
|
|
1072
1098
|
|
|
1073
1099
|
if self.coordinator.state is not MemberState.STABLE:
|
|
@@ -1078,14 +1104,24 @@ class HeartbeatThread(threading.Thread):
|
|
|
1078
1104
|
self.disable()
|
|
1079
1105
|
return
|
|
1080
1106
|
|
|
1107
|
+
# TODO: When consumer.wakeup() is implemented, we need to
|
|
1108
|
+
# disable here to prevent propagating an exception to this
|
|
1109
|
+
# heartbeat thread
|
|
1110
|
+
self.coordinator._client.poll(timeout_ms=0)
|
|
1111
|
+
|
|
1081
1112
|
if self.coordinator.coordinator_unknown():
|
|
1082
1113
|
future = self.coordinator.lookup_coordinator()
|
|
1083
1114
|
if not future.is_done or future.failed():
|
|
1084
1115
|
# the immediate future check ensures that we backoff
|
|
1085
1116
|
# properly in the case that no brokers are available
|
|
1086
1117
|
# to connect to (and the future is automatically failed).
|
|
1118
|
+
self.coordinator._client._lock.release()
|
|
1087
1119
|
self.coordinator._lock.wait(self.coordinator.config['retry_backoff_ms'] / 1000)
|
|
1088
1120
|
|
|
1121
|
+
elif not self.coordinator.connected():
|
|
1122
|
+
self.coordinator._client._lock.release()
|
|
1123
|
+
self.coordinator._lock.wait(self.coordinator.config['retry_backoff_ms'] / 1000)
|
|
1124
|
+
|
|
1089
1125
|
elif self.coordinator.heartbeat.session_timeout_expired():
|
|
1090
1126
|
# the session timeout has expired without seeing a
|
|
1091
1127
|
# successful heartbeat, so we should probably make sure
|
|
@@ -1097,28 +1133,39 @@ class HeartbeatThread(threading.Thread):
|
|
|
1097
1133
|
# the poll timeout has expired, which means that the
|
|
1098
1134
|
# foreground thread has stalled in between calls to
|
|
1099
1135
|
# poll(), so we explicitly leave the group.
|
|
1100
|
-
heartbeat_log.warning(
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1136
|
+
heartbeat_log.warning(
|
|
1137
|
+
"Consumer poll timeout has expired. This means the time between subsequent calls to poll()"
|
|
1138
|
+
" was longer than the configured max_poll_interval_ms, which typically implies that"
|
|
1139
|
+
" the poll loop is spending too much time processing messages. You can address this"
|
|
1140
|
+
" either by increasing max_poll_interval_ms or by reducing the maximum size of batches"
|
|
1141
|
+
" returned in poll() with max_poll_records."
|
|
1142
|
+
)
|
|
1106
1143
|
self.coordinator.maybe_leave_group()
|
|
1107
1144
|
|
|
1108
1145
|
elif not self.coordinator.heartbeat.should_heartbeat():
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
self.coordinator._lock.wait(
|
|
1146
|
+
next_hb = self.coordinator.heartbeat.time_to_next_heartbeat()
|
|
1147
|
+
heartbeat_log.debug('Waiting %0.1f secs to send next heartbeat', next_hb)
|
|
1148
|
+
self.coordinator._client._lock.release()
|
|
1149
|
+
self.coordinator._lock.wait(next_hb)
|
|
1113
1150
|
|
|
1114
1151
|
else:
|
|
1152
|
+
heartbeat_log.debug('Sending heartbeat for group %s %s', self.coordinator.group_id, self.coordinator._generation)
|
|
1115
1153
|
self.coordinator.heartbeat.sent_heartbeat()
|
|
1116
1154
|
future = self.coordinator._send_heartbeat_request()
|
|
1117
1155
|
future.add_callback(self._handle_heartbeat_success)
|
|
1118
1156
|
future.add_errback(self._handle_heartbeat_failure)
|
|
1119
1157
|
|
|
1158
|
+
finally:
|
|
1159
|
+
self.coordinator._lock.release()
|
|
1160
|
+
try:
|
|
1161
|
+
# Possibly released in block above to allow coordinator lock wait()
|
|
1162
|
+
self.coordinator._client._lock.release()
|
|
1163
|
+
except RuntimeError:
|
|
1164
|
+
pass
|
|
1165
|
+
|
|
1120
1166
|
def _handle_heartbeat_success(self, result):
|
|
1121
1167
|
with self.coordinator._lock:
|
|
1168
|
+
heartbeat_log.debug('Heartbeat success')
|
|
1122
1169
|
self.coordinator.heartbeat.received_heartbeat()
|
|
1123
1170
|
|
|
1124
1171
|
def _handle_heartbeat_failure(self, exception):
|
|
@@ -1129,8 +1176,10 @@ class HeartbeatThread(threading.Thread):
|
|
|
1129
1176
|
# member in the group for as long as the duration of the
|
|
1130
1177
|
# rebalance timeout. If we stop sending heartbeats, however,
|
|
1131
1178
|
# then the session timeout may expire before we can rejoin.
|
|
1179
|
+
heartbeat_log.debug('Treating RebalanceInProgressError as successful heartbeat')
|
|
1132
1180
|
self.coordinator.heartbeat.received_heartbeat()
|
|
1133
1181
|
else:
|
|
1182
|
+
heartbeat_log.debug('Heartbeat failure: %s', exception)
|
|
1134
1183
|
self.coordinator.heartbeat.fail_heartbeat()
|
|
1135
1184
|
# wake up the thread if it's sleeping to reschedule the heartbeat
|
|
1136
1185
|
self.coordinator._lock.notify()
|
|
@@ -608,6 +608,11 @@ class ConsumerCoordinator(BaseCoordinator):
|
|
|
608
608
|
if node_id is None:
|
|
609
609
|
return Future().failure(Errors.CoordinatorNotAvailableError)
|
|
610
610
|
|
|
611
|
+
# Verify node is ready
|
|
612
|
+
if not self._client.ready(node_id, metadata_priority=False):
|
|
613
|
+
log.debug("Node %s not ready -- failing offset commit request",
|
|
614
|
+
node_id)
|
|
615
|
+
return Future().failure(Errors.NodeNotReadyError)
|
|
611
616
|
|
|
612
617
|
# create the offset commit request
|
|
613
618
|
offset_data = collections.defaultdict(dict)
|
|
@@ -616,7 +621,7 @@ class ConsumerCoordinator(BaseCoordinator):
|
|
|
616
621
|
|
|
617
622
|
version = self._client.api_version(OffsetCommitRequest, max_version=6)
|
|
618
623
|
if version > 1 and self._subscription.partitions_auto_assigned():
|
|
619
|
-
generation = self.
|
|
624
|
+
generation = self.generation_if_stable()
|
|
620
625
|
else:
|
|
621
626
|
generation = Generation.NO_GENERATION
|
|
622
627
|
|
|
@@ -625,7 +630,18 @@ class ConsumerCoordinator(BaseCoordinator):
|
|
|
625
630
|
# and let the user rejoin the group in poll()
|
|
626
631
|
if generation is None:
|
|
627
632
|
log.info("Failing OffsetCommit request since the consumer is not part of an active group")
|
|
628
|
-
|
|
633
|
+
if self.rebalance_in_progress():
|
|
634
|
+
# if the client knows it is already rebalancing, we can use RebalanceInProgressError instead of
|
|
635
|
+
# CommitFailedError to indicate this is not a fatal error
|
|
636
|
+
return Future().failure(Errors.RebalanceInProgressError(
|
|
637
|
+
"Offset commit cannot be completed since the"
|
|
638
|
+
" consumer is undergoing a rebalance for auto partition assignment. You can try completing the rebalance"
|
|
639
|
+
" by calling poll() and then retry the operation."))
|
|
640
|
+
else:
|
|
641
|
+
return Future().failure(Errors.CommitFailedError(
|
|
642
|
+
"Offset commit cannot be completed since the"
|
|
643
|
+
" consumer is not part of an active group for auto partition assignment; it is likely that the consumer"
|
|
644
|
+
" was kicked out of the group."))
|
|
629
645
|
|
|
630
646
|
if version == 0:
|
|
631
647
|
request = OffsetCommitRequest[version](
|
|
@@ -756,7 +772,7 @@ class ConsumerCoordinator(BaseCoordinator):
|
|
|
756
772
|
# However, we do not need to reset generations and just request re-join, such that
|
|
757
773
|
# if the caller decides to proceed and poll, it would still try to proceed and re-join normally.
|
|
758
774
|
self.request_rejoin()
|
|
759
|
-
future.failure(Errors.CommitFailedError(
|
|
775
|
+
future.failure(Errors.CommitFailedError(error_type()))
|
|
760
776
|
return
|
|
761
777
|
elif error_type in (Errors.UnknownMemberIdError,
|
|
762
778
|
Errors.IllegalGenerationError):
|
|
@@ -765,7 +781,7 @@ class ConsumerCoordinator(BaseCoordinator):
|
|
|
765
781
|
log.warning("OffsetCommit for group %s failed: %s",
|
|
766
782
|
self.group_id, error)
|
|
767
783
|
self.reset_generation()
|
|
768
|
-
future.failure(Errors.CommitFailedError())
|
|
784
|
+
future.failure(Errors.CommitFailedError(error_type()))
|
|
769
785
|
return
|
|
770
786
|
else:
|
|
771
787
|
log.error("Group %s failed to commit partition %s at offset"
|
|
@@ -804,7 +820,7 @@ class ConsumerCoordinator(BaseCoordinator):
|
|
|
804
820
|
return Future().failure(Errors.CoordinatorNotAvailableError)
|
|
805
821
|
|
|
806
822
|
# Verify node is ready
|
|
807
|
-
if not self._client.ready(node_id):
|
|
823
|
+
if not self._client.ready(node_id, metadata_priority=False):
|
|
808
824
|
log.debug("Node %s not ready -- failing offset fetch request",
|
|
809
825
|
node_id)
|
|
810
826
|
return Future().failure(Errors.NodeNotReadyError)
|
|
@@ -24,14 +24,7 @@ class CommitFailedError(KafkaError):
|
|
|
24
24
|
def __init__(self, *args):
|
|
25
25
|
if not args:
|
|
26
26
|
args = ("Commit cannot be completed since the group has already"
|
|
27
|
-
" rebalanced and assigned the partitions to another member."
|
|
28
|
-
" This means that the time between subsequent calls to poll()"
|
|
29
|
-
" was longer than the configured max_poll_interval_ms, which"
|
|
30
|
-
" typically implies that the poll loop is spending too much"
|
|
31
|
-
" time message processing. You can address this either by"
|
|
32
|
-
" increasing the rebalance timeout with max_poll_interval_ms,"
|
|
33
|
-
" or by reducing the maximum size of batches returned in poll()"
|
|
34
|
-
" with max_poll_records.",)
|
|
27
|
+
" rebalanced and assigned the partitions to another member.",)
|
|
35
28
|
super(CommitFailedError, self).__init__(*args)
|
|
36
29
|
|
|
37
30
|
|
|
@@ -944,7 +944,7 @@ class KafkaProducer(object):
|
|
|
944
944
|
"""
|
|
945
945
|
# add topic to metadata topic list if it is not there already.
|
|
946
946
|
self._sender.add_topic(topic)
|
|
947
|
-
timer = Timer(max_wait_ms, "Failed to update metadata after %.1f secs." % (max_wait_ms
|
|
947
|
+
timer = Timer(max_wait_ms, "Failed to update metadata after %.1f secs." % (max_wait_ms / 1000,))
|
|
948
948
|
metadata_event = None
|
|
949
949
|
while True:
|
|
950
950
|
partitions = self._metadata.partitions_for_topic(topic)
|
|
@@ -962,7 +962,7 @@ class KafkaProducer(object):
|
|
|
962
962
|
metadata_event.wait(timer.timeout_ms / 1000)
|
|
963
963
|
if not metadata_event.is_set():
|
|
964
964
|
raise Errors.KafkaTimeoutError(
|
|
965
|
-
"Failed to update metadata after %.1f secs." % (max_wait_ms
|
|
965
|
+
"Failed to update metadata after %.1f secs." % (max_wait_ms / 1000,))
|
|
966
966
|
elif topic in self._metadata.unauthorized_topics:
|
|
967
967
|
raise Errors.TopicAuthorizationFailedError(set([topic]))
|
|
968
968
|
else:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.2.8'
|
|
@@ -125,6 +125,20 @@ def test_group(kafka_broker, topic):
|
|
|
125
125
|
for partition in range(num_partitions)])
|
|
126
126
|
logging.info('Assignment looks good!')
|
|
127
127
|
|
|
128
|
+
logging.info('Verifying heartbeats')
|
|
129
|
+
while True:
|
|
130
|
+
for c in range(num_consumers):
|
|
131
|
+
heartbeat = consumers[c]._coordinator.heartbeat
|
|
132
|
+
last_hb = time.time() - 0.5
|
|
133
|
+
if (heartbeat.heartbeat_failed or
|
|
134
|
+
heartbeat.last_receive < last_hb or
|
|
135
|
+
heartbeat.last_reset > last_hb):
|
|
136
|
+
time.sleep(0.1)
|
|
137
|
+
continue
|
|
138
|
+
else:
|
|
139
|
+
break
|
|
140
|
+
logging.info('Heartbeats look good')
|
|
141
|
+
|
|
128
142
|
finally:
|
|
129
143
|
logging.info('Shutting down %s consumers', num_consumers)
|
|
130
144
|
for c in range(num_consumers):
|
|
@@ -163,18 +177,28 @@ def test_heartbeat_thread(kafka_broker, topic):
|
|
|
163
177
|
heartbeat_interval_ms=500)
|
|
164
178
|
|
|
165
179
|
# poll until we have joined group / have assignment
|
|
180
|
+
start = time.time()
|
|
166
181
|
while not consumer.assignment():
|
|
167
182
|
consumer.poll(timeout_ms=100)
|
|
168
183
|
|
|
169
184
|
assert consumer._coordinator.state is MemberState.STABLE
|
|
170
185
|
last_poll = consumer._coordinator.heartbeat.last_poll
|
|
171
|
-
|
|
186
|
+
|
|
187
|
+
# wait until we receive first heartbeat
|
|
188
|
+
while consumer._coordinator.heartbeat.last_receive < start:
|
|
189
|
+
time.sleep(0.1)
|
|
190
|
+
|
|
191
|
+
last_send = consumer._coordinator.heartbeat.last_send
|
|
192
|
+
last_recv = consumer._coordinator.heartbeat.last_receive
|
|
193
|
+
assert last_poll > start
|
|
194
|
+
assert last_send > start
|
|
195
|
+
assert last_recv > start
|
|
172
196
|
|
|
173
197
|
timeout = time.time() + 30
|
|
174
198
|
while True:
|
|
175
199
|
if time.time() > timeout:
|
|
176
200
|
raise RuntimeError('timeout waiting for heartbeat')
|
|
177
|
-
if consumer._coordinator.heartbeat.
|
|
201
|
+
if consumer._coordinator.heartbeat.last_receive > last_recv:
|
|
178
202
|
break
|
|
179
203
|
time.sleep(0.5)
|
|
180
204
|
|
|
@@ -658,6 +658,7 @@ def test_heartbeat(mocker, patched_coord):
|
|
|
658
658
|
heartbeat.enable()
|
|
659
659
|
patched_coord.state = MemberState.STABLE
|
|
660
660
|
mocker.spy(patched_coord, '_send_heartbeat_request')
|
|
661
|
+
mocker.patch.object(patched_coord, 'connected', return_value=True)
|
|
661
662
|
mocker.patch.object(patched_coord.heartbeat, 'should_heartbeat', return_value=True)
|
|
662
663
|
heartbeat._run_once()
|
|
663
664
|
assert patched_coord._send_heartbeat_request.call_count == 1
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.2.7'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|