karafka-rdkafka 0.31.0-aarch64-linux-gnu → 0.32.0-aarch64-linux-gnu
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +83 -35
- data/README.md +2 -1
- data/ext/librdkafka.so +0 -0
- data/karafka-rdkafka.gemspec +4 -3
- data/lib/rdkafka/admin/acl_binding_result.rb +4 -16
- data/lib/rdkafka/admin/alter_consumer_group_offsets_report.rb +2 -2
- data/lib/rdkafka/admin/create_acl_report.rb +1 -3
- data/lib/rdkafka/admin/create_topic_report.rb +2 -6
- data/lib/rdkafka/admin/delete_consumer_group_offsets_report.rb +2 -2
- data/lib/rdkafka/admin/delete_groups_report.rb +2 -6
- data/lib/rdkafka/admin/delete_topic_report.rb +2 -6
- data/lib/rdkafka/admin/describe_cluster_report.rb +3 -0
- data/lib/rdkafka/admin/list_consumer_group_offsets_handle.rb +31 -0
- data/lib/rdkafka/admin/list_consumer_group_offsets_report.rb +52 -0
- data/lib/rdkafka/admin.rb +180 -10
- data/lib/rdkafka/bindings/admin/configs.rb +1 -1
- data/lib/rdkafka/bindings/admin.rb +14 -0
- data/lib/rdkafka/bindings/callbacks.rb +13 -6
- data/lib/rdkafka/bindings/configuration.rb +3 -3
- data/lib/rdkafka/bindings/consumer.rb +44 -2
- data/lib/rdkafka/bindings/errors.rb +1 -1
- data/lib/rdkafka/bindings/handle.rb +1 -1
- data/lib/rdkafka/bindings.rb +3 -0
- data/lib/rdkafka/callbacks/list_consumer_group_offsets_handler.rb +62 -0
- data/lib/rdkafka/callbacks.rb +5 -6
- data/lib/rdkafka/config.rb +70 -0
- data/lib/rdkafka/consumer/allocator.rb +0 -2
- data/lib/rdkafka/consumer/topic_partition_list.rb +7 -7
- data/lib/rdkafka/consumer.rb +76 -36
- data/lib/rdkafka/defaults.rb +6 -4
- data/lib/rdkafka/native_kafka.rb +45 -31
- data/lib/rdkafka/producer/partitions_count_cache.rb +109 -26
- data/lib/rdkafka/producer.rb +141 -71
- data/lib/rdkafka/share_consumer.rb +64 -34
- data/lib/rdkafka/version.rb +1 -1
- data/lib/rdkafka.rb +4 -1
- data/package-lock.json +3 -3
- data/renovate.json +7 -0
- metadata +5 -30
data/lib/rdkafka/config.rb
CHANGED
|
@@ -156,6 +156,25 @@ module Rdkafka
|
|
|
156
156
|
)
|
|
157
157
|
end
|
|
158
158
|
|
|
159
|
+
# Returns the time-to-live of the process-wide partitions count cache used by
|
|
160
|
+
# `produce(partition_key:)`.
|
|
161
|
+
#
|
|
162
|
+
# @return [Integer] time-to-live in milliseconds
|
|
163
|
+
def self.partition_count_cache_ttl_ms
|
|
164
|
+
Rdkafka::Producer.partitions_count_cache.ttl_ms
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Sets the time-to-live of the process-wide partitions count cache shared by all the producers.
|
|
168
|
+
# Cached counts are kept, so it can be changed at any time.
|
|
169
|
+
#
|
|
170
|
+
# @param value [Integer] time-to-live in milliseconds
|
|
171
|
+
# (defaults to {Defaults::PARTITIONS_COUNT_CACHE_TTL_MS})
|
|
172
|
+
# @return [void]
|
|
173
|
+
# @raise [ArgumentError] when the value is not a positive Integer
|
|
174
|
+
def self.partition_count_cache_ttl_ms=(value)
|
|
175
|
+
Rdkafka::Producer.partitions_count_cache.ttl_ms = value
|
|
176
|
+
end
|
|
177
|
+
|
|
159
178
|
# @private
|
|
160
179
|
def self.opaques
|
|
161
180
|
@@opaques
|
|
@@ -180,6 +199,7 @@ module Rdkafka
|
|
|
180
199
|
|
|
181
200
|
@config_hash = DEFAULT_CONFIG.merge(config_hash)
|
|
182
201
|
@consumer_rebalance_listener = nil
|
|
202
|
+
@consumer_offset_commit_listener = nil
|
|
183
203
|
@consumer_poll_set = true
|
|
184
204
|
end
|
|
185
205
|
|
|
@@ -207,6 +227,20 @@ module Rdkafka
|
|
|
207
227
|
# @return [Object, #on_partitions_assigned, #on_partitions_revoked] listener instance
|
|
208
228
|
attr_writer :consumer_rebalance_listener
|
|
209
229
|
|
|
230
|
+
# Get the result of every offset commit, including async commits that the broker rejects
|
|
231
|
+
#
|
|
232
|
+
# `#on_offsets_committed` gets an {RdkafkaError} (nil on success) and the
|
|
233
|
+
# {Consumer::TopicPartitionList} of the commit, whose partitions carry their own errors. It
|
|
234
|
+
# runs inside {Consumer#poll} (or {Consumer#events_poll} when `consumer_poll_set` is false)
|
|
235
|
+
# and inside {Consumer#close}, on the thread that calls them.
|
|
236
|
+
#
|
|
237
|
+
# `#on_offsets_commit_failed` gets the same arguments, but only for a failed commit: not on
|
|
238
|
+
# success and not on `no_offset` (nothing new to commit). A listener with only this method
|
|
239
|
+
# costs nothing for the other commits.
|
|
240
|
+
#
|
|
241
|
+
# @return [Object, #on_offsets_committed, #on_offsets_commit_failed] listener instance
|
|
242
|
+
attr_writer :consumer_offset_commit_listener
|
|
243
|
+
|
|
210
244
|
# Should we use a single queue for the underlying consumer and events.
|
|
211
245
|
#
|
|
212
246
|
# This is an advanced API that allows for more granular control of the polling process.
|
|
@@ -243,6 +277,11 @@ module Rdkafka
|
|
|
243
277
|
Rdkafka::Bindings.rd_kafka_conf_set_rebalance_cb(config, Rdkafka::Bindings::RebalanceCallback)
|
|
244
278
|
end
|
|
245
279
|
|
|
280
|
+
if @consumer_offset_commit_listener
|
|
281
|
+
opaque.consumer_offset_commit_listener = @consumer_offset_commit_listener
|
|
282
|
+
Rdkafka::Bindings.rd_kafka_conf_set_offset_commit_cb(config, Rdkafka::Bindings::OffsetCommitCallback)
|
|
283
|
+
end
|
|
284
|
+
|
|
246
285
|
build_native_client(
|
|
247
286
|
config,
|
|
248
287
|
:rd_kafka_consumer,
|
|
@@ -598,6 +637,7 @@ module Rdkafka
|
|
|
598
637
|
class Opaque
|
|
599
638
|
attr_accessor :producer
|
|
600
639
|
attr_accessor :consumer_rebalance_listener
|
|
640
|
+
attr_accessor :consumer_offset_commit_listener
|
|
601
641
|
# Backreference set for share consumers so conf-level callbacks (which receive the opaque)
|
|
602
642
|
# can hand instance-scoped data - currently the librdkafka client name - back to the
|
|
603
643
|
# ShareConsumer, which has no native accessor for it
|
|
@@ -635,5 +675,35 @@ module Rdkafka
|
|
|
635
675
|
|
|
636
676
|
consumer_rebalance_listener.on_partitions_revoked(list)
|
|
637
677
|
end
|
|
678
|
+
|
|
679
|
+
# Invokes the on_offsets_committed callback on the offset commit listener if set
|
|
680
|
+
# @param error [Rdkafka::RdkafkaError, nil] the commit error or nil on success
|
|
681
|
+
# @param list [Rdkafka::Consumer::TopicPartitionList] the offsets of the commit
|
|
682
|
+
def call_on_offsets_committed(error, list)
|
|
683
|
+
return unless consumer_offset_commit_listener
|
|
684
|
+
return unless consumer_offset_commit_listener.respond_to?(:on_offsets_committed)
|
|
685
|
+
|
|
686
|
+
consumer_offset_commit_listener.on_offsets_committed(error, list)
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
# Invokes the on_offsets_commit_failed callback on the offset commit listener if set
|
|
690
|
+
# @param error [Rdkafka::RdkafkaError] the commit error
|
|
691
|
+
# @param list [Rdkafka::Consumer::TopicPartitionList] the offsets of the commit
|
|
692
|
+
def call_on_offsets_commit_failed(error, list)
|
|
693
|
+
return unless consumer_offset_commit_listener
|
|
694
|
+
return unless consumer_offset_commit_listener.respond_to?(:on_offsets_commit_failed)
|
|
695
|
+
|
|
696
|
+
consumer_offset_commit_listener.on_offsets_commit_failed(error, list)
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
# @return [Boolean] true if the offset commit listener wants the result of every commit
|
|
700
|
+
def offsets_committed_listened?
|
|
701
|
+
consumer_offset_commit_listener.respond_to?(:on_offsets_committed)
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
# @return [Boolean] true if the offset commit listener wants the failed commits
|
|
705
|
+
def offsets_commit_failed_listened?
|
|
706
|
+
consumer_offset_commit_listener.respond_to?(:on_offsets_commit_failed)
|
|
707
|
+
end
|
|
638
708
|
end
|
|
639
709
|
end
|
|
@@ -38,7 +38,7 @@ module Rdkafka
|
|
|
38
38
|
# Calling this method multiple times for the same topic will overwrite the previous
|
|
39
39
|
# configuraton.
|
|
40
40
|
#
|
|
41
|
-
# @param
|
|
41
|
+
# @param topic_name [String] The topic's name
|
|
42
42
|
# @param partitions [Array<Integer>, Range<Integer>, Integer] The topic's partitions or
|
|
43
43
|
# partition count
|
|
44
44
|
#
|
|
@@ -52,14 +52,14 @@ module Rdkafka
|
|
|
52
52
|
#
|
|
53
53
|
# @example Add a topic with all topics up to a count
|
|
54
54
|
# tpl.add_topic("topic", 9)
|
|
55
|
-
def add_topic(
|
|
55
|
+
def add_topic(topic_name, partitions = nil)
|
|
56
56
|
if partitions.nil?
|
|
57
|
-
@data[
|
|
57
|
+
@data[topic_name.to_s] = nil
|
|
58
58
|
else
|
|
59
59
|
if partitions.is_a? Integer
|
|
60
60
|
partitions = (0..partitions - 1)
|
|
61
61
|
end
|
|
62
|
-
@data[
|
|
62
|
+
@data[topic_name.to_s] = partitions.map { |p| Partition.new(p, nil, Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR) }
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
@@ -67,12 +67,12 @@ module Rdkafka
|
|
|
67
67
|
# Calling this method multiple times for the same topic will overwrite the previous
|
|
68
68
|
# configuraton.
|
|
69
69
|
#
|
|
70
|
-
# @param
|
|
70
|
+
# @param topic_name [String] The topic's name
|
|
71
71
|
# @param partitions_with_offsets [Hash{Integer => Integer}, Array<Consumer::Partition>] The
|
|
72
72
|
# topic's partitions and offsets (Hash) or partitions with offsets and metadata (Array)
|
|
73
73
|
# @return [nil]
|
|
74
|
-
def add_topic_and_partitions_with_offsets(
|
|
75
|
-
@data[
|
|
74
|
+
def add_topic_and_partitions_with_offsets(topic_name, partitions_with_offsets)
|
|
75
|
+
@data[topic_name.to_s] = partitions_with_offsets.map do |p, o|
|
|
76
76
|
p.is_a?(Partition) ? p : Partition.new(p, o)
|
|
77
77
|
end
|
|
78
78
|
end
|
data/lib/rdkafka/consumer.rb
CHANGED
|
@@ -17,6 +17,9 @@ module Rdkafka
|
|
|
17
17
|
include Helpers::Metadata
|
|
18
18
|
include Helpers::ListOffsets
|
|
19
19
|
|
|
20
|
+
# Reason passed to the error callback when closing the consumer fails
|
|
21
|
+
CLOSE_ERROR_REASON = "Consumer close failed"
|
|
22
|
+
|
|
20
23
|
# @private
|
|
21
24
|
# @param native_kafka [NativeKafka] wrapper around the native Kafka consumer handle
|
|
22
25
|
def initialize(native_kafka)
|
|
@@ -31,30 +34,61 @@ module Rdkafka
|
|
|
31
34
|
ObjectSpace.define_finalizer(self, self.class.finalizer(native_kafka, @consumer_queue_holder))
|
|
32
35
|
end
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
37
|
+
class << self
|
|
38
|
+
# Builds the GC finalizer for a consumer. It mirrors {#close}: close the consumer, destroy the
|
|
39
|
+
# consumer-queue reference, then destroy the native client. The default
|
|
40
|
+
# `NativeKafka#finalizer` went straight to `rd_kafka_destroy`, leaving the consumer-queue
|
|
41
|
+
# reference (from `rd_kafka_queue_get_consumer`, taken by `poll_batch`) dangling - which can
|
|
42
|
+
# make `rd_kafka_destroy` block inside the finalizer (process hang at GC/shutdown) or leak the
|
|
43
|
+
# handle.
|
|
44
|
+
#
|
|
45
|
+
# @private
|
|
46
|
+
# @param native_kafka [NativeKafka] the wrapped native client
|
|
47
|
+
# @param queue_holder [Array] single-element holder carrying the consumer queue pointer
|
|
48
|
+
# (or empty)
|
|
49
|
+
# @return [Proc] finalizer proc that must not reference the consumer instance
|
|
50
|
+
def finalizer(native_kafka, queue_holder)
|
|
51
|
+
proc do
|
|
52
|
+
next if native_kafka.closed?
|
|
53
|
+
|
|
54
|
+
close_response = nil
|
|
55
|
+
|
|
56
|
+
native_kafka.synchronize do |inner|
|
|
57
|
+
close_response = Rdkafka::Bindings.rd_kafka_consumer_close(inner)
|
|
58
|
+
|
|
59
|
+
queue = queue_holder[0]
|
|
60
|
+
Rdkafka::Bindings.rd_kafka_queue_destroy(queue) if queue
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
report_close_error(native_kafka, close_response)
|
|
64
|
+
|
|
65
|
+
native_kafka.close
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Reports a failed `rd_kafka_consumer_close` (e.g. of a fatally-errored consumer) through
|
|
70
|
+
# {Bindings::ErrorCallback}, the same path as errors emitted by librdkafka, so it reaches
|
|
71
|
+
# {Config.error_callback} in the shape the callback expects (libraries such as karafka-core
|
|
72
|
+
# replace `ErrorCallback`). It runs after the close, but before the native client is
|
|
73
|
+
# destroyed, as the callback reads the client name and fatal error from it. Close runs during
|
|
74
|
+
# shutdown and from the GC finalizer, so the error is reported, never raised.
|
|
75
|
+
#
|
|
76
|
+
# @private
|
|
77
|
+
# @param native_kafka [NativeKafka] the wrapped native client
|
|
78
|
+
# @param response [Integer] result code of `rd_kafka_consumer_close`
|
|
79
|
+
# @return [nil]
|
|
80
|
+
def report_close_error(native_kafka, response)
|
|
81
|
+
return if response == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
82
|
+
|
|
83
|
+
native_kafka.with_inner do |inner|
|
|
84
|
+
Rdkafka::Bindings::ErrorCallback.call(inner, response, CLOSE_ERROR_REASON, nil)
|
|
55
85
|
end
|
|
56
86
|
|
|
57
|
-
|
|
87
|
+
nil
|
|
88
|
+
rescue Exception => err
|
|
89
|
+
Rdkafka::Config.logger.error("Unhandled exception: #{err.class} - #{err.message}")
|
|
90
|
+
|
|
91
|
+
nil
|
|
58
92
|
end
|
|
59
93
|
end
|
|
60
94
|
|
|
@@ -114,7 +148,7 @@ module Rdkafka
|
|
|
114
148
|
|
|
115
149
|
# Polls for events in a non-blocking loop, yielding the count after each iteration.
|
|
116
150
|
#
|
|
117
|
-
# This method processes events (stats, errors, etc.) in a single GVL
|
|
151
|
+
# This method processes events (stats, errors, etc.) in a single GVL session,
|
|
118
152
|
# which is more efficient than repeated individual polls. It uses non-blocking polls
|
|
119
153
|
# internally (no GVL release between polls).
|
|
120
154
|
#
|
|
@@ -135,8 +169,9 @@ module Rdkafka
|
|
|
135
169
|
# consumer.events_poll_nb_each do |_count|
|
|
136
170
|
# :stop if monotonic_now >= deadline
|
|
137
171
|
# end
|
|
138
|
-
# @note This method
|
|
139
|
-
# Other
|
|
172
|
+
# @note This method runs inside `with_inner` and does not hold a lock while it loops.
|
|
173
|
+
# Other operations on the same client run concurrently. `close` waits until this
|
|
174
|
+
# method returns.
|
|
140
175
|
# @note This method is thread-safe as it uses @native_kafka.with_inner synchronization
|
|
141
176
|
# @note Do NOT use this if `consumer_poll_set` was set to `true`
|
|
142
177
|
def events_poll_nb_each
|
|
@@ -153,7 +188,7 @@ module Rdkafka
|
|
|
153
188
|
|
|
154
189
|
# Polls for messages in a non-blocking loop, yielding each message to the caller.
|
|
155
190
|
#
|
|
156
|
-
# This method processes messages in a single GVL
|
|
191
|
+
# This method processes messages in a single GVL session until the queue is empty
|
|
157
192
|
# or the caller returns `:stop`. It handles the message pointer lifecycle internally,
|
|
158
193
|
# ensuring proper cleanup via `rd_kafka_message_destroy`.
|
|
159
194
|
#
|
|
@@ -180,8 +215,9 @@ module Rdkafka
|
|
|
180
215
|
# `events_poll_nb_each` which uses `rd_kafka_poll` for event callbacks (delivery reports,
|
|
181
216
|
# statistics, etc.). For consumers, use this method to receive messages and
|
|
182
217
|
# `events_poll_nb_each` for processing background events.
|
|
183
|
-
# @note This method
|
|
184
|
-
#
|
|
218
|
+
# @note This method runs inside `with_inner` and does not hold a lock while it loops.
|
|
219
|
+
# Other operations on the same client run concurrently. `close` waits until this
|
|
220
|
+
# method returns.
|
|
185
221
|
# @note Timeout/max_messages logic should be implemented by the caller
|
|
186
222
|
def poll_nb_each
|
|
187
223
|
closed_consumer_check(__method__)
|
|
@@ -213,8 +249,10 @@ module Rdkafka
|
|
|
213
249
|
return if closed?
|
|
214
250
|
ObjectSpace.undefine_finalizer(self)
|
|
215
251
|
|
|
252
|
+
close_response = nil
|
|
253
|
+
|
|
216
254
|
@native_kafka.synchronize do |inner|
|
|
217
|
-
Rdkafka::Bindings.rd_kafka_consumer_close(inner)
|
|
255
|
+
close_response = Rdkafka::Bindings.rd_kafka_consumer_close(inner)
|
|
218
256
|
|
|
219
257
|
if @consumer_queue
|
|
220
258
|
Rdkafka::Bindings.rd_kafka_queue_destroy(@consumer_queue)
|
|
@@ -223,6 +261,8 @@ module Rdkafka
|
|
|
223
261
|
end
|
|
224
262
|
end
|
|
225
263
|
|
|
264
|
+
self.class.report_close_error(@native_kafka, close_response)
|
|
265
|
+
|
|
226
266
|
@native_kafka.close
|
|
227
267
|
end
|
|
228
268
|
|
|
@@ -469,12 +509,12 @@ module Rdkafka
|
|
|
469
509
|
|
|
470
510
|
# Query broker for low (oldest/beginning) and high (newest/end) offsets for a partition.
|
|
471
511
|
#
|
|
472
|
-
# @param
|
|
512
|
+
# @param topic_name [String] The topic to query
|
|
473
513
|
# @param partition [Integer] The partition to query
|
|
474
514
|
# @param timeout_ms [Integer] The timeout for querying the broker
|
|
475
515
|
# @return [Integer] The low and high watermark
|
|
476
516
|
# @raise [RdkafkaError] When querying the broker fails.
|
|
477
|
-
def query_watermark_offsets(
|
|
517
|
+
def query_watermark_offsets(topic_name, partition, timeout_ms = Defaults::CONSUMER_QUERY_WATERMARK_TIMEOUT_MS)
|
|
478
518
|
closed_consumer_check(__method__)
|
|
479
519
|
|
|
480
520
|
low = FFI::MemoryPointer.new(:int64, 1)
|
|
@@ -483,13 +523,13 @@ module Rdkafka
|
|
|
483
523
|
@native_kafka.with_inner do |inner|
|
|
484
524
|
response = Rdkafka::Bindings.rd_kafka_query_watermark_offsets(
|
|
485
525
|
inner,
|
|
486
|
-
|
|
526
|
+
topic_name,
|
|
487
527
|
partition,
|
|
488
528
|
low,
|
|
489
529
|
high,
|
|
490
530
|
timeout_ms
|
|
491
531
|
)
|
|
492
|
-
Rdkafka::RdkafkaError.validate!(response, "Error querying watermark offsets for partition #{partition} of #{
|
|
532
|
+
Rdkafka::RdkafkaError.validate!(response, "Error querying watermark offsets for partition #{partition} of #{topic_name}", client_ptr: inner)
|
|
493
533
|
end
|
|
494
534
|
|
|
495
535
|
[low.read_array_of_int64(1).first, high.read_array_of_int64(1).first]
|
|
@@ -651,12 +691,12 @@ module Rdkafka
|
|
|
651
691
|
# Seek to a particular message by providing the topic, partition and offset. The next poll on
|
|
652
692
|
# the topic/partition will return the message at the given offset.
|
|
653
693
|
#
|
|
654
|
-
# @param
|
|
694
|
+
# @param topic_name [String] The topic in which to seek
|
|
655
695
|
# @param partition [Integer] The partition number to seek
|
|
656
696
|
# @param offset [Integer] The partition offset to seek
|
|
657
697
|
# @return [nil]
|
|
658
698
|
# @raise [RdkafkaError] When seeking fails
|
|
659
|
-
def seek_by(
|
|
699
|
+
def seek_by(topic_name, partition, offset)
|
|
660
700
|
closed_consumer_check(__method__)
|
|
661
701
|
|
|
662
702
|
# rd_kafka_offset_store is one of the few calls that does not support
|
|
@@ -664,7 +704,7 @@ module Rdkafka
|
|
|
664
704
|
native_topic = @native_kafka.with_inner do |inner|
|
|
665
705
|
Rdkafka::Bindings.rd_kafka_topic_new(
|
|
666
706
|
inner,
|
|
667
|
-
|
|
707
|
+
topic_name,
|
|
668
708
|
nil
|
|
669
709
|
)
|
|
670
710
|
end
|
data/lib/rdkafka/defaults.rb
CHANGED
|
@@ -114,12 +114,14 @@ module Rdkafka
|
|
|
114
114
|
# @see Config#admin
|
|
115
115
|
NATIVE_KAFKA_POLL_TIMEOUT_MS = 100
|
|
116
116
|
|
|
117
|
-
# Sleep interval used in NativeKafka#synchronize wait loop
|
|
118
|
-
# @see NativeKafka#synchronize
|
|
119
|
-
NATIVE_KAFKA_SYNCHRONIZE_SLEEP_INTERVAL_MS = 10
|
|
120
|
-
|
|
121
117
|
# TTL for partitions count cache entries (30 seconds)
|
|
122
118
|
# @see Producer::PartitionsCountCache
|
|
123
119
|
PARTITIONS_COUNT_CACHE_TTL_MS = 30_000
|
|
120
|
+
|
|
121
|
+
# How long an expired partitions count is served after a refresh failed on a transient
|
|
122
|
+
# (e.g. transport) error, before the next refresh attempt. Longer than one failed metadata
|
|
123
|
+
# refresh (up to ~7 s with retries), so produces are not blocked most of the time
|
|
124
|
+
# @see Producer::PartitionsCountCache#get
|
|
125
|
+
PARTITIONS_COUNT_CACHE_STALE_BACKOFF_MS = 10_000
|
|
124
126
|
end
|
|
125
127
|
end
|
data/lib/rdkafka/native_kafka.rb
CHANGED
|
@@ -25,21 +25,15 @@ module Rdkafka
|
|
|
25
25
|
@access_mutex = Mutex.new
|
|
26
26
|
# Lock around internal polling
|
|
27
27
|
@poll_mutex = Mutex.new
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# We do not use the same mutex, because it could create a deadlock when an already
|
|
39
|
-
# incremented operation cannot decrement because `@access_lock` is now owned by a different
|
|
40
|
-
# thread in a synchronized mode and the synchronized mode is waiting on the decrement.
|
|
41
|
-
@decrement_mutex = Mutex.new
|
|
42
|
-
# counter for operations in progress using inner
|
|
28
|
+
# Signalled when `#synchronize` ends and, only while `#synchronize` waits, when the operations
|
|
29
|
+
# in progress counter reaches zero: no one else waits for zero, so other decrements skip the
|
|
30
|
+
# broadcast. `#synchronize` waits on it with `@access_mutex` released, so operations that
|
|
31
|
+
# started before it can still decrement the counter.
|
|
32
|
+
@operations_done = ConditionVariable.new
|
|
33
|
+
# Set while `#synchronize` runs, so no new operation starts until it ends
|
|
34
|
+
@synchronizing = false
|
|
35
|
+
# counter for operations in progress using inner, changed only under `@access_mutex`, so no
|
|
36
|
+
# update is lost on Rubies without a GVL (JRuby)
|
|
43
37
|
@operations_in_progress = 0
|
|
44
38
|
|
|
45
39
|
@run_polling_thread = run_polling_thread
|
|
@@ -63,8 +57,7 @@ module Rdkafka
|
|
|
63
57
|
Rdkafka::Bindings.rd_kafka_poll(@inner, 0)
|
|
64
58
|
|
|
65
59
|
if @run_polling_thread
|
|
66
|
-
# Start thread to poll client for delivery callbacks,
|
|
67
|
-
# not used in consumer.
|
|
60
|
+
# Start thread to poll client for delivery callbacks, not used in consumer.
|
|
68
61
|
@polling_thread = Thread.new do
|
|
69
62
|
loop do
|
|
70
63
|
@poll_mutex.synchronize do
|
|
@@ -90,15 +83,29 @@ module Rdkafka
|
|
|
90
83
|
# @return [Object] the result of the block
|
|
91
84
|
# @raise [ClosedInnerError] when the inner handle is nil
|
|
92
85
|
def with_inner
|
|
93
|
-
|
|
86
|
+
owned = @access_mutex.owned?
|
|
87
|
+
|
|
88
|
+
if owned
|
|
94
89
|
@operations_in_progress += 1
|
|
95
90
|
else
|
|
96
|
-
@access_mutex.synchronize
|
|
91
|
+
@access_mutex.synchronize do
|
|
92
|
+
@operations_done.wait(@access_mutex) while @synchronizing
|
|
93
|
+
@operations_in_progress += 1
|
|
94
|
+
end
|
|
97
95
|
end
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
begin
|
|
98
|
+
@inner.nil? ? raise(ClosedInnerError) : yield(@inner)
|
|
99
|
+
ensure
|
|
100
|
+
if owned
|
|
101
|
+
@operations_in_progress -= 1
|
|
102
|
+
else
|
|
103
|
+
@access_mutex.synchronize do
|
|
104
|
+
@operations_in_progress -= 1
|
|
105
|
+
@operations_done.broadcast if @synchronizing && @operations_in_progress.zero?
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
102
109
|
end
|
|
103
110
|
|
|
104
111
|
# Executes a block while holding exclusive access to the native Kafka handle
|
|
@@ -107,13 +114,21 @@ module Rdkafka
|
|
|
107
114
|
# @return [Object] the result of the block
|
|
108
115
|
def synchronize(&block)
|
|
109
116
|
@access_mutex.synchronize do
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
@operations_done.wait(@access_mutex) while @synchronizing
|
|
118
|
+
@synchronizing = true
|
|
119
|
+
|
|
120
|
+
begin
|
|
121
|
+
# Wait for any commands using the inner to finish
|
|
122
|
+
# This can take a while on blocking operations like polling but is essential not to
|
|
123
|
+
# proceed with certain types of operations like resources destruction as it can cause the
|
|
124
|
+
# process to hang or crash
|
|
125
|
+
@operations_done.wait(@access_mutex) until @operations_in_progress.zero?
|
|
126
|
+
|
|
127
|
+
with_inner(&block)
|
|
128
|
+
ensure
|
|
129
|
+
@synchronizing = false
|
|
130
|
+
@operations_done.broadcast
|
|
131
|
+
end
|
|
117
132
|
end
|
|
118
133
|
end
|
|
119
134
|
|
|
@@ -210,8 +225,7 @@ module Rdkafka
|
|
|
210
225
|
# Indicate to polling thread that we're closing
|
|
211
226
|
@polling_thread[:closing] = true
|
|
212
227
|
|
|
213
|
-
# Wait for the polling thread to finish up,
|
|
214
|
-
# this can be aborted in practice if this
|
|
228
|
+
# Wait for the polling thread to finish up, this can be aborted in practice if this
|
|
215
229
|
# code runs from a finalizer.
|
|
216
230
|
@polling_thread.join
|
|
217
231
|
end
|