karafka 2.6.0.beta1 → 2.6.0.rc2

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.
Files changed (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -27
  3. data/certs/expired.txt +2 -0
  4. data/config/locales/errors.yml +5 -0
  5. data/config/locales/pro_errors.yml +5 -5
  6. data/karafka.gemspec +1 -1
  7. data/lib/active_job/queue_adapters/karafka_adapter.rb +1 -1
  8. data/lib/karafka/active_job/current_attributes/loading.rb +1 -1
  9. data/lib/karafka/active_job/current_attributes.rb +4 -2
  10. data/lib/karafka/active_job/dispatcher.rb +1 -2
  11. data/lib/karafka/admin/consumer_groups.rb +8 -2
  12. data/lib/karafka/admin/replication.rb +6 -3
  13. data/lib/karafka/admin/topics.rb +32 -12
  14. data/lib/karafka/admin.rb +155 -59
  15. data/lib/karafka/app.rb +1 -1
  16. data/lib/karafka/base_consumer.rb +33 -5
  17. data/lib/karafka/cli/console.rb +0 -1
  18. data/lib/karafka/cli/help.rb +0 -1
  19. data/lib/karafka/cli/info.rb +0 -1
  20. data/lib/karafka/cli/install.rb +0 -1
  21. data/lib/karafka/cli/server.rb +0 -1
  22. data/lib/karafka/cli/swarm.rb +0 -1
  23. data/lib/karafka/cli.rb +2 -2
  24. data/lib/karafka/connection/client.rb +44 -7
  25. data/lib/karafka/connection/listener.rb +3 -3
  26. data/lib/karafka/connection/manager.rb +0 -1
  27. data/lib/karafka/connection/pauses_manager.rb +20 -3
  28. data/lib/karafka/connection/proxy.rb +45 -0
  29. data/lib/karafka/connection/status.rb +0 -1
  30. data/lib/karafka/instrumentation/assignments_tracker.rb +34 -0
  31. data/lib/karafka/instrumentation/callbacks/consumer_groups/decorator.rb +19 -0
  32. data/lib/karafka/instrumentation/callbacks/consumer_groups/error.rb +0 -1
  33. data/lib/karafka/instrumentation/callbacks/consumer_groups/statistics.rb +3 -2
  34. data/lib/karafka/instrumentation/critical_errors_listener.rb +85 -0
  35. data/lib/karafka/instrumentation/logger_listener.rb +2 -0
  36. data/lib/karafka/instrumentation/monitor.rb +2 -3
  37. data/lib/karafka/instrumentation/vendors/appsignal/errors_listener.rb +0 -1
  38. data/lib/karafka/instrumentation/vendors/appsignal/metrics_listener.rb +0 -1
  39. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +4 -3
  40. data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +0 -2
  41. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +181 -18
  42. data/lib/karafka/pro/admin/recovery/errors.rb +0 -1
  43. data/lib/karafka/pro/admin/recovery.rb +6 -4
  44. data/lib/karafka/pro/base_consumer.rb +5 -5
  45. data/lib/karafka/pro/cleaner/messages/metadata.rb +0 -1
  46. data/lib/karafka/pro/cli/parallel_segments/base.rb +1 -2
  47. data/lib/karafka/pro/connection/manager.rb +7 -4
  48. data/lib/karafka/pro/encryption/messages/middleware.rb +5 -0
  49. data/lib/karafka/pro/encryption/messages/parser.rb +4 -0
  50. data/lib/karafka/pro/instrumentation/callbacks/consumer_groups/decorator.rb +63 -0
  51. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/compensator.rb +120 -0
  52. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/fetcher.rb +88 -0
  53. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/refresher.rb +185 -0
  54. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/registry.rb +102 -0
  55. data/lib/karafka/pro/instrumentation/performance_tracker.rb +40 -6
  56. data/lib/karafka/pro/iterator/tpl_builder.rb +27 -7
  57. data/lib/karafka/pro/iterator.rb +22 -7
  58. data/lib/karafka/pro/loader.rb +12 -0
  59. data/lib/karafka/pro/processing/consumer_groups/adaptive_iterator/consumer.rb +0 -3
  60. data/lib/karafka/pro/processing/consumer_groups/adaptive_iterator/tracker.rb +2 -4
  61. data/lib/karafka/pro/processing/consumer_groups/collapser.rb +6 -9
  62. data/lib/karafka/pro/processing/consumer_groups/coordinator.rb +1 -4
  63. data/lib/karafka/pro/processing/consumer_groups/coordinators/errors_tracker.rb +0 -3
  64. data/lib/karafka/pro/processing/consumer_groups/coordinators/filters_applier.rb +21 -10
  65. data/lib/karafka/pro/processing/consumer_groups/coordinators/virtual_offset_manager.rb +4 -4
  66. data/lib/karafka/pro/processing/consumer_groups/expansions_selector.rb +0 -3
  67. data/lib/karafka/pro/processing/consumer_groups/filters/actions.rb +84 -0
  68. data/lib/karafka/pro/processing/consumer_groups/filters/base.rb +5 -6
  69. data/lib/karafka/pro/processing/consumer_groups/filters/delayer.rb +2 -5
  70. data/lib/karafka/pro/processing/consumer_groups/filters/expirer.rb +1 -4
  71. data/lib/karafka/pro/processing/consumer_groups/filters/inline_insights_delayer.rb +1 -4
  72. data/lib/karafka/pro/processing/consumer_groups/filters/throttler.rb +2 -5
  73. data/lib/karafka/pro/processing/consumer_groups/filters/virtual_limiter.rb +0 -3
  74. data/lib/karafka/pro/processing/consumer_groups/jobs/consume_non_blocking.rb +2 -3
  75. data/lib/karafka/pro/processing/consumer_groups/jobs/eofed_non_blocking.rb +0 -2
  76. data/lib/karafka/pro/processing/consumer_groups/jobs/periodic.rb +2 -3
  77. data/lib/karafka/pro/processing/consumer_groups/jobs/periodic_non_blocking.rb +0 -2
  78. data/lib/karafka/pro/processing/consumer_groups/jobs/revoked_non_blocking.rb +0 -3
  79. data/lib/karafka/pro/processing/consumer_groups/jobs_builder.rb +5 -4
  80. data/lib/karafka/pro/processing/consumer_groups/offset_metadata/consumer.rb +3 -7
  81. data/lib/karafka/pro/processing/consumer_groups/offset_metadata/fetcher.rb +8 -10
  82. data/lib/karafka/pro/processing/consumer_groups/offset_metadata/listener.rb +0 -3
  83. data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/base.rb +3 -5
  84. data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/default.rb +0 -3
  85. data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/mom.rb +0 -3
  86. data/lib/karafka/pro/processing/consumer_groups/partitioner.rb +2 -5
  87. data/lib/karafka/pro/processing/consumer_groups/periodic_job/consumer.rb +2 -5
  88. data/lib/karafka/pro/processing/consumer_groups/piping/consumer.rb +2 -5
  89. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_lrj_mom.rb +10 -9
  90. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_lrj_mom_vp.rb +11 -10
  91. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_mom.rb +0 -3
  92. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_mom_vp.rb +0 -3
  93. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_lrj_mom.rb +8 -7
  94. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_lrj_mom_vp.rb +9 -8
  95. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_mom.rb +5 -8
  96. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_mom_vp.rb +3 -6
  97. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_lrj_mom.rb +4 -7
  98. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_lrj_mom_vp.rb +6 -9
  99. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_mom.rb +3 -7
  100. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_mom_vp.rb +4 -8
  101. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/lrj_mom.rb +3 -6
  102. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/lrj_mom_vp.rb +4 -7
  103. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/mom.rb +2 -5
  104. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/mom_vp.rb +3 -6
  105. data/lib/karafka/pro/processing/consumer_groups/strategies/base.rb +0 -3
  106. data/lib/karafka/pro/processing/consumer_groups/strategies/default.rb +56 -33
  107. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/default.rb +27 -14
  108. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr.rb +0 -3
  109. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj.rb +4 -7
  110. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_mom.rb +5 -8
  111. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_mom_vp.rb +5 -8
  112. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_vp.rb +3 -7
  113. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_mom.rb +0 -3
  114. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_mom_vp.rb +0 -3
  115. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_vp.rb +0 -3
  116. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj.rb +2 -5
  117. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_mom.rb +0 -3
  118. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_mom_vp.rb +0 -3
  119. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_vp.rb +3 -6
  120. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/mom.rb +0 -3
  121. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/mom_vp.rb +3 -6
  122. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/vp.rb +65 -8
  123. data/lib/karafka/pro/processing/consumer_groups/strategies/ftr/default.rb +0 -4
  124. data/lib/karafka/pro/processing/consumer_groups/strategies/ftr/vp.rb +2 -5
  125. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/default.rb +0 -3
  126. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr.rb +3 -6
  127. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_mom.rb +4 -7
  128. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_mom_vp.rb +4 -8
  129. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_vp.rb +3 -6
  130. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/mom.rb +2 -5
  131. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/mom_vp.rb +3 -7
  132. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/vp.rb +2 -5
  133. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/default.rb +0 -3
  134. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/ftr.rb +0 -3
  135. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/ftr_vp.rb +0 -3
  136. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/vp.rb +0 -3
  137. data/lib/karafka/pro/processing/consumer_groups/strategies/vp/default.rb +11 -6
  138. data/lib/karafka/pro/processing/consumer_groups/strategy_selector.rb +0 -3
  139. data/lib/karafka/pro/processing/consumer_groups/subscription_groups_coordinator.rb +0 -3
  140. data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/balanced.rb +0 -3
  141. data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/base.rb +2 -4
  142. data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/consistent.rb +0 -3
  143. data/lib/karafka/pro/processing/jobs_queue.rb +4 -1
  144. data/lib/karafka/pro/processing/schedulers/base.rb +4 -2
  145. data/lib/karafka/pro/processing/schedulers/default.rb +2 -2
  146. data/lib/karafka/pro/recurring_tasks/consumer.rb +9 -2
  147. data/lib/karafka/pro/recurring_tasks/contracts/config.rb +0 -1
  148. data/lib/karafka/pro/recurring_tasks/executor.rb +25 -13
  149. data/lib/karafka/pro/routing/features/consumer_groups/direct_assignments/subscription_group.rb +0 -2
  150. data/lib/karafka/pro/routing/features/consumer_groups/long_running_job.rb +0 -2
  151. data/lib/karafka/pro/routing/features/consumer_groups/multiplexing/proxy.rb +2 -2
  152. data/lib/karafka/pro/routing/features/consumer_groups/non_blocking_job.rb +0 -2
  153. data/lib/karafka/pro/routing/features/consumer_groups/offset_metadata/topic.rb +0 -6
  154. data/lib/karafka/pro/routing/features/consumer_groups/parallel_segments/consumer_group.rb +5 -4
  155. data/lib/karafka/pro/routing/features/consumer_groups/patterns/config.rb +0 -6
  156. data/lib/karafka/pro/routing/features/consumer_groups/patterns/consumer_group.rb +2 -1
  157. data/lib/karafka/pro/routing/features/consumer_groups/patterns/contracts/pattern.rb +0 -1
  158. data/lib/karafka/pro/routing/features/consumer_groups/patterns/detector.rb +2 -1
  159. data/lib/karafka/pro/routing/features/consumer_groups/patterns/pattern.rb +2 -1
  160. data/lib/karafka/pro/routing/features/consumer_groups/patterns/topics.rb +8 -8
  161. data/lib/karafka/pro/routing/features/consumer_groups/periodic_job/topic.rb +5 -5
  162. data/lib/karafka/pro/routing/features/consumer_groups/recurring_tasks/builder.rb +34 -15
  163. data/lib/karafka/pro/routing/features/consumer_groups/scheduled_messages/builder.rb +52 -28
  164. data/lib/karafka/pro/routing/features/consumer_groups/scheduled_messages/topic.rb +2 -1
  165. data/lib/karafka/pro/routing/features/consumer_groups/virtual_partitions/topic.rb +6 -5
  166. data/lib/karafka/pro/routing/features/pausing/config.rb +7 -11
  167. data/lib/karafka/pro/routing/features/pausing/contracts/topic.rb +6 -8
  168. data/lib/karafka/pro/routing/features/pausing/topic.rb +18 -63
  169. data/lib/karafka/pro/scheduled_messages/consumer.rb +7 -1
  170. data/lib/karafka/pro/scheduled_messages/schema_validator.rb +6 -1
  171. data/lib/karafka/pro/swarm/liveness_listener.rb +118 -13
  172. data/lib/karafka/pro.rb +0 -6
  173. data/lib/karafka/processing/consumer_groups/coordinator.rb +4 -5
  174. data/lib/karafka/processing/consumer_groups/coordinators_buffer.rb +6 -2
  175. data/lib/karafka/processing/consumer_groups/executor.rb +4 -4
  176. data/lib/karafka/processing/consumer_groups/executors_buffer.rb +2 -4
  177. data/lib/karafka/processing/consumer_groups/expansions_selector.rb +0 -2
  178. data/lib/karafka/processing/consumer_groups/inline_insights/consumer.rb +0 -2
  179. data/lib/karafka/processing/consumer_groups/inline_insights/listener.rb +0 -2
  180. data/lib/karafka/processing/consumer_groups/inline_insights/tracker.rb +14 -16
  181. data/lib/karafka/processing/consumer_groups/jobs/consume.rb +2 -5
  182. data/lib/karafka/processing/consumer_groups/jobs/eofed.rb +2 -3
  183. data/lib/karafka/processing/consumer_groups/jobs/idle.rb +2 -2
  184. data/lib/karafka/processing/consumer_groups/jobs/revoked.rb +2 -4
  185. data/lib/karafka/processing/consumer_groups/jobs/shutdown.rb +2 -2
  186. data/lib/karafka/processing/consumer_groups/partitioner.rb +3 -6
  187. data/lib/karafka/processing/consumer_groups/strategies/aj_dlq_mom.rb +5 -2
  188. data/lib/karafka/processing/consumer_groups/strategies/aj_mom.rb +2 -4
  189. data/lib/karafka/processing/consumer_groups/strategies/base.rb +3 -5
  190. data/lib/karafka/processing/consumer_groups/strategies/default.rb +43 -13
  191. data/lib/karafka/processing/consumer_groups/strategies/dlq.rb +5 -2
  192. data/lib/karafka/processing/consumer_groups/strategies/dlq_mom.rb +5 -2
  193. data/lib/karafka/processing/consumer_groups/strategies/mom.rb +2 -3
  194. data/lib/karafka/processing/consumer_groups/strategy_selector.rb +0 -2
  195. data/lib/karafka/processing/coordinator.rb +0 -2
  196. data/lib/karafka/processing/coordinators_buffer.rb +0 -2
  197. data/lib/karafka/processing/partitioner.rb +0 -2
  198. data/lib/karafka/processing/strategy_selector.rb +0 -2
  199. data/lib/karafka/processing/worker.rb +14 -6
  200. data/lib/karafka/processing/workers_pool.rb +21 -4
  201. data/lib/karafka/railtie.rb +0 -1
  202. data/lib/karafka/routing/contracts/routing.rb +2 -2
  203. data/lib/karafka/routing/features/consumer_groups/active_job/topic.rb +6 -5
  204. data/lib/karafka/routing/features/consumer_groups/manual_offset_management/topic.rb +2 -2
  205. data/lib/karafka/routing/features/deserializers/topic.rb +0 -1
  206. data/lib/karafka/routing/features/pausing/config.rb +27 -0
  207. data/lib/karafka/routing/features/pausing.rb +13 -0
  208. data/lib/karafka/routing/subscription_group.rb +1 -1
  209. data/lib/karafka/routing/topic.rb +20 -7
  210. data/lib/karafka/routing/topics.rb +21 -1
  211. data/lib/karafka/server.rb +9 -0
  212. data/lib/karafka/setup/config.rb +52 -55
  213. data/lib/karafka/setup/config_proxy.rb +2 -1
  214. data/lib/karafka/setup/contracts/config.rb +14 -0
  215. data/lib/karafka/swarm/node.rb +2 -2
  216. data/lib/karafka/version.rb +1 -2
  217. data/lib/karafka.rb +2 -2
  218. metadata +14 -4
@@ -47,28 +47,50 @@ module Karafka
47
47
  # - 1 - polling ttl exceeded
48
48
  # - 2 - consuming ttl exceeded
49
49
  # - 3 - memory limit exceeded
50
+ # - 4 - stability ttl exceeded (stuck in non-steady librdkafka join state)
50
51
  #
51
52
  # @note This listener should not break anything if subscribed in the supervisor prior to
52
53
  # forking as it relies on server events for operations.
53
54
  class LivenessListener < Karafka::Swarm::LivenessListener
55
+ # Default time in ms after which we consider consumption hanging (5 minutes)
56
+ DEFAULT_CONSUMING_TTL = 5 * 60 * 1_000
57
+ # Default max time in ms between polls before the node is considered dead (5 minutes)
58
+ DEFAULT_POLLING_TTL = 5 * 60 * 1_000
59
+ # Multiplier applied to max.poll.interval.ms when deriving the default stability_ttl.
60
+ STABILITY_TTL_MAX_POLL_MULTIPLIER = 2
61
+
62
+ private_constant(
63
+ :DEFAULT_CONSUMING_TTL,
64
+ :DEFAULT_POLLING_TTL,
65
+ :STABILITY_TTL_MAX_POLL_MULTIPLIER
66
+ )
67
+
54
68
  # @param memory_limit [Integer] max memory in MB for this process to be considered healthy
55
- # @param consuming_ttl [Integer] time in ms after which we consider consumption hanging.
56
- # It allows us to define max consumption time after which supervisor should consider
57
- # given process as hanging
58
- # @param polling_ttl [Integer] max time in ms for polling. If polling (any) does not
59
- # happen that often, process should be considered dead.
60
- # @note The default TTL matches the default `max.poll.interval.ms`
69
+ # @param consuming_ttl [Integer] see Kubernetes::LivenessListener for full documentation.
70
+ # @param polling_ttl [Integer] see Kubernetes::LivenessListener for full documentation.
71
+ # @param stability_ttl [Integer, nil] see Kubernetes::LivenessListener for full
72
+ # documentation, including the derivation rules (max `max.poll.interval.ms` across
73
+ # all active subscription groups, times 2, resolved lazily on first health
74
+ # evaluation). Same semantics apply here; the node is reported unhealthy with
75
+ # status code 4 instead of returning HTTP 500.
61
76
  def initialize(
62
77
  memory_limit: Float::INFINITY,
63
- consuming_ttl: 5 * 60 * 1_000,
64
- polling_ttl: 5 * 60 * 1_000
78
+ consuming_ttl: DEFAULT_CONSUMING_TTL,
79
+ polling_ttl: DEFAULT_POLLING_TTL,
80
+ stability_ttl: nil
65
81
  )
66
82
  @polling_ttl = polling_ttl
67
83
  @consuming_ttl = consuming_ttl
84
+ # When nil, resolved lazily via #stability_ttl on first health evaluation because
85
+ # the default derives from the routing subscription groups, which may not be drawn
86
+ # yet when the listener is constructed in karafka.rb
87
+ @stability_ttl = stability_ttl
68
88
  # We cast it just in case someone would provide '10MB' or something similar
69
89
  @memory_limit = memory_limit.is_a?(String) ? memory_limit.to_i : memory_limit
70
90
  @pollings = {}
71
91
  @consumptions = {}
92
+ @join_states = {}
93
+ @instabilities = {}
72
94
 
73
95
  super()
74
96
  end
@@ -117,6 +139,27 @@ module Karafka
117
139
  RUBY
118
140
  end
119
141
 
142
+ # @see Karafka::Instrumentation::Vendors::Kubernetes::LivenessListener#on_statistics_emitted
143
+ # @param event [Karafka::Core::Monitoring::Event]
144
+ def on_statistics_emitted(event)
145
+ cgrp = event[:statistics]&.dig("cgrp")
146
+ return unless cgrp
147
+
148
+ sg_id = event[:subscription_group_id]
149
+ join_state = cgrp["join_state"]
150
+ return unless join_state
151
+
152
+ synchronize do
153
+ if join_state == "steady" || join_state == "init" || join_state == "wait-metadata"
154
+ @instabilities.delete(sg_id)
155
+ @join_states.delete(sg_id)
156
+ elsif @join_states[sg_id] != join_state
157
+ @join_states[sg_id] = join_state
158
+ @instabilities[sg_id] = monotonic_now
159
+ end
160
+ end
161
+ end
162
+
120
163
  # @param _event [Karafka::Core::Monitoring::Event]
121
164
  def on_error_occurred(_event)
122
165
  clear_consumption_tick
@@ -143,6 +186,13 @@ module Karafka
143
186
  clear_polling_tick
144
187
  end
145
188
 
189
+ # @see Karafka::Instrumentation::Vendors::Kubernetes::LivenessListener
190
+ # #on_connection_listener_after_fetch_loop
191
+ # @param event [Karafka::Core::Monitoring::Event]
192
+ def on_connection_listener_after_fetch_loop(event)
193
+ clear_instability_tracking(event[:subscription_group])
194
+ end
195
+
146
196
  private
147
197
 
148
198
  # Reports the current status to the supervisor periodically
@@ -158,10 +208,21 @@ module Karafka
158
208
  end
159
209
  end
160
210
 
161
- # @return [Integer] object id of the current fiber
162
- # @note We use fiber object id instead of thread object id to ensure fiber-safety.
163
- # Multiple fibers can run on the same thread, and using thread id would cause them
164
- # to overwrite each other's timestamps.
211
+ # @see Karafka::Instrumentation::Vendors::Kubernetes::LivenessListener
212
+ # #clear_instability_tracking
213
+ # @param subscription_group [Karafka::Routing::SubscriptionGroup, nil]
214
+ def clear_instability_tracking(subscription_group)
215
+ return unless subscription_group
216
+
217
+ sg_id = subscription_group.id
218
+
219
+ synchronize do
220
+ @instabilities.delete(sg_id)
221
+ @join_states.delete(sg_id)
222
+ end
223
+ end
224
+
225
+ # @see Karafka::Instrumentation::Vendors::Kubernetes::LivenessListener#fiber_id
165
226
  def fiber_id
166
227
  Fiber.current.object_id
167
228
  end
@@ -195,17 +256,61 @@ module Karafka
195
256
  end
196
257
 
197
258
  # Did we exceed any of the ttls
198
- # @return [String] 204 string if ok, 500 otherwise
259
+ # @return [Integer] 0 if healthy, positive error code otherwise:
260
+ # 1 - polling TTL exceeded
261
+ # 2 - consuming TTL exceeded
262
+ # 3 - memory limit exceeded
263
+ # 4 - stability TTL exceeded (stuck in non-steady join state)
264
+ # @note Reads `@pollings`, `@consumptions` and `@instabilities` without acquiring the
265
+ # mutex. This is intentional and consistent across all three: `status` is called from
266
+ # inside `periodically`, which already holds `@mutex` for scheduling, so re-entering
267
+ # `synchronize` here would deadlock. Concurrent writes from `mark_*` / `clear_*` /
268
+ # `on_statistics_emitted` are safe to observe partially on MRI (GVL atomises Hash ops).
269
+ # This is the asymmetry vs `Kubernetes::LivenessListener`, which can lock its reads
270
+ # because it has no equivalent periodic-scheduling lock. Do not "fix" this without
271
+ # resolving the periodic-scheduling lock first - JRuby is not a supported runtime.
199
272
  def status
200
273
  time = monotonic_now
201
274
 
202
275
  return 1 if @pollings.values.any? { |tick| (time - tick) > @polling_ttl }
203
276
  return 2 if @consumptions.values.any? { |tick| (time - tick) > @consuming_ttl }
204
277
  return 3 if rss_mb > @memory_limit
278
+ return 4 if @instabilities.values.any? { |start| (time - start) > stability_ttl }
205
279
 
206
280
  0
207
281
  end
208
282
 
283
+ # @return [Integer] effective stability ttl in ms. Resolved lazily (instead of in
284
+ # `#initialize`) because the default derives from the routing subscription groups,
285
+ # which may not be drawn yet when the listener is constructed in karafka.rb. By the
286
+ # time status is evaluated, routing is final.
287
+ def stability_ttl
288
+ @stability_ttl ||= default_stability_ttl
289
+ end
290
+
291
+ # @return [Integer] default stability_ttl derived from the configured
292
+ # max.poll.interval.ms, multiplied by 2 for headroom above the slowest legitimate
293
+ # rebalance phase. Uses the maximum value across all active subscription groups
294
+ # (their kafka configs already have librdkafka defaults injected), so per
295
+ # subscription group overrides set via the routing DSL are respected. Falls back to
296
+ # the root kafka config when no subscription groups are available.
297
+ def default_stability_ttl
298
+ max_poll_interval = Karafka::App
299
+ .subscription_groups
300
+ .values
301
+ .flatten
302
+ .map { |sg| sg.kafka.fetch(:"max.poll.interval.ms") }
303
+ .max
304
+
305
+ max_poll_interval ||= begin
306
+ kafka_config = Karafka::App.config.kafka.dup
307
+ Karafka::Setup::DefaultsInjector.consumer(kafka_config)
308
+ kafka_config.fetch(:"max.poll.interval.ms")
309
+ end
310
+
311
+ max_poll_interval * STABILITY_TTL_MAX_POLL_MULTIPLIER
312
+ end
313
+
209
314
  # @return [Integer] RSS in MB for the current process
210
315
  def rss_mb
211
316
  RUBY_PLATFORM.include?("linux") ? rss_mb_linux : rss_mb_macos
data/lib/karafka/pro.rb CHANGED
@@ -1,11 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # This Karafka component is a Pro component.
4
- # All of the commercial components are present in the lib/karafka/pro directory of this repository
5
- # and their usage requires commercial license agreement.
6
- #
7
- # Karafka has also commercial-friendly license, commercial support and commercial components.
8
- #
9
3
  module Karafka
10
4
  # Namespace for pro components, licensed under the commercial license agreement.
11
5
  module Pro
@@ -2,16 +2,14 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  # Basic coordinator that allows us to provide coordination objects into consumers.
9
7
  #
10
8
  # This is a wrapping layer to simplify management of work to be handled around consumption.
11
9
  #
12
10
  # @note This coordinator needs to be thread safe. Some operations are performed only in the
13
- # listener thread, but we go with thread-safe by default for all not to worry about potential
14
- # future mistakes.
11
+ # listener thread, but we go with thread-safe by default for all not to worry about
12
+ # potential future mistakes.
15
13
  class Coordinator
16
14
  extend Forwardable
17
15
  include Core::Helpers::Time
@@ -29,7 +27,8 @@ module Karafka
29
27
 
30
28
  # @param topic [Karafka::Routing::Topic]
31
29
  # @param partition [Integer]
32
- # @param pause_tracker [Karafka::TimeTrackers::Pause] pause tracker for given topic partition
30
+ # @param pause_tracker [Karafka::TimeTrackers::Pause] pause tracker for given topic
31
+ # partition
33
32
  def initialize(topic, partition, pause_tracker)
34
33
  @topic = topic
35
34
  @partition = partition
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  # Coordinators builder used to build coordinators per topic partition
9
7
  #
@@ -50,6 +48,12 @@ module Karafka
50
48
  def revoke(topic_name, partition)
51
49
  return unless @coordinators[topic_name].key?(partition)
52
50
 
51
+ # Reset the partition's pause tracker attempt count. The tracker lives in the pauses
52
+ # manager keyed by topic-partition and would otherwise be reused as-is if we reclaim this
53
+ # partition, carrying a stale retry attempt count across the rebalance (which, with DLQ,
54
+ # would send the next failure straight to the dead letter queue, skipping the retries)
55
+ @pauses_manager.revoke(@topics.find(topic_name), partition)
56
+
53
57
  # The fact that we delete here does not change the fact that the executor still holds the
54
58
  # reference to this coordinator. We delete it here, as we will no longer process any
55
59
  # new stuff with it and we may need a new coordinator if we regain this partition, but the
@@ -7,8 +7,8 @@ module Karafka
7
7
  # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
8
8
  module ConsumerGroups
9
9
  # Executors:
10
- # - run consumers code (for `#call`) or run given preparation / teardown operations when needed
11
- # from separate threads.
10
+ # - run consumers code (for `#call`) or run given preparation / teardown operations when
11
+ # needed from separate threads.
12
12
  # - they re-create consumer instances in case of partitions that were revoked and assigned
13
13
  # back.
14
14
  #
@@ -54,8 +54,8 @@ module Karafka
54
54
  @coordinator = coordinator
55
55
  end
56
56
 
57
- # Allows us to prepare the consumer in the listener thread prior to the job being send to
58
- # be scheduled. It also allows to run some code that is time sensitive and cannot wait in the
57
+ # Allows us to prepare the consumer in the listener thread prior to the job being send to be
58
+ # scheduled. It also allows to run some code that is time sensitive and cannot wait in the
59
59
  # queue as it could cause starvation.
60
60
  #
61
61
  # @param messages [Array<Karafka::Messages::Message>]
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  # Buffer for executors of a given subscription group. It wraps around the concept of building
9
7
  # and caching them, so we can re-use them instead of creating new each time.
@@ -63,8 +61,8 @@ module Karafka
63
61
  #
64
62
  # @param topic [String] topic name
65
63
  # @param partition [Integer] partition number
66
- # @return [Array<Executor, Pro::Processing::ConsumerGroups::Executor>] executors in use for this
67
- # topic + partition
64
+ # @return [Array<Executor, Pro::Processing::ConsumerGroups::Executor>] executors in use for
65
+ # this topic + partition
68
66
  def find_all(topic, partition)
69
67
  @buffer[topic][partition].values
70
68
  end
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  # Selector of appropriate topic setup based features enhancements.
9
7
  #
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  # Namespace of the Inline Insights feature "non routing" related components
9
7
  #
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  module InlineInsights
9
7
  # Listener that adds statistics to our inline tracker
@@ -2,31 +2,29 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  module InlineInsights
9
7
  # Object used to track statistics coming from librdkafka in a way that can be accessible by
10
8
  # the consumers
11
9
  #
12
- # We use a single tracker because we do not need state management here as our consumer groups
13
- # clients identified by statistics name value are unique. On top of that, having a per
14
- # process one that is a singleton allows us to use tracker easily also from other places like
15
- # filtering API etc.
10
+ # We use a single tracker because we do not need state management here as our consumer
11
+ # groups clients identified by statistics name value are unique. On top of that, having a
12
+ # per process one that is a singleton allows us to use tracker easily also from other places
13
+ # like filtering API etc.
16
14
  #
17
15
  # @note We include cache of 5 minutes for revoked partitions to compensate for cases where
18
- # when using LRJ a lost partition data would not be present anymore, however we would still
19
- # be in the processing phase. Since those metrics are published with each `poll`, regular
20
- # processing is not a subject of this issue. For LRJ we keep the reference. The only case
21
- # where this could be switched midway is when LRJ is running for an extended period of time
22
- # after the involuntary revocation. Having a time based cache instead of tracking
23
- # simplifies the design as we do not have to deal with state tracking, especially since
24
- # we would have to track also operations running in a revoked state.
16
+ # when using LRJ a lost partition data would not be present anymore, however we would
17
+ # still be in the processing phase. Since those metrics are published with each `poll`,
18
+ # regular processing is not a subject of this issue. For LRJ we keep the reference. The
19
+ # only case where this could be switched midway is when LRJ is running for an extended
20
+ # period of time after the involuntary revocation. Having a time based cache instead of
21
+ # tracking simplifies the design as we do not have to deal with state tracking, especially
22
+ # since we would have to track also operations running in a revoked state.
25
23
  #
26
24
  # @note This tracker keeps in memory data about all topics and partitions that it encounters
27
- # because in case of routing patterns, we may start getting statistics prior to registering
28
- # given topic via dynamic routing expansions. In such case we would not have insights
29
- # where they were actually available for us to use.
25
+ # because in case of routing patterns, we may start getting statistics prior to
26
+ # registering given topic via dynamic routing expansions. In such case we would not have
27
+ # insights where they were actually available for us to use.
30
28
  #
31
29
  # @note Memory usage is negligible as long as we can evict expired data. Single metrics set
32
30
  # for a single partition contains around 4KB of data. This means, that in case of an
@@ -2,10 +2,7 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
- # Consumer-group-specific job types
9
6
  module Jobs
10
7
  # The main job type. It runs the executor that triggers given topic partition messages
11
8
  # processing in an underlying consumer instance.
@@ -15,8 +12,8 @@ module Karafka
15
12
 
16
13
  self.action = :consume
17
14
 
18
- # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is suppose to run a given
19
- # job
15
+ # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is suppose
16
+ # to run a given job
20
17
  # @param messages [Karafka::Messages::Messages] karafka messages batch
21
18
  # @return [Consume]
22
19
  def initialize(executor, messages)
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  # Consumer-group-specific job types
9
7
  module Jobs
@@ -11,7 +9,8 @@ module Karafka
11
9
  class Eofed < ::Karafka::Processing::Jobs::Base
12
10
  self.action = :eofed
13
11
 
14
- # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is supposed to run the job
12
+ # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is
13
+ # supposed to run the job
15
14
  # @return [Eofed]
16
15
  def initialize(executor)
17
16
  @executor = executor
@@ -9,8 +9,8 @@ module Karafka
9
9
  class Idle < ::Karafka::Processing::Jobs::Base
10
10
  self.action = :idle
11
11
 
12
- # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is supposed to run a given
13
- # job on an active consumer
12
+ # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is
13
+ # supposed to run a given job on an active consumer
14
14
  # @return [Idle]
15
15
  def initialize(executor)
16
16
  @executor = executor
@@ -2,16 +2,14 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
- # Consumer-group-specific job types
9
6
  module Jobs
10
7
  # Job that runs the revoked operation when we lose a partition on a consumer that lost it.
11
8
  class Revoked < ::Karafka::Processing::Jobs::Base
12
9
  self.action = :revoked
13
10
 
14
- # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is supposed to run the job
11
+ # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is
12
+ # supposed to run the job
15
13
  # @return [Revoked]
16
14
  def initialize(executor)
17
15
  @executor = executor
@@ -8,8 +8,8 @@ module Karafka
8
8
  class Shutdown < ::Karafka::Processing::Jobs::Base
9
9
  self.action = :shutdown
10
10
 
11
- # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is supposed to run a given
12
- # job on an active consumer
11
+ # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is
12
+ # supposed to run a given job on an active consumer
13
13
  # @return [Shutdown]
14
14
  def initialize(executor)
15
15
  @executor = executor
@@ -2,11 +2,8 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
- # Basic partitioner for work division
9
- # It does not divide any work.
6
+ # Basic partitioner for work division. It does not divide any work.
10
7
  class Partitioner
11
8
  # @param subscription_group [Karafka::Routing::SubscriptionGroup] subscription group
12
9
  def initialize(subscription_group)
@@ -15,8 +12,8 @@ module Karafka
15
12
 
16
13
  # @param _topic [String] topic name
17
14
  # @param messages [Array<Karafka::Messages::Message>] karafka messages
18
- # @param _coordinator [Karafka::Processing::ConsumerGroups::Coordinator] processing coordinator that will
19
- # be used with those messages
15
+ # @param _coordinator [Karafka::Processing::ConsumerGroups::Coordinator] processing
16
+ # coordinator that will be used with those messages
20
17
  # @yieldparam [Integer] group id
21
18
  # @yieldparam [Array<Karafka::Messages::Message>] karafka messages
22
19
  def call(_topic, messages, _coordinator)
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  module Strategies
9
7
  # ActiveJob strategy to cooperate with the DLQ.
@@ -28,6 +26,11 @@ module Karafka
28
26
  if coordinator.success?
29
27
  # Do NOT commit offsets, they are comitted after each job in the AJ consumer.
30
28
  coordinator.pause_tracker.reset
29
+ # Process-critical errors are never dispatched to the DLQ regardless of the retries
30
+ # state: the retry pause protects the partition during the critical shutdown and
31
+ # the failed batch is redelivered after the restart
32
+ elsif critical_error?(coordinator.consumption(self).cause)
33
+ retry_after_pause
31
34
  elsif coordinator.pause_tracker.attempt <= topic.dead_letter_queue.max_retries
32
35
  retry_after_pause
33
36
  else
@@ -2,12 +2,10 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  module Strategies
9
- # ActiveJob enabled
10
- # Manual offset management enabled
7
+ # - ActiveJob enabled
8
+ # - Manual offset management enabled
11
9
  #
12
10
  # This is the default AJ strategy since AJ cannot be used without MOM
13
11
  module AjMom
@@ -2,12 +2,10 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
- # Our processing patterns differ depending on various features configurations
9
- # In this namespace we collect strategies for particular feature combinations to simplify the
10
- # design. Based on features combinations we can then select handling strategy for a given case.
6
+ # Our processing patterns differ depending on various features configurations. In this
7
+ # namespace we collect strategies for particular feature combinations to simplify the design.
8
+ # Based on features combinations we can then select handling strategy for a given case.
11
9
  #
12
10
  # @note The lack of common code here is intentional. It would get complex if there would be
13
11
  # any type of composition, so each strategy is expected to be self-sufficient
@@ -2,13 +2,12 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  module Strategies
9
- # No features enabled.
10
- # No manual offset management
11
- # No long running jobs
7
+ # No features enabled:
8
+ # - No manual offset management
9
+ # - No long running jobs
10
+ #
12
11
  # Nothing. Just standard, automatic flow
13
12
  module Default
14
13
  include Base
@@ -59,9 +58,18 @@ module Karafka
59
58
  # In case like this we ignore marking
60
59
  return true if seek_offset.nil?
61
60
  # Ignore double markings of the same offset
61
+ # Only this exact re-mark is skipped - marking genuinely older offsets is intentionally
62
+ # allowed and rewinds the seek offset for reprocessing (see #2432)
62
63
  return true if (seek_offset - 1) == message.offset
63
64
  return false if revoked?
64
- return revoked? unless client.mark_as_consumed(message)
65
+ unless client.mark_as_consumed(message)
66
+ # Evaluate ownership for its coordinator-revoking side effect - all marking
67
+ # failure causes are ownership related. The failure itself always reports false:
68
+ # the offset was not stored, regardless of the current ownership state
69
+ revoked?
70
+
71
+ return false
72
+ end
65
73
 
66
74
  self.seek_offset = message.offset + 1
67
75
 
@@ -78,10 +86,19 @@ module Karafka
78
86
  # In case like this we ignore marking
79
87
  return true if seek_offset.nil?
80
88
  # Ignore double markings of the same offset
89
+ # Only this exact re-mark is skipped - marking genuinely older offsets is intentionally
90
+ # allowed and rewinds the seek offset for reprocessing (see #2432)
81
91
  return true if (seek_offset - 1) == message.offset
82
92
  return false if revoked?
83
93
 
84
- return revoked? unless client.mark_as_consumed!(message)
94
+ unless client.mark_as_consumed!(message)
95
+ # Evaluate ownership for its coordinator-revoking side effect - all marking
96
+ # failure causes are ownership related. The failure itself always reports false:
97
+ # the offset was not committed, regardless of the current ownership state
98
+ revoked?
99
+
100
+ return false
101
+ end
85
102
 
86
103
  self.seek_offset = message.offset + 1
87
104
 
@@ -91,17 +108,27 @@ module Karafka
91
108
  # Triggers an async offset commit
92
109
  #
93
110
  # @param async [Boolean] should we use async (default) or sync commit
94
- # @return [Boolean] true if we still own the partition.
111
+ # @return [Boolean] true if the commit went through and we still owned the partition
112
+ # at that time, false otherwise. False means the offsets were not committed - the
113
+ # caller can retry (the offsets stay stored client-side) or treat the partition as
114
+ # fenced.
95
115
  # @note Due to its async nature, this may not fully represent the offset state in some
96
116
  # edge cases (like for example going beyond max.poll.interval)
117
+ # @note A commit with no offsets pending is a client-side no-op reported as success
118
+ # without broker validation - the marking result is the fence for that case.
97
119
  def commit_offsets(async: true)
98
120
  # Do not commit if we already lost the assignment
99
121
  return false if revoked?
100
122
  return true if client.commit_offsets(async: async)
101
123
 
102
- # This will once more check the librdkafka revocation status and will revoke the
103
- # coordinator in case it was not revoked
124
+ # Evaluate ownership once more for its coordinator-revoking side effect: all
125
+ # commit failure causes the client reports as false are ownership related. The
126
+ # failed commit itself always reports false - its value must not depend on the
127
+ # revocation state, which may not yet be locally visible (or may indicate a
128
+ # retained partition under a cooperative rebalance generation bump)
104
129
  revoked?
130
+
131
+ false
105
132
  end
106
133
 
107
134
  # Triggers a synchronous offsets commit to Kafka
@@ -138,7 +165,10 @@ module Karafka
138
165
 
139
166
  # Mark job as successful
140
167
  coordinator.success!(self)
141
- rescue => e
168
+ # Failure recording must be class-agnostic: an unrecorded non-StandardError would
169
+ # leave the consumption result in its default (successful) state and the after-consume
170
+ # flow would mark the failed batch as consumed instead of engaging the retry
171
+ rescue Exception => e
142
172
  coordinator.failure!(self, e)
143
173
 
144
174
  # Re-raise so reported in the consumer
@@ -187,8 +217,8 @@ module Karafka
187
217
  end
188
218
 
189
219
  # We need to always un-pause the processing in case we have lost a given partition.
190
- # Otherwise the underlying librdkafka would not know we may want to continue processing and
191
- # the pause could in theory last forever
220
+ # Otherwise the underlying librdkafka would not know we may want to continue processing
221
+ # and the pause could in theory last forever
192
222
  def handle_revoked
193
223
  resume
194
224