kafka-python 2.2.6__py2.py3-none-any.whl → 2.2.8__py2.py3-none-any.whl

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/coordinator/base.py CHANGED
@@ -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.debug("Attempt to join group %s rejected since coordinator %s"
595
- " is loading the group.", self.group_id, self.coordinator_id)
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.debug("Attempt to join group %s failed due to unknown member id",
603
- self.group_id)
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.debug("Attempt to join group %s failed due to obsolete "
610
- "coordinator information: %s", self.group_id,
611
- error_type.__name__)
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.debug("SyncGroup for group %s failed due to coordinator"
709
- " rebalance", self.group_id)
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.debug("SyncGroup for group %s failed due to %s", self.group_id, error)
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.debug("SyncGroup for group %s failed due to %s", self.group_id, error)
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 generation(self):
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.debug("LeaveGroup request for group %s returned successfully",
879
- self.group_id)
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("Heartbeat: %s[%s] %s", request.group, request.generation_id, request.member_id) # pylint: disable-msg=no-member
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,31 +937,31 @@ 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):
916
947
  heartbeat_log.warning("Heartbeat failed for group %s: coordinator (node %s)"
917
- " is either not started or not valid", self.group_id,
948
+ " is either not started or not valid", self.group_id,
918
949
  self.coordinator())
919
950
  self.coordinator_dead(error_type())
920
951
  future.failure(error_type())
921
952
  elif error_type is Errors.RebalanceInProgressError:
922
953
  heartbeat_log.warning("Heartbeat failed for group %s because it is"
923
- " rebalancing", self.group_id)
954
+ " rebalancing", self.group_id)
924
955
  self.request_rejoin()
925
956
  future.failure(error_type())
926
957
  elif error_type is Errors.IllegalGenerationError:
927
958
  heartbeat_log.warning("Heartbeat failed for group %s: generation id is not "
928
- " current.", self.group_id)
959
+ " current.", self.group_id)
929
960
  self.reset_generation()
930
961
  future.failure(error_type())
931
962
  elif error_type is Errors.UnknownMemberIdError:
932
963
  heartbeat_log.warning("Heartbeat: local member_id was not recognized;"
933
- " this consumer needs to re-join")
964
+ " this consumer needs to re-join")
934
965
  self.reset_generation()
935
966
  future.failure(error_type)
936
967
  elif error_type is Errors.GroupAuthorizationFailedError:
@@ -1038,36 +1069,31 @@ class HeartbeatThread(threading.Thread):
1038
1069
 
1039
1070
  def run(self):
1040
1071
  try:
1041
- heartbeat_log.debug('Heartbeat thread started')
1072
+ heartbeat_log.debug('Heartbeat thread started: %s', self.coordinator.heartbeat)
1042
1073
  while not self.closed:
1043
1074
  self._run_once()
1044
1075
 
1045
1076
  except ReferenceError:
1046
1077
  heartbeat_log.debug('Heartbeat thread closed due to coordinator gc')
1047
1078
 
1048
- except RuntimeError as e:
1049
- heartbeat_log.error("Heartbeat thread for group %s failed due to unexpected error: %s",
1050
- self.coordinator.group_id, e)
1079
+ except Exception as e:
1080
+ heartbeat_log.exception("Heartbeat thread for group %s failed due to unexpected error: %s",
1081
+ self.coordinator.group_id, e)
1051
1082
  self.failed = e
1052
1083
 
1053
1084
  finally:
1054
1085
  heartbeat_log.debug('Heartbeat thread closed')
1055
1086
 
1056
1087
  def _run_once(self):
1057
- with self.coordinator._client._lock, self.coordinator._lock:
1058
- if self.enabled and self.coordinator.state is MemberState.STABLE:
1059
- # TODO: When consumer.wakeup() is implemented, we need to
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
- heartbeat_log.debug('Heartbeat re-enabled.')
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('Heartbeat poll expired, leaving group')
1101
- ### XXX
1102
- # maybe_leave_group acquires client + coordinator lock;
1103
- # if we hold coordinator lock before calling, we risk deadlock
1104
- # release() is safe here because this is the last code in the current context
1105
- self.coordinator._lock.release()
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
- # poll again after waiting for the retry backoff in case
1110
- # the heartbeat failed or the coordinator disconnected
1111
- heartbeat_log.log(0, 'Not ready to heartbeat, waiting')
1112
- self.coordinator._lock.wait(self.coordinator.config['retry_backoff_ms'] / 1000)
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.generation()
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
- return Future().failure(Errors.CommitFailedError('Group rebalance in progress'))
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('Group rebalance in progress'))
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)
@@ -1,8 +1,13 @@
1
1
  from __future__ import absolute_import, division
2
2
 
3
3
  import copy
4
+ import logging
4
5
  import time
5
6
 
7
+ from kafka.errors import KafkaConfigurationError
8
+
9
+ log = logging.getLogger(__name__)
10
+
6
11
 
7
12
  class Heartbeat(object):
8
13
  DEFAULT_CONFIG = {
@@ -20,9 +25,13 @@ class Heartbeat(object):
20
25
  self.config[key] = configs[key]
21
26
 
22
27
  if self.config['group_id'] is not None:
23
- assert (self.config['heartbeat_interval_ms']
24
- <= self.config['session_timeout_ms']), (
25
- 'Heartbeat interval must be lower than the session timeout')
28
+ if self.config['heartbeat_interval_ms'] >= self.config['session_timeout_ms']:
29
+ raise KafkaConfigurationError('Heartbeat interval must be lower than the session timeout (%s v %s)' % (
30
+ self.config['heartbeat_interval_ms'], self.config['session_timeout_ms']))
31
+ if self.config['heartbeat_interval_ms'] > (self.config['session_timeout_ms'] / 3):
32
+ log.warning('heartbeat_interval_ms is high relative to session_timeout_ms (%s v %s).'
33
+ ' Recommend heartbeat interval less than 1/3rd of session timeout',
34
+ self.config['heartbeat_interval_ms'], self.config['session_timeout_ms'])
26
35
 
27
36
  self.last_send = -1 * float('inf')
28
37
  self.last_receive = -1 * float('inf')
@@ -66,3 +75,10 @@ class Heartbeat(object):
66
75
 
67
76
  def poll_timeout_expired(self):
68
77
  return (time.time() - self.last_poll) > (self.config['max_poll_interval_ms'] / 1000)
78
+
79
+ def __str__(self):
80
+ return ("<Heartbeat group_id={group_id}"
81
+ " heartbeat_interval_ms={heartbeat_interval_ms}"
82
+ " session_timeout_ms={session_timeout_ms}"
83
+ " max_poll_interval_ms={max_poll_interval_ms}"
84
+ " retry_backoff_ms={retry_backoff_ms}>").format(**self.config)
kafka/errors.py CHANGED
@@ -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
 
kafka/producer/kafka.py CHANGED
@@ -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 * 1000,))
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 * 1000,))
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:
kafka/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '2.2.6'
1
+ __version__ = '2.2.8'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kafka-python
3
- Version: 2.2.6
3
+ Version: 2.2.8
4
4
  Summary: Pure Python client for Apache Kafka
5
5
  Author-email: Dana Powers <dana.powers@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/dpkp/kafka-python
@@ -3,12 +3,12 @@ kafka/client_async.py,sha256=R8q_rRpG3RrYrRmcZo7XgO2oSdpLJATNcq8w-1vIJ_8,56878
3
3
  kafka/cluster.py,sha256=N3_Al4We4ZhWzz6lVHy6SfqwDZfQy73iV7Qg4g4nxRs,16745
4
4
  kafka/codec.py,sha256=8NZpnehzNrhSBIjzbPVSvyFbSeLAqEntE7BfVHu-_9I,10036
5
5
  kafka/conn.py,sha256=pDmzcn-m8oiFdvYh-97qbRLEBXh0sSl9nT74VIIRuEE,69472
6
- kafka/errors.py,sha256=VygO7AYZvbb52wVgjxuXz-6S2W3vNzzDstF5FNP8Bvk,33829
6
+ kafka/errors.py,sha256=qX2Fp0qawU_HBNcZCwB7EDCmx3C2PehrETi6qSEJHmk,33290
7
7
  kafka/future.py,sha256=ZQStbfUYIPJRrgMfAWxxjrIRVxsw4WCtSR0J0bkyGno,2847
8
8
  kafka/socks5_wrapper.py,sha256=6woOaCTJXJ5e89_zdyW5BjOpyE4rCbYFH-kd-FeuPuk,9827
9
9
  kafka/structs.py,sha256=SJGzmLdV21jZyQ7247k0WFy16UiusgTHK3I-e4qzI-E,3058
10
10
  kafka/util.py,sha256=EnzCJuRkQ6Kh2lIdNwFKvT4PddkZ5bzop4ooGGIhe5g,4366
11
- kafka/version.py,sha256=ki8CvreWdC_q66zVTHoUGjT2EI8obe9OHjOQcNZfOfM,22
11
+ kafka/version.py,sha256=IqD9h-jsDDriviuNsOqmA0rXPSWRSWW_V-IX7uKeRnk,22
12
12
  kafka/admin/__init__.py,sha256=S_XxqyyV480_yXhttK79XZqNAmZyXRjspd3SoqYykE8,720
13
13
  kafka/admin/acl_resource.py,sha256=ak_dUsSni4SyP0ORbSKenZpwTy0Ykxq3FSt_9XgLR8k,8265
14
14
  kafka/admin/client.py,sha256=RabA8l8Im3iBEXgPVkiofNW6QyeatQHaymBWFZ8Sxkw,78929
@@ -27,9 +27,9 @@ kafka/consumer/fetcher.py,sha256=iwYhWotaEQ55oXTzGKPUOYxvC_6FcoIks_ZqL-gu3DE,688
27
27
  kafka/consumer/group.py,sha256=xmEpVMPJbCAk9__pdAOMswh8I-Ujj5hBax_hPZHZb_s,58758
28
28
  kafka/consumer/subscription_state.py,sha256=f_qJQMhTWQnUd_7lPj43gsagWSKGEmP4jpnEwA6s1Ec,23661
29
29
  kafka/coordinator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- kafka/coordinator/base.py,sha256=mYTn0ldZgoljxxstF8mA4MxUQaYPSZr83FwvwCU3Av0,51761
31
- kafka/coordinator/consumer.py,sha256=IJWWt4E6E7JZZGKtGgPtud9V3eqs0js6EaosS3bxffE,44766
32
- kafka/coordinator/heartbeat.py,sha256=WJqZGnXHG7TTq1Is3D0mKDis-bBwWVZlSgQiUoZv1jU,2304
30
+ kafka/coordinator/base.py,sha256=NmHXyqoJZVXL2KhahXLCOH1zVx9gyTdhrt-_unxIAaE,54365
31
+ kafka/coordinator/consumer.py,sha256=52yYmM5o_VzNOKvMJkN7CwHlvWWHoNu6xAFuX52JCdo,45870
32
+ kafka/coordinator/heartbeat.py,sha256=LeJJlwz1oUEOfEMIFT-R7ZOHBQ-b-luVKwmKyWxLfDo,3242
33
33
  kafka/coordinator/protocol.py,sha256=wTaIOnUVbj0CKXZ82FktZo-zMRvOCk3hdQAoHJ62e3I,1041
34
34
  kafka/coordinator/assignors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
35
  kafka/coordinator/assignors/abstract.py,sha256=belUnCkuw70HJ8HTWYIgVrT6pJmIBBrTl1vkO-bN1C0,1507
@@ -67,7 +67,7 @@ kafka/partitioner/__init__.py,sha256=Fks3C5_kokVWYw1Ad5wv0sVVzaaBtOejL-2bIL1yRII
67
67
  kafka/partitioner/default.py,sha256=tW-RC1PWIPRDEbeEAaPTLn-00oiZnXoVouEk9AnYE4w,2879
68
68
  kafka/producer/__init__.py,sha256=i3Wxih0NHjmqCkRNE54ial8fBp9siqabUE6ZGyL6oX8,122
69
69
  kafka/producer/future.py,sha256=UC3-g9QlgVFmbitrtMXVpeP0Pbvr7xl2kcw6bAehKG8,2983
70
- kafka/producer/kafka.py,sha256=rzsAoB4ser889nRCtILqGqzWI7jREGV9HPngimCWJPE,53211
70
+ kafka/producer/kafka.py,sha256=-xWSiy4V8kNTpqNZVZiEtEdZG2H27n54MTw8sPZx9Cc,53211
71
71
  kafka/producer/record_accumulator.py,sha256=dhJW2vxiEDxsws0xRQ5REIrt3lLNu1g0R7HIMs6pZOY,28172
72
72
  kafka/producer/sender.py,sha256=8-TLTw6vQO7AheWSDPI33cQdWMyTDxi1k-pkXuUb9k0,37789
73
73
  kafka/producer/transaction_manager.py,sha256=HNfJNZwNfJtYdftn9SeaDfi7I5MKk0LD3sK64inuPt0,41537
@@ -120,7 +120,7 @@ kafka/vendor/enum34.py,sha256=-u-lxAiJMt6ru4Do7NUDY9OpeWkYJMksb2xengJawFE,31204
120
120
  kafka/vendor/selectors34.py,sha256=gxejLO4eXf8mRSGXaQiknPig3GdX1rtsZiYOQJVuAy8,20594
121
121
  kafka/vendor/six.py,sha256=lLBa9_HrANP5BMZ7twEzg1M3wofwPmXyptuWmHX0brY,34826
122
122
  kafka/vendor/socketpair.py,sha256=Fi3PoY1Okkppab720wFk1BhHXyjcw7hi5DwhqrYZH2Y,2737
123
- kafka_python-2.2.6.dist-info/METADATA,sha256=3J37JUCY6RdrHghp6U2KKIb4TN-B8ePAWcMvKi6waSs,9951
124
- kafka_python-2.2.6.dist-info/WHEEL,sha256=oSJJyWjO7Z2XSScFQUpXG1HL-N0sFMqqeKVVbZTPkWc,109
125
- kafka_python-2.2.6.dist-info/top_level.txt,sha256=IivJz7l5WHdLNDT6RIiVAlhjQzYRwGqBBmKHZ7WjPeM,6
126
- kafka_python-2.2.6.dist-info/RECORD,,
123
+ kafka_python-2.2.8.dist-info/METADATA,sha256=SHY6zV6gEmQJmTCl59wWgrwSG-D88F6gEaqkZGfndR8,9951
124
+ kafka_python-2.2.8.dist-info/WHEEL,sha256=egKm5cKfE6OqlHwodY8Jjp4yqZDBXgsj09UsV5ojd_U,109
125
+ kafka_python-2.2.8.dist-info/top_level.txt,sha256=IivJz7l5WHdLNDT6RIiVAlhjQzYRwGqBBmKHZ7WjPeM,6
126
+ kafka_python-2.2.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any