karafka 2.6.0.beta1 → 2.6.0.rc1

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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -27
  3. data/certs/expired.txt +2 -0
  4. data/config/locales/errors.yml +5 -0
  5. data/karafka.gemspec +1 -1
  6. data/lib/active_job/queue_adapters/karafka_adapter.rb +1 -1
  7. data/lib/karafka/active_job/current_attributes/loading.rb +1 -1
  8. data/lib/karafka/active_job/current_attributes.rb +4 -2
  9. data/lib/karafka/active_job/dispatcher.rb +1 -2
  10. data/lib/karafka/admin/consumer_groups.rb +8 -2
  11. data/lib/karafka/admin/replication.rb +6 -3
  12. data/lib/karafka/admin/topics.rb +32 -12
  13. data/lib/karafka/admin.rb +155 -59
  14. data/lib/karafka/app.rb +1 -1
  15. data/lib/karafka/base_consumer.rb +33 -5
  16. data/lib/karafka/cli/console.rb +0 -1
  17. data/lib/karafka/cli/help.rb +0 -1
  18. data/lib/karafka/cli/info.rb +0 -1
  19. data/lib/karafka/cli/install.rb +0 -1
  20. data/lib/karafka/cli/server.rb +0 -1
  21. data/lib/karafka/cli/swarm.rb +0 -1
  22. data/lib/karafka/cli.rb +2 -2
  23. data/lib/karafka/connection/client.rb +44 -7
  24. data/lib/karafka/connection/listener.rb +3 -3
  25. data/lib/karafka/connection/manager.rb +0 -1
  26. data/lib/karafka/connection/pauses_manager.rb +17 -0
  27. data/lib/karafka/connection/proxy.rb +45 -0
  28. data/lib/karafka/connection/status.rb +0 -1
  29. data/lib/karafka/instrumentation/assignments_tracker.rb +34 -0
  30. data/lib/karafka/instrumentation/callbacks/consumer_groups/decorator.rb +19 -0
  31. data/lib/karafka/instrumentation/callbacks/consumer_groups/error.rb +0 -1
  32. data/lib/karafka/instrumentation/callbacks/consumer_groups/statistics.rb +3 -2
  33. data/lib/karafka/instrumentation/critical_errors_listener.rb +85 -0
  34. data/lib/karafka/instrumentation/logger_listener.rb +2 -0
  35. data/lib/karafka/instrumentation/monitor.rb +2 -3
  36. data/lib/karafka/instrumentation/vendors/appsignal/errors_listener.rb +0 -1
  37. data/lib/karafka/instrumentation/vendors/appsignal/metrics_listener.rb +0 -1
  38. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +4 -3
  39. data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +0 -2
  40. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +181 -18
  41. data/lib/karafka/pro/admin/recovery/errors.rb +0 -1
  42. data/lib/karafka/pro/admin/recovery.rb +6 -4
  43. data/lib/karafka/pro/base_consumer.rb +5 -5
  44. data/lib/karafka/pro/cleaner/messages/metadata.rb +0 -1
  45. data/lib/karafka/pro/cli/parallel_segments/base.rb +1 -2
  46. data/lib/karafka/pro/connection/manager.rb +7 -4
  47. data/lib/karafka/pro/encryption/messages/middleware.rb +5 -0
  48. data/lib/karafka/pro/encryption/messages/parser.rb +4 -0
  49. data/lib/karafka/pro/instrumentation/callbacks/consumer_groups/decorator.rb +63 -0
  50. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/compensator.rb +120 -0
  51. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/fetcher.rb +88 -0
  52. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/refresher.rb +185 -0
  53. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/registry.rb +102 -0
  54. data/lib/karafka/pro/instrumentation/performance_tracker.rb +40 -6
  55. data/lib/karafka/pro/iterator/tpl_builder.rb +27 -7
  56. data/lib/karafka/pro/iterator.rb +22 -7
  57. data/lib/karafka/pro/loader.rb +12 -0
  58. data/lib/karafka/pro/processing/consumer_groups/adaptive_iterator/consumer.rb +0 -3
  59. data/lib/karafka/pro/processing/consumer_groups/adaptive_iterator/tracker.rb +2 -4
  60. data/lib/karafka/pro/processing/consumer_groups/collapser.rb +6 -9
  61. data/lib/karafka/pro/processing/consumer_groups/coordinator.rb +1 -4
  62. data/lib/karafka/pro/processing/consumer_groups/coordinators/errors_tracker.rb +0 -3
  63. data/lib/karafka/pro/processing/consumer_groups/coordinators/filters_applier.rb +21 -10
  64. data/lib/karafka/pro/processing/consumer_groups/coordinators/virtual_offset_manager.rb +4 -4
  65. data/lib/karafka/pro/processing/consumer_groups/expansions_selector.rb +0 -3
  66. data/lib/karafka/pro/processing/consumer_groups/filters/actions.rb +84 -0
  67. data/lib/karafka/pro/processing/consumer_groups/filters/base.rb +5 -6
  68. data/lib/karafka/pro/processing/consumer_groups/filters/delayer.rb +2 -5
  69. data/lib/karafka/pro/processing/consumer_groups/filters/expirer.rb +1 -4
  70. data/lib/karafka/pro/processing/consumer_groups/filters/inline_insights_delayer.rb +1 -4
  71. data/lib/karafka/pro/processing/consumer_groups/filters/throttler.rb +2 -5
  72. data/lib/karafka/pro/processing/consumer_groups/filters/virtual_limiter.rb +0 -3
  73. data/lib/karafka/pro/processing/consumer_groups/jobs/consume_non_blocking.rb +2 -3
  74. data/lib/karafka/pro/processing/consumer_groups/jobs/eofed_non_blocking.rb +0 -2
  75. data/lib/karafka/pro/processing/consumer_groups/jobs/periodic.rb +2 -3
  76. data/lib/karafka/pro/processing/consumer_groups/jobs/periodic_non_blocking.rb +0 -2
  77. data/lib/karafka/pro/processing/consumer_groups/jobs/revoked_non_blocking.rb +0 -3
  78. data/lib/karafka/pro/processing/consumer_groups/jobs_builder.rb +5 -4
  79. data/lib/karafka/pro/processing/consumer_groups/offset_metadata/consumer.rb +3 -7
  80. data/lib/karafka/pro/processing/consumer_groups/offset_metadata/fetcher.rb +8 -10
  81. data/lib/karafka/pro/processing/consumer_groups/offset_metadata/listener.rb +0 -3
  82. data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/base.rb +3 -5
  83. data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/default.rb +0 -3
  84. data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/mom.rb +0 -3
  85. data/lib/karafka/pro/processing/consumer_groups/partitioner.rb +2 -5
  86. data/lib/karafka/pro/processing/consumer_groups/periodic_job/consumer.rb +2 -5
  87. data/lib/karafka/pro/processing/consumer_groups/piping/consumer.rb +2 -5
  88. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_lrj_mom.rb +10 -9
  89. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_lrj_mom_vp.rb +11 -10
  90. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_mom.rb +0 -3
  91. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_mom_vp.rb +0 -3
  92. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_lrj_mom.rb +8 -7
  93. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_lrj_mom_vp.rb +9 -8
  94. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_mom.rb +5 -8
  95. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_mom_vp.rb +3 -6
  96. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_lrj_mom.rb +4 -7
  97. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_lrj_mom_vp.rb +6 -9
  98. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_mom.rb +3 -7
  99. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_mom_vp.rb +4 -8
  100. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/lrj_mom.rb +3 -6
  101. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/lrj_mom_vp.rb +4 -7
  102. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/mom.rb +2 -5
  103. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/mom_vp.rb +3 -6
  104. data/lib/karafka/pro/processing/consumer_groups/strategies/base.rb +0 -3
  105. data/lib/karafka/pro/processing/consumer_groups/strategies/default.rb +56 -33
  106. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/default.rb +27 -14
  107. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr.rb +0 -3
  108. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj.rb +4 -7
  109. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_mom.rb +5 -8
  110. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_mom_vp.rb +5 -8
  111. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_vp.rb +3 -7
  112. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_mom.rb +0 -3
  113. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_mom_vp.rb +0 -3
  114. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_vp.rb +0 -3
  115. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj.rb +2 -5
  116. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_mom.rb +0 -3
  117. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_mom_vp.rb +0 -3
  118. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_vp.rb +3 -6
  119. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/mom.rb +0 -3
  120. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/mom_vp.rb +3 -6
  121. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/vp.rb +65 -8
  122. data/lib/karafka/pro/processing/consumer_groups/strategies/ftr/default.rb +0 -4
  123. data/lib/karafka/pro/processing/consumer_groups/strategies/ftr/vp.rb +2 -5
  124. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/default.rb +0 -3
  125. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr.rb +3 -6
  126. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_mom.rb +4 -7
  127. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_mom_vp.rb +4 -8
  128. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_vp.rb +3 -6
  129. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/mom.rb +2 -5
  130. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/mom_vp.rb +3 -7
  131. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/vp.rb +2 -5
  132. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/default.rb +0 -3
  133. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/ftr.rb +0 -3
  134. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/ftr_vp.rb +0 -3
  135. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/vp.rb +0 -3
  136. data/lib/karafka/pro/processing/consumer_groups/strategies/vp/default.rb +11 -6
  137. data/lib/karafka/pro/processing/consumer_groups/strategy_selector.rb +0 -3
  138. data/lib/karafka/pro/processing/consumer_groups/subscription_groups_coordinator.rb +0 -3
  139. data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/balanced.rb +0 -3
  140. data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/base.rb +2 -4
  141. data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/consistent.rb +0 -3
  142. data/lib/karafka/pro/processing/jobs_queue.rb +4 -1
  143. data/lib/karafka/pro/processing/schedulers/base.rb +4 -2
  144. data/lib/karafka/pro/processing/schedulers/default.rb +2 -2
  145. data/lib/karafka/pro/recurring_tasks/consumer.rb +9 -2
  146. data/lib/karafka/pro/recurring_tasks/contracts/config.rb +0 -1
  147. data/lib/karafka/pro/recurring_tasks/executor.rb +25 -13
  148. data/lib/karafka/pro/routing/features/consumer_groups/direct_assignments/subscription_group.rb +0 -2
  149. data/lib/karafka/pro/routing/features/consumer_groups/long_running_job.rb +0 -2
  150. data/lib/karafka/pro/routing/features/consumer_groups/multiplexing/proxy.rb +2 -2
  151. data/lib/karafka/pro/routing/features/consumer_groups/non_blocking_job.rb +0 -2
  152. data/lib/karafka/pro/routing/features/consumer_groups/offset_metadata/topic.rb +0 -6
  153. data/lib/karafka/pro/routing/features/consumer_groups/parallel_segments/consumer_group.rb +5 -4
  154. data/lib/karafka/pro/routing/features/consumer_groups/patterns/config.rb +0 -6
  155. data/lib/karafka/pro/routing/features/consumer_groups/patterns/consumer_group.rb +2 -1
  156. data/lib/karafka/pro/routing/features/consumer_groups/patterns/contracts/pattern.rb +0 -1
  157. data/lib/karafka/pro/routing/features/consumer_groups/patterns/detector.rb +2 -1
  158. data/lib/karafka/pro/routing/features/consumer_groups/patterns/pattern.rb +2 -1
  159. data/lib/karafka/pro/routing/features/consumer_groups/patterns/topics.rb +8 -8
  160. data/lib/karafka/pro/routing/features/consumer_groups/periodic_job/topic.rb +5 -5
  161. data/lib/karafka/pro/routing/features/consumer_groups/recurring_tasks/builder.rb +33 -13
  162. data/lib/karafka/pro/routing/features/consumer_groups/scheduled_messages/builder.rb +52 -28
  163. data/lib/karafka/pro/routing/features/consumer_groups/scheduled_messages/topic.rb +2 -1
  164. data/lib/karafka/pro/routing/features/consumer_groups/virtual_partitions/topic.rb +6 -5
  165. data/lib/karafka/pro/scheduled_messages/consumer.rb +7 -1
  166. data/lib/karafka/pro/scheduled_messages/schema_validator.rb +6 -1
  167. data/lib/karafka/pro/swarm/liveness_listener.rb +118 -13
  168. data/lib/karafka/pro.rb +0 -6
  169. data/lib/karafka/processing/consumer_groups/coordinator.rb +4 -5
  170. data/lib/karafka/processing/consumer_groups/coordinators_buffer.rb +6 -2
  171. data/lib/karafka/processing/consumer_groups/executor.rb +4 -4
  172. data/lib/karafka/processing/consumer_groups/executors_buffer.rb +2 -4
  173. data/lib/karafka/processing/consumer_groups/expansions_selector.rb +0 -2
  174. data/lib/karafka/processing/consumer_groups/inline_insights/consumer.rb +0 -2
  175. data/lib/karafka/processing/consumer_groups/inline_insights/listener.rb +0 -2
  176. data/lib/karafka/processing/consumer_groups/inline_insights/tracker.rb +14 -16
  177. data/lib/karafka/processing/consumer_groups/jobs/consume.rb +2 -5
  178. data/lib/karafka/processing/consumer_groups/jobs/eofed.rb +2 -3
  179. data/lib/karafka/processing/consumer_groups/jobs/idle.rb +2 -2
  180. data/lib/karafka/processing/consumer_groups/jobs/revoked.rb +2 -4
  181. data/lib/karafka/processing/consumer_groups/jobs/shutdown.rb +2 -2
  182. data/lib/karafka/processing/consumer_groups/partitioner.rb +3 -6
  183. data/lib/karafka/processing/consumer_groups/strategies/aj_dlq_mom.rb +5 -2
  184. data/lib/karafka/processing/consumer_groups/strategies/aj_mom.rb +2 -4
  185. data/lib/karafka/processing/consumer_groups/strategies/base.rb +3 -5
  186. data/lib/karafka/processing/consumer_groups/strategies/default.rb +43 -13
  187. data/lib/karafka/processing/consumer_groups/strategies/dlq.rb +5 -2
  188. data/lib/karafka/processing/consumer_groups/strategies/dlq_mom.rb +5 -2
  189. data/lib/karafka/processing/consumer_groups/strategies/mom.rb +2 -3
  190. data/lib/karafka/processing/consumer_groups/strategy_selector.rb +0 -2
  191. data/lib/karafka/processing/coordinator.rb +0 -2
  192. data/lib/karafka/processing/coordinators_buffer.rb +0 -2
  193. data/lib/karafka/processing/partitioner.rb +0 -2
  194. data/lib/karafka/processing/strategy_selector.rb +0 -2
  195. data/lib/karafka/processing/worker.rb +14 -6
  196. data/lib/karafka/processing/workers_pool.rb +21 -4
  197. data/lib/karafka/railtie.rb +0 -1
  198. data/lib/karafka/routing/contracts/routing.rb +2 -2
  199. data/lib/karafka/routing/features/consumer_groups/active_job/topic.rb +6 -5
  200. data/lib/karafka/routing/features/consumer_groups/manual_offset_management/topic.rb +2 -2
  201. data/lib/karafka/routing/features/deserializers/topic.rb +0 -1
  202. data/lib/karafka/routing/subscription_group.rb +1 -1
  203. data/lib/karafka/routing/topic.rb +4 -4
  204. data/lib/karafka/routing/topics.rb +21 -1
  205. data/lib/karafka/server.rb +9 -0
  206. data/lib/karafka/setup/config.rb +54 -10
  207. data/lib/karafka/setup/config_proxy.rb +2 -1
  208. data/lib/karafka/setup/contracts/config.rb +14 -0
  209. data/lib/karafka/swarm/node.rb +2 -2
  210. data/lib/karafka/version.rb +1 -2
  211. data/lib/karafka.rb +2 -2
  212. metadata +12 -4
data/lib/karafka/admin.rb CHANGED
@@ -3,8 +3,10 @@
3
3
  module Karafka
4
4
  # Admin actions that we can perform via Karafka on our Kafka cluster
5
5
  #
6
- # @note It always initializes a new admin instance as we want to ensure it is always closed
7
- # Since admin actions are not performed that often, that should be ok.
6
+ # @note Each operation initializes its own dedicated instance as we want to ensure it is
7
+ # always closed. Since admin actions are not performed that often, that should be ok. When
8
+ # an external client is provided, it is reused instead and its lifecycle stays fully with
9
+ # its owner.
8
10
  #
9
11
  # @note By default it uses the primary defined cluster. For multi-cluster operations, create
10
12
  # an Admin instance with custom kafka configuration:
@@ -22,34 +24,80 @@ module Karafka
22
24
  admin_kafka: %i[admin kafka]
23
25
  )
24
26
 
25
- # Custom kafka configuration for this admin instance
26
- # @return [Hash] custom kafka settings to merge with defaults
27
- attr_reader :custom_kafka
28
-
29
27
  # Creates a new Admin instance
30
28
  #
31
29
  # @param kafka [Hash] custom kafka configuration to merge with app defaults.
32
30
  # Useful for multi-cluster operations where you want to target a different cluster.
31
+ # @param external_client [Object, nil] active rdkafka client (raw, wrapped with
32
+ # `Karafka::Connection::Proxy` or a `Karafka::Connection::Client` of a running consumer)
33
+ # on which admin operations should run, instead of each operation creating its own
34
+ # short-lived instance. Routing is capability based: rdkafka admin instances are used by
35
+ # admin-based operations (`with_admin` and everything built on top of it, e.g.
36
+ # `cluster_info`), any other external client is used by consumer-based operations
37
+ # (`with_consumer` and everything built on top of it). The lifecycle of an external
38
+ # client belongs fully to its owner: it is never configured, started or closed here and
39
+ # all operations run within its identity, including its `group.id`. This is a low-level
40
+ # internal API: the caller is responsible for providing a client capable of the invoked
41
+ # operations and for invoking only operations that are safe to run on a live client.
42
+ #
43
+ # @note Raw and proxied rdkafka instances are resolved once at construction, so the admin
44
+ # instance should not outlive them. `Karafka::Connection::Client` instances are resolved
45
+ # on each use instead, so the admin instance follows such a client across the underlying
46
+ # connection recovery resets.
33
47
  #
34
48
  # @example Create admin for a different cluster
35
49
  # admin = Karafka::Admin.new(kafka: { 'bootstrap.servers': 'other-cluster:9092' })
36
50
  # admin.cluster_info
37
- def initialize(kafka: {})
51
+ #
52
+ # @example Read lags of a running consumer via its own client connection
53
+ # admin = Karafka::Admin.new(external_client: client)
54
+ # admin.read_lags_with_offsets({ 'my-group' => ['events'] })
55
+ def initialize(kafka: {}, external_client: nil)
38
56
  @custom_kafka = kafka
57
+ @external_client = nil
58
+ @external_admin = false
59
+ @external_consumer = false
60
+
61
+ return unless external_client
62
+
63
+ # A running consumer connection client is kept as-is and its current handle is resolved
64
+ # on each use: such clients swap their underlying rdkafka instance across recovery
65
+ # resets, so a handle resolved once at construction could go stale while the client
66
+ # itself remains fully operational
67
+ if external_client.is_a?(Karafka::Connection::Client)
68
+ @external_client = external_client
69
+ @external_consumer = true
70
+
71
+ return
72
+ end
73
+
74
+ # Raw and proxied rdkafka instances are wrapped upfront - the proxy itself prevents
75
+ # double wrapping
76
+ @external_client = Karafka::Connection::Proxy.new(external_client)
77
+
78
+ # Capability based routing resolved once upfront: rdkafka admin instances serve
79
+ # admin-based operations, any other external client is assumed consumer capable
80
+ if @external_client.wrapped.is_a?(Rdkafka::Admin)
81
+ @external_admin = true
82
+ else
83
+ @external_consumer = true
84
+ end
39
85
  end
40
86
 
41
87
  # No-op close to normalize the API surface.
42
88
  #
43
- # Each admin operation currently opens and closes its own underlying rdkafka admin instance
44
- # internally, so there is nothing to release at the `Karafka::Admin` level right now. This
45
- # method exists so that callers who hold an instance and call `#close` on it (matching the
46
- # pattern of other closeable resources) do not raise `NoMethodError`.
89
+ # Each admin operation opens and closes its own dedicated rdkafka instance internally,
90
+ # while external clients are owned and closed by their providers, so there is nothing to
91
+ # release at the `Karafka::Admin` level right now. This method exists so that callers who
92
+ # hold an instance and call `#close` on it (matching the pattern of other closeable
93
+ # resources) do not raise `NoMethodError`.
47
94
  #
48
- # In the future, `Karafka::Admin` is planned to be refactored to reuse a single rdkafka admin
49
- # instance across multiple operations rather than creating and tearing one down per call. When
50
- # that happens, this method will need to release that shared instance. The no-op is here now
51
- # so that all callers are already written against the correct API and require no changes when
52
- # the real implementation lands.
95
+ # In the future, `Karafka::Admin` is planned to be refactored to reuse a single owned
96
+ # rdkafka admin instance across multiple operations rather than creating and tearing one
97
+ # down per call. When that happens, this method will need to release that shared owned
98
+ # instance - though never an external client. The no-op is here now so that all callers are
99
+ # already written against the correct API and require no changes when the real
100
+ # implementation lands.
53
101
  def close
54
102
  end
55
103
 
@@ -257,7 +305,7 @@ module Karafka
257
305
  # @param settings [Hash] kafka extra settings (optional)
258
306
  # @see Topics#read
259
307
  def read_topic(name, partition, count, start_offset = -1, settings = {})
260
- Topics.new(kafka: @custom_kafka).read(name, partition, count, start_offset, settings)
308
+ topics_admin.read(name, partition, count, start_offset, settings)
261
309
  end
262
310
 
263
311
  # @param name [String] topic name
@@ -266,47 +314,47 @@ module Karafka
266
314
  # @param topic_config [Hash] topic config details
267
315
  # @see Topics#create
268
316
  def create_topic(name, partitions, replication_factor, topic_config = {})
269
- Topics.new(kafka: @custom_kafka).create(name, partitions, replication_factor, topic_config)
317
+ topics_admin.create(name, partitions, replication_factor, topic_config)
270
318
  end
271
319
 
272
320
  # @param name [String] topic name
273
321
  # @see Topics#delete
274
322
  def delete_topic(name)
275
- Topics.new(kafka: @custom_kafka).delete(name)
323
+ topics_admin.delete(name)
276
324
  end
277
325
 
278
326
  # @param name [String] topic name
279
327
  # @param partitions [Integer] total number of partitions we expect to end up with
280
328
  # @see Topics#create_partitions
281
329
  def create_partitions(name, partitions)
282
- Topics.new(kafka: @custom_kafka).create_partitions(name, partitions)
330
+ topics_admin.create_partitions(name, partitions)
283
331
  end
284
332
 
285
333
  # @param name_or_hash [String, Symbol, Hash] topic name or hash with topics and partitions
286
334
  # @param partition [Integer, nil] partition (nil when using hash format)
287
335
  # @see Topics#read_watermark_offsets
288
336
  def read_watermark_offsets(name_or_hash, partition = nil)
289
- Topics.new(kafka: @custom_kafka).read_watermark_offsets(name_or_hash, partition)
337
+ topics_admin.read_watermark_offsets(name_or_hash, partition)
290
338
  end
291
339
 
292
340
  # @param topic_partition_offsets [Hash{String => Array<Hash>}] topics with partition specs
293
341
  # @param isolation_level [Integer, nil] optional isolation level constant
294
342
  # @see Topics#read_partition_offsets
295
343
  def read_partition_offsets(topic_partition_offsets, isolation_level: nil)
296
- Topics.new(kafka: @custom_kafka).read_partition_offsets(topic_partition_offsets, isolation_level: isolation_level)
344
+ topics_admin.read_partition_offsets(topic_partition_offsets, isolation_level: isolation_level)
297
345
  end
298
346
 
299
347
  # @param topic_name [String] name of the topic we're interested in
300
348
  # @see Topics#info
301
349
  def topic_info(topic_name)
302
- Topics.new(kafka: @custom_kafka).info(topic_name)
350
+ topics_admin.info(topic_name)
303
351
  end
304
352
 
305
353
  # @param group_id [String] group for which we want to move offsets
306
354
  # @param topics_with_partitions_and_offsets [Hash] hash with topics and settings
307
355
  # @see ConsumerGroups#seek
308
356
  def seek_consumer_group(group_id, topics_with_partitions_and_offsets)
309
- ConsumerGroups.new(kafka: @custom_kafka).seek(
357
+ consumer_groups_admin.seek(
310
358
  group_id,
311
359
  topics_with_partitions_and_offsets
312
360
  )
@@ -317,7 +365,7 @@ module Karafka
317
365
  # @param topics [Array<String>] topics for which we want to copy offsets
318
366
  # @see ConsumerGroups#copy
319
367
  def copy_consumer_group(previous_name, new_name, topics)
320
- ConsumerGroups.new(kafka: @custom_kafka).copy(previous_name, new_name, topics)
368
+ consumer_groups_admin.copy(previous_name, new_name, topics)
321
369
  end
322
370
 
323
371
  # @param previous_name [String] old consumer group name
@@ -326,7 +374,7 @@ module Karafka
326
374
  # @param delete_previous [Boolean] should we delete previous consumer group after rename
327
375
  # @see ConsumerGroups#rename
328
376
  def rename_consumer_group(previous_name, new_name, topics, delete_previous: true)
329
- ConsumerGroups.new(kafka: @custom_kafka).rename(
377
+ consumer_groups_admin.rename(
330
378
  previous_name,
331
379
  new_name,
332
380
  topics,
@@ -337,13 +385,13 @@ module Karafka
337
385
  # @param group_id [String] group name
338
386
  # @see ConsumerGroups#delete
339
387
  def delete_consumer_group(group_id)
340
- ConsumerGroups.new(kafka: @custom_kafka).delete(group_id)
388
+ consumer_groups_admin.delete(group_id)
341
389
  end
342
390
 
343
391
  # @param group_id [String] group id to trigger rebalance for
344
392
  # @see ConsumerGroups#trigger_rebalance
345
393
  def trigger_rebalance(group_id)
346
- ConsumerGroups.new(kafka: @custom_kafka).trigger_rebalance(group_id)
394
+ consumer_groups_admin.trigger_rebalance(group_id)
347
395
  end
348
396
 
349
397
  # @param groups_with_topics [Hash{String => Array<String>}] hash with group
@@ -351,7 +399,7 @@ module Karafka
351
399
  # @param active_topics_only [Boolean] if set to false, will select also inactive topics
352
400
  # @see ConsumerGroups#read_lags_with_offsets
353
401
  def read_lags_with_offsets(groups_with_topics = {}, active_topics_only: true)
354
- ConsumerGroups.new(kafka: @custom_kafka).read_lags_with_offsets(
402
+ consumer_groups_admin.read_lags_with_offsets(
355
403
  groups_with_topics,
356
404
  active_topics_only: active_topics_only
357
405
  )
@@ -362,7 +410,7 @@ module Karafka
362
410
  # @param brokers [Hash, nil] optional manual broker assignments per partition
363
411
  # @see Replication#plan
364
412
  def plan_topic_replication(topic:, replication_factor:, brokers: nil)
365
- Replication.new(kafka: @custom_kafka).plan(
413
+ Replication.new(kafka: @custom_kafka, external_client: @external_client).plan(
366
414
  topic: topic,
367
415
  to: replication_factor,
368
416
  brokers: brokers
@@ -381,53 +429,101 @@ module Karafka
381
429
  #
382
430
  # @note We always ship and yield a proxied consumer because admin API performance is not
383
431
  # that relevant. That is, there are no high frequency calls that would have to be delegated
432
+ #
433
+ # @note When an external client is present, it is yielded wrapped with
434
+ # `Karafka::Connection::Proxy` (which unwraps pre-proxied clients, so no double wrapping
435
+ # occurs) and its lifecycle is not managed here in any way: no oauth binding, no start and
436
+ # no closing. `settings` are
437
+ # ignored as the external instance is already configured and operations run within its
438
+ # identity, including its `group.id`. External rdkafka admin instances are not used here
439
+ # as they are not capable of consumer operations - they are used by `#with_admin` instead.
384
440
  def with_consumer(settings = {})
385
- bind_id = SecureRandom.uuid
441
+ return yield(external_client_proxy) if @external_consumer
386
442
 
387
- consumer = config(:consumer, settings).consumer(native_kafka_auto_start: false)
388
- bind_oauth(bind_id, consumer)
443
+ bind_id = SecureRandom.uuid
444
+ consumer = nil
389
445
 
390
- consumer.start
391
- proxy = Karafka::Connection::Proxy.new(consumer)
392
- yield(proxy)
393
- ensure
394
- # Always unsubscribe consumer just to be sure, that no metadata requests are running
395
- # when we close the consumer. This in theory should prevent from some race-conditions
396
- # that originate from librdkafka
397
446
  begin
398
- consumer&.unsubscribe
399
- # Ignore any errors and continue to close consumer despite them
400
- rescue Rdkafka::RdkafkaError
401
- nil
447
+ consumer = config(:consumer, settings).consumer(native_kafka_auto_start: false)
448
+ bind_oauth(bind_id, consumer)
449
+
450
+ consumer.start
451
+ proxy = Karafka::Connection::Proxy.new(consumer)
452
+ yield(proxy)
453
+ ensure
454
+ # Always unsubscribe consumer just to be sure, that no metadata requests are running
455
+ # when we close the consumer. This in theory should prevent from some race-conditions
456
+ # that originate from librdkafka
457
+ begin
458
+ consumer&.unsubscribe
459
+ # Ignore any errors and continue to close consumer despite them
460
+ rescue Rdkafka::RdkafkaError
461
+ nil
462
+ end
463
+
464
+ consumer&.close
465
+
466
+ unbind_oauth(bind_id)
402
467
  end
403
-
404
- consumer&.close
405
-
406
- unbind_oauth(bind_id)
407
468
  end
408
469
 
409
470
  # Creates admin instance and yields it. After usage it closes the admin instance
471
+ #
472
+ # @note When the external client is an rdkafka admin instance, it is yielded wrapped with
473
+ # `Karafka::Connection::Proxy` (which unwraps pre-proxied clients, so no double wrapping
474
+ # occurs) and its lifecycle is not managed here in any way, same as with `#with_consumer`.
475
+ # External
476
+ # clients of other types (consumers, producers) are not capable of admin operations, thus
477
+ # a dedicated admin instance is created for them as usual.
410
478
  def with_admin
479
+ return yield(external_client_proxy) if @external_admin
480
+
411
481
  bind_id = SecureRandom.uuid
482
+ admin = nil
412
483
 
413
- admin = config(:producer, {}).admin(
414
- native_kafka_auto_start: false,
415
- native_kafka_poll_timeout_ms: self.class.poll_timeout
416
- )
484
+ begin
485
+ admin = config(:producer, {}).admin(
486
+ native_kafka_auto_start: false,
487
+ native_kafka_poll_timeout_ms: self.class.poll_timeout
488
+ )
417
489
 
418
- bind_oauth(bind_id, admin)
490
+ bind_oauth(bind_id, admin)
419
491
 
420
- admin.start
421
- proxy = Karafka::Connection::Proxy.new(admin)
422
- yield(proxy)
423
- ensure
424
- admin&.close
492
+ admin.start
493
+ proxy = Karafka::Connection::Proxy.new(admin)
494
+ yield(proxy)
495
+ ensure
496
+ admin&.close
425
497
 
426
- unbind_oauth(bind_id)
498
+ unbind_oauth(bind_id)
499
+ end
427
500
  end
428
501
 
429
502
  private
430
503
 
504
+ # @return [Karafka::Connection::Proxy] proxy wrapping the current external client handle.
505
+ # For connection clients the handle is resolved on each use as they swap their underlying
506
+ # rdkafka instance across recovery resets while remaining operational
507
+ def external_client_proxy
508
+ if @external_client.is_a?(Karafka::Connection::Client)
509
+ @external_client.wrapped_kafka
510
+ else
511
+ @external_client
512
+ end
513
+ end
514
+
515
+ # @return [Topics] topics admin operating within this instance context (custom kafka and
516
+ # external client carried over)
517
+ def topics_admin
518
+ Topics.new(kafka: @custom_kafka, external_client: @external_client)
519
+ end
520
+
521
+ # @return [ConsumerGroups] consumer groups admin operating within this instance context
522
+ # (custom kafka and external client carried over)
523
+ def consumer_groups_admin
524
+ ConsumerGroups.new(kafka: @custom_kafka, external_client: @external_client)
525
+ end
526
+
431
527
  # @return [Integer] max wait time in ms
432
528
  def max_wait_time_ms
433
529
  self.class.max_wait_time
data/lib/karafka/app.rb CHANGED
@@ -57,7 +57,7 @@ module Karafka
57
57
 
58
58
  # Just a nicer name for the consumer groups
59
59
  alias_method :routes, :consumer_groups
60
- # Generalized alias routing entries are "groups" (consumer groups today, other kinds
60
+ # Generalized alias - routing entries are "groups" (consumer groups today, other kinds
61
61
  # of groups like KIP-932 share groups in the future).
62
62
  alias_method :groups, :consumer_groups
63
63
 
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Karafka module namespace
4
3
  module Karafka
5
4
  # Base consumer from which all Karafka consumers should inherit
6
5
  class BaseConsumer
7
6
  # Allow for consumer instance tagging for instrumentation
8
7
  include Karafka::Core::Taggable
9
8
  include Helpers::ConfigImporter.new(
10
- monitor: %i[monitor]
9
+ monitor: %i[monitor],
10
+ critical_errors: %i[internal processing critical_errors]
11
11
  )
12
12
 
13
13
  extend Forwardable
@@ -104,7 +104,17 @@ module Karafka
104
104
  # message.
105
105
  def on_consume
106
106
  handle_consume
107
- rescue => e
107
+ # Containment is intentionally broader than StandardError: any error escaping this method
108
+ # would bypass `#on_after_consume` in the worker, skipping the retry/pause flow entirely.
109
+ # The next successful batch would then auto-mark its offsets, durably committing past the
110
+ # failed batch - a silent at-least-once violation. Errors like SystemStackError or the
111
+ # ScriptError family (e.g. LoadError surfacing from a Rails autoload hiccup) are per-message
112
+ # failures and go through the regular retry flow like any other processing error.
113
+ # Process-critical errors additionally trigger a graceful shutdown via the auto-subscribed
114
+ # `Instrumentation::CriticalErrorsListener` watching this very instrumentation - the
115
+ # recorded failure plus the retry pause keep this partition protected during the shutdown
116
+ # window and the batch is redelivered after restart.
117
+ rescue Exception => e
108
118
  monitor.instrument(
109
119
  "error.occurred",
110
120
  error: e,
@@ -124,7 +134,11 @@ module Karafka
124
134
  # flows do not interact with external systems and their errors are expected to bubble up
125
135
  def on_after_consume
126
136
  handle_after_consume
127
- rescue => e
137
+ # Same containment rationale as in `#on_consume`: an error escaping this method would skip
138
+ # the retry below and the next successful batch would auto-mark its offsets, committing
139
+ # past the failed batch. The after-consume flow runs user-extensible code as well (DLQ
140
+ # strategies, dispatch enhancements), so it needs the same class-agnostic protection
141
+ rescue Exception => e
128
142
  monitor.instrument(
129
143
  "error.occurred",
130
144
  error: e,
@@ -380,7 +394,6 @@ module Karafka
380
394
  # @note Please note, that if you are seeking to a time offset, getting the offset is blocking
381
395
  def seek(offset, manual_seek = true, reset_offset: true)
382
396
  coordinator.manual_seek if manual_seek
383
- self.seek_offset = nil if reset_offset
384
397
 
385
398
  message = Karafka::Messages::Seek.new(
386
399
  topic.name,
@@ -398,6 +411,12 @@ module Karafka
398
411
  reset_offset: reset_offset
399
412
  ) do
400
413
  client.seek(message)
414
+
415
+ # We reset the seek offset only after the seek actually succeeded. Resetting it before the
416
+ # seek would, on a raising seek (for example an unresolvable time-based offset), leave
417
+ # seek_offset nil so the failure-driven retry would pause without seeking back and skip the
418
+ # rest of the batch.
419
+ self.seek_offset = nil if reset_offset
401
420
  end
402
421
  end
403
422
 
@@ -446,5 +465,14 @@ module Karafka
446
465
  attempt: attempt
447
466
  )
448
467
  end
468
+
469
+ # @param error [Exception, nil] error to check or nil when none was recorded
470
+ # @return [Boolean] is the error one of the process-critical ones (configurable via the
471
+ # `internal.processing.critical_errors` setting)
472
+ def critical_error?(error)
473
+ return false unless error
474
+
475
+ critical_errors.any? { |type| error.is_a?(type) }
476
+ end
449
477
  end
450
478
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Karafka
4
- # Karafka framework Cli
5
4
  class Cli
6
5
  # Console Karafka Cli action
7
6
  class Console < Base
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Karafka
4
- # Karafka framework Cli
5
4
  class Cli
6
5
  # Prints info with list of commands available
7
6
  class Help < Base
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Karafka
4
- # Karafka framework Cli
5
4
  class Cli
6
5
  # Info Karafka Cli action
7
6
  class Info < Base
@@ -3,7 +3,6 @@
3
3
  require "erb"
4
4
 
5
5
  module Karafka
6
- # Karafka framework Cli
7
6
  class Cli
8
7
  # Install Karafka Cli action
9
8
  class Install < Base
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Karafka
4
- # Karafka framework Cli
5
4
  class Cli
6
5
  # Server Karafka Cli action
7
6
  class Server < Base
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Karafka
4
- # Karafka framework Cli
5
4
  class Cli
6
5
  # Starts swarm of consumers forked from the supervisor
7
6
  class Swarm < Base
data/lib/karafka/cli.rb CHANGED
@@ -50,7 +50,7 @@ end
50
50
  # so this method is only available when the Karafka console is running
51
51
  #
52
52
  # We skip this because this should exist and be only valid in the console
53
- # :nocov:
53
+ # simplecov:disable
54
54
  if ENV["KARAFKA_CONSOLE"]
55
55
  # Reloads Karafka irb console session
56
56
  def reload!
@@ -58,4 +58,4 @@ if ENV["KARAFKA_CONSOLE"]
58
58
  Kernel.exec Karafka::Cli::Console.command
59
59
  end
60
60
  end
61
- # :nocov:
61
+ # simplecov:enable
@@ -28,6 +28,12 @@ module Karafka
28
28
  # @return [String] id of the client
29
29
  attr_reader :id
30
30
 
31
+ # @return [Karafka::Connection::Proxy, nil] proxy wrapping the underlying rdkafka consumer
32
+ # instance or nil when the connection was not yet established. Used by the admin APIs
33
+ # external client support to run admin operations on top of a running consumer
34
+ # connection. Low-level internal API.
35
+ attr_reader :wrapped_kafka
36
+
31
37
  # How many times should we retry polling in case of a failure
32
38
  MAX_POLL_RETRIES = 20
33
39
 
@@ -320,8 +326,8 @@ module Karafka
320
326
  # won't get into this loop again. This can happen when supervision decides it should close
321
327
  # things faster
322
328
  #
323
- # @see https://github.com/confluentinc/librdkafka/issues/4792
324
- # @see https://github.com/confluentinc/librdkafka/issues/4527
329
+ # See https://github.com/confluentinc/librdkafka/issues/4792
330
+ # See https://github.com/confluentinc/librdkafka/issues/4527
325
331
  if unsubscribe?
326
332
  @unsubscribing = true
327
333
 
@@ -454,6 +460,26 @@ module Karafka
454
460
  @wrapped_kafka.query_watermark_offsets(topic, partition)
455
461
  end
456
462
 
463
+ # Resolves offsets of many partitions with one batched request instead of one broker
464
+ # roundtrip per partition.
465
+ #
466
+ # @param topic_partition_offsets [Hash{String => Array<Hash>}] topics with arrays of
467
+ # partition offset specs, each with a `:partition` and an `:offset` (`:earliest`,
468
+ # `:latest`, `:max_timestamp` or an integer timestamp in ms)
469
+ # @return [Array<Hash>] resolved offsets, each with `:topic`, `:partition` and `:offset`
470
+ # @note This consumer own isolation level is forwarded to the query. Be aware that the
471
+ # underlying batched `ListOffsets` resolves `:latest` to the high watermark regardless of
472
+ # the isolation level (unlike the consumer `query_watermark_offsets`, which returns the
473
+ # last stable offset for a read_committed consumer). On a topic with an in-flight
474
+ # transaction `:latest` therefore includes the uncommitted messages a read_committed
475
+ # consumer will not see. Non-transactional topics are unaffected (LSO == HWM).
476
+ def read_partition_offsets(topic_partition_offsets)
477
+ @wrapped_kafka.read_partition_offsets(
478
+ topic_partition_offsets,
479
+ isolation_level: isolation_level
480
+ )
481
+ end
482
+
457
483
  # @return [String] safe inspection string that is causing circular dependencies and other
458
484
  # issues
459
485
  def inspect
@@ -463,6 +489,17 @@ module Karafka
463
489
 
464
490
  private
465
491
 
492
+ # @return [Integer] isolation level of this consumer mapped to the constant expected by the
493
+ # offsets queries. It cannot change after the client is created, hence memoized.
494
+ # @note When not configured, librdkafka reads committed, so we mirror that default here.
495
+ def isolation_level
496
+ @isolation_level ||=
497
+ case @subscription_group.kafka.fetch(:"isolation.level", "read_committed").to_s
498
+ when "read_uncommitted" then Admin::IsolationLevels::READ_UNCOMMITTED
499
+ else Admin::IsolationLevels::READ_COMMITTED
500
+ end
501
+ end
502
+
466
503
  # When we cannot store an offset, it means we no longer own the partition
467
504
  #
468
505
  # Non thread-safe offset storing method
@@ -560,8 +597,8 @@ module Karafka
560
597
  @kafka = nil
561
598
  @wrapped_kafka = nil
562
599
  @buffer.clear
563
- # @note We do not clear rebalance manager here as we may still have revocation info
564
- # here that we want to consider valid prior to running another reconnection
600
+ # We do not clear rebalance manager here as we may still have revocation info here that we
601
+ # want to consider valid prior to running another reconnection
565
602
  end
566
603
 
567
604
  # Unsubscribes from all the subscriptions
@@ -665,7 +702,7 @@ module Karafka
665
702
  case e.code
666
703
  when *EARLY_REPORT_ERRORS
667
704
  early_report = true
668
- # @see
705
+ # See
669
706
  # https://github.com/confluentinc/confluent-kafka-dotnet/issues/1366#issuecomment-821842990
670
707
  # This will be raised each time poll detects a non-existing topic. When auto creation is
671
708
  # on, we can safely ignore it
@@ -700,8 +737,8 @@ module Karafka
700
737
  raise unless retryable
701
738
 
702
739
  # Most of the errors can be safely ignored as librdkafka will recover from them
703
- # @see https://github.com/edenhill/librdkafka/issues/1987#issuecomment-422008750
704
- # @see https://github.com/edenhill/librdkafka/wiki/Error-handling
740
+ # See https://github.com/edenhill/librdkafka/issues/1987#issuecomment-422008750
741
+ # See https://github.com/edenhill/librdkafka/wiki/Error-handling
705
742
 
706
743
  time_poll.checkpoint
707
744
  time_poll.backoff
@@ -26,9 +26,9 @@ module Karafka
26
26
  # @return [Karafka::Routing::SubscriptionGroup] subscription group that this listener handles
27
27
  attr_reader :subscription_group
28
28
 
29
- # @return [Processing::ConsumerGroups::CoordinatorsBuffer] coordinator buffers that can be used directly in
30
- # advanced cases of changes to the polling flow (like triggered seek back without messages
31
- # ahead in the topic)
29
+ # @return [Processing::ConsumerGroups::CoordinatorsBuffer] coordinator buffers that can be
30
+ # used directly in advanced cases of changes to the polling flow (like triggered seek back
31
+ # without messages ahead in the topic)
32
32
  attr_reader :coordinators
33
33
 
34
34
  # How long to wait in the initial events poll. Increases chances of having the initial events
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Karafka
4
- # Namespace for Kafka connection related logic
5
4
  module Connection
6
5
  # Connections manager responsible for starting and managing listeners connections
7
6
  #
@@ -25,6 +25,23 @@ module Karafka
25
25
  )
26
26
  end
27
27
 
28
+ # Resets the attempt count of a given topic partition pause tracker.
29
+ #
30
+ # Used on revocation so that a later reclaim of the same partition starts counting retry
31
+ # attempts from zero instead of carrying the stale count across the rebalance. We reset
32
+ # rather than remove the tracker because the pause itself may still need to be resumed after
33
+ # the reclaim (the partition can be re-paused via the retained paused offsets on rebalance).
34
+ #
35
+ # A coordinator and its pause tracker are created together in
36
+ # `CoordinatorsBuffer#find_or_create`, and `CoordinatorsBuffer#revoke` only calls us once it
37
+ # has confirmed the coordinator exists - so the tracker is always present here.
38
+ #
39
+ # @param topic [::Karafka::Routing::Topic] topic
40
+ # @param partition [Integer] partition number
41
+ def revoke(topic, partition)
42
+ @pauses[topic][partition].reset
43
+ end
44
+
28
45
  # Resumes processing of partitions for which pause time has ended.
29
46
  #
30
47
  # @yieldparam [Karafka::Routing::Topic] topic