rdkafka 0.28.0-aarch64-linux-gnu → 0.29.1-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 +42 -0
- data/README.md +3 -2
- data/bin/verify_kafka_warnings +5 -0
- data/docker-compose-ssl.yml +4 -1
- data/docker-compose.yml +4 -1
- data/ext/librdkafka.so +0 -0
- data/lib/rdkafka/abstract_handle.rb +31 -2
- data/lib/rdkafka/admin/config_binding_result.rb +2 -2
- data/lib/rdkafka/admin/create_acl_handle.rb +6 -5
- data/lib/rdkafka/admin/create_acl_report.rb +2 -1
- data/lib/rdkafka/admin/create_partitions_handle.rb +4 -6
- data/lib/rdkafka/admin/create_topic_handle.rb +4 -6
- data/lib/rdkafka/admin/delete_acl_handle.rb +5 -7
- data/lib/rdkafka/admin/delete_groups_handle.rb +4 -6
- data/lib/rdkafka/admin/delete_records_handle.rb +31 -0
- data/lib/rdkafka/admin/delete_records_report.rb +25 -0
- data/lib/rdkafka/admin/delete_topic_handle.rb +4 -6
- data/lib/rdkafka/admin/describe_acl_handle.rb +5 -7
- data/lib/rdkafka/admin/describe_acl_report.rb +2 -1
- data/lib/rdkafka/admin/describe_configs_handle.rb +4 -10
- data/lib/rdkafka/admin/describe_configs_report.rb +1 -5
- data/lib/rdkafka/admin/incremental_alter_configs_handle.rb +4 -10
- data/lib/rdkafka/admin/incremental_alter_configs_report.rb +1 -5
- data/lib/rdkafka/admin/list_consumer_groups_handle.rb +31 -0
- data/lib/rdkafka/admin/list_consumer_groups_report.rb +83 -0
- data/lib/rdkafka/admin/list_offsets_handle.rb +5 -10
- data/lib/rdkafka/admin/list_offsets_report.rb +5 -2
- data/lib/rdkafka/admin.rb +238 -145
- data/lib/rdkafka/bindings.rb +61 -16
- data/lib/rdkafka/callbacks/base_handler.rb +62 -0
- data/lib/rdkafka/callbacks/create_acl_handler.rb +37 -0
- data/lib/rdkafka/callbacks/create_partitions_handler.rb +37 -0
- data/lib/rdkafka/callbacks/create_topic_handler.rb +37 -0
- data/lib/rdkafka/callbacks/delete_acl_handler.rb +42 -0
- data/lib/rdkafka/callbacks/delete_groups_handler.rb +37 -0
- data/lib/rdkafka/callbacks/delete_records_handler.rb +38 -0
- data/lib/rdkafka/callbacks/delete_topic_handler.rb +37 -0
- data/lib/rdkafka/callbacks/describe_acl_handler.rb +35 -0
- data/lib/rdkafka/callbacks/describe_configs_handler.rb +42 -0
- data/lib/rdkafka/callbacks/incremental_alter_configs_handler.rb +42 -0
- data/lib/rdkafka/callbacks/list_consumer_groups_handler.rb +39 -0
- data/lib/rdkafka/callbacks/list_offsets_handler.rb +42 -0
- data/lib/rdkafka/callbacks.rb +54 -246
- data/lib/rdkafka/config.rb +69 -50
- data/lib/rdkafka/consumer/headers.rb +19 -5
- data/lib/rdkafka/consumer/partition.rb +8 -1
- data/lib/rdkafka/consumer/topic_partition_list.rb +58 -30
- data/lib/rdkafka/consumer.rb +211 -52
- data/lib/rdkafka/defaults.rb +24 -7
- data/lib/rdkafka/helpers/list_offsets.rb +127 -0
- data/lib/rdkafka/helpers/metadata.rb +29 -0
- data/lib/rdkafka/helpers/oauth.rb +10 -5
- data/lib/rdkafka/metadata.rb +86 -19
- data/lib/rdkafka/native_kafka.rb +22 -11
- data/lib/rdkafka/producer/delivery_handle.rb +4 -5
- data/lib/rdkafka/producer/partitions_count_cache.rb +24 -38
- data/lib/rdkafka/producer.rb +81 -61
- data/lib/rdkafka/version.rb +3 -3
- data/lib/rdkafka.rb +19 -0
- data/package-lock.json +3 -3
- data/rdkafka.gemspec +1 -0
- metadata +21 -5
- data/Gemfile +0 -13
- data/Gemfile.lint +0 -14
- data/Gemfile.lint.lock +0 -123
|
@@ -6,7 +6,8 @@ module Rdkafka
|
|
|
6
6
|
class TopicPartitionList
|
|
7
7
|
# Create a topic partition list.
|
|
8
8
|
#
|
|
9
|
-
# @param data [Hash{String => nil,Partition}] The topic and partition data or nil to create an
|
|
9
|
+
# @param data [Hash{String => nil,Partition}] The topic and partition data or nil to create an
|
|
10
|
+
# empty list
|
|
10
11
|
#
|
|
11
12
|
# @return [TopicPartitionList]
|
|
12
13
|
def initialize(data = nil)
|
|
@@ -34,10 +35,12 @@ module Rdkafka
|
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
# Add a topic with optionally partitions to the list.
|
|
37
|
-
# Calling this method multiple times for the same topic will overwrite the previous
|
|
38
|
+
# Calling this method multiple times for the same topic will overwrite the previous
|
|
39
|
+
# configuraton.
|
|
38
40
|
#
|
|
39
41
|
# @param topic [String] The topic's name
|
|
40
|
-
# @param partitions [Array<Integer>, Range<Integer>, Integer] The topic's partitions or
|
|
42
|
+
# @param partitions [Array<Integer>, Range<Integer>, Integer] The topic's partitions or
|
|
43
|
+
# partition count
|
|
41
44
|
#
|
|
42
45
|
# @return [nil]
|
|
43
46
|
#
|
|
@@ -61,11 +64,12 @@ module Rdkafka
|
|
|
61
64
|
end
|
|
62
65
|
|
|
63
66
|
# Add a topic with partitions and offsets set to the list
|
|
64
|
-
# Calling this method multiple times for the same topic will overwrite the previous
|
|
67
|
+
# Calling this method multiple times for the same topic will overwrite the previous
|
|
68
|
+
# configuraton.
|
|
65
69
|
#
|
|
66
70
|
# @param topic [String] The topic's name
|
|
67
|
-
# @param partitions_with_offsets [Hash{Integer => Integer}, Array<Consumer::Partition>] The
|
|
68
|
-
# partitions and offsets (Hash) or partitions with offsets and metadata (Array)
|
|
71
|
+
# @param partitions_with_offsets [Hash{Integer => Integer}, Array<Consumer::Partition>] The
|
|
72
|
+
# topic's partitions and offsets (Hash) or partitions with offsets and metadata (Array)
|
|
69
73
|
# @return [nil]
|
|
70
74
|
def add_topic_and_partitions_with_offsets(topic, partitions_with_offsets)
|
|
71
75
|
@data[topic.to_s] = partitions_with_offsets.map do |p, o|
|
|
@@ -73,7 +77,8 @@ module Rdkafka
|
|
|
73
77
|
end
|
|
74
78
|
end
|
|
75
79
|
|
|
76
|
-
# Return a `Hash` with the topics as keys and and an array of partition information as the
|
|
80
|
+
# Return a `Hash` with the topics as keys and and an array of partition information as the
|
|
81
|
+
# value if present.
|
|
77
82
|
#
|
|
78
83
|
# @return [Hash{String => Array<Partition>,nil}]
|
|
79
84
|
def to_h
|
|
@@ -97,7 +102,8 @@ module Rdkafka
|
|
|
97
102
|
#
|
|
98
103
|
# @private
|
|
99
104
|
#
|
|
100
|
-
# @param pointer [FFI::Pointer] Optional pointer to an existing native list. Its contents will
|
|
105
|
+
# @param pointer [FFI::Pointer] Optional pointer to an existing native list. Its contents will
|
|
106
|
+
# be copied.
|
|
101
107
|
#
|
|
102
108
|
# @return [TopicPartitionList]
|
|
103
109
|
def self.from_native_tpl(pointer)
|
|
@@ -118,7 +124,12 @@ module Rdkafka
|
|
|
118
124
|
else
|
|
119
125
|
elem[:offset]
|
|
120
126
|
end
|
|
121
|
-
partition = Partition.new(
|
|
127
|
+
partition = Partition.new(
|
|
128
|
+
elem[:partition],
|
|
129
|
+
offset,
|
|
130
|
+
elem[:err],
|
|
131
|
+
elem[:metadata].null? ? nil : elem[:metadata].read_string(elem[:metadata_size])
|
|
132
|
+
)
|
|
122
133
|
partitions.push(partition)
|
|
123
134
|
data[elem[:topic]] = partitions
|
|
124
135
|
end
|
|
@@ -137,33 +148,50 @@ module Rdkafka
|
|
|
137
148
|
def to_native_tpl
|
|
138
149
|
tpl = Rdkafka::Bindings.rd_kafka_topic_partition_list_new(count)
|
|
139
150
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
partitions
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
topic,
|
|
146
|
-
p.partition
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
if p.offset
|
|
150
|
-
offset = p.offset.is_a?(Time) ? p.offset.to_f * 1_000 : p.offset
|
|
151
|
-
|
|
152
|
-
Rdkafka::Bindings.rd_kafka_topic_partition_list_set_offset(
|
|
151
|
+
begin
|
|
152
|
+
@data.each do |topic, partitions|
|
|
153
|
+
if partitions
|
|
154
|
+
partitions.each do |p|
|
|
155
|
+
ref = Rdkafka::Bindings.rd_kafka_topic_partition_list_add(
|
|
153
156
|
tpl,
|
|
154
157
|
topic,
|
|
155
|
-
p.partition
|
|
156
|
-
offset
|
|
158
|
+
p.partition
|
|
157
159
|
)
|
|
160
|
+
|
|
161
|
+
if p.metadata
|
|
162
|
+
part = Rdkafka::Bindings::TopicPartition.new(ref)
|
|
163
|
+
str_ptr = FFI::MemoryPointer.from_string(p.metadata)
|
|
164
|
+
# The metadata string is owned by librdkafka once handed over and released here:
|
|
165
|
+
# https://github.com/confluentinc/librdkafka/blob/e03d3bb91ed92a38f38d9806b8d8deffe78a1de5/src/rdkafka_partition.c#L2682C18-L2682C18
|
|
166
|
+
str_ptr.autorelease = false
|
|
167
|
+
part[:metadata] = str_ptr
|
|
168
|
+
part[:metadata_size] = p.metadata.bytesize
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
if p.offset
|
|
172
|
+
offset = p.offset.is_a?(Time) ? p.offset.to_f * 1_000 : p.offset
|
|
173
|
+
|
|
174
|
+
Rdkafka::Bindings.rd_kafka_topic_partition_list_set_offset(
|
|
175
|
+
tpl,
|
|
176
|
+
topic,
|
|
177
|
+
p.partition,
|
|
178
|
+
offset
|
|
179
|
+
)
|
|
180
|
+
end
|
|
158
181
|
end
|
|
182
|
+
else
|
|
183
|
+
Rdkafka::Bindings.rd_kafka_topic_partition_list_add(
|
|
184
|
+
tpl,
|
|
185
|
+
topic,
|
|
186
|
+
Rdkafka::Bindings::RD_KAFKA_PARTITION_UA
|
|
187
|
+
)
|
|
159
188
|
end
|
|
160
|
-
else
|
|
161
|
-
Rdkafka::Bindings.rd_kafka_topic_partition_list_add(
|
|
162
|
-
tpl,
|
|
163
|
-
topic,
|
|
164
|
-
Rdkafka::Bindings::RD_KAFKA_PARTITION_UA
|
|
165
|
-
)
|
|
166
189
|
end
|
|
190
|
+
rescue Exception
|
|
191
|
+
# The native list is caller-owned until it is handed to librdkafka. If population raises
|
|
192
|
+
# partway (e.g. an invalid partition), destroy it here so it is not leaked.
|
|
193
|
+
Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl)
|
|
194
|
+
raise
|
|
167
195
|
end
|
|
168
196
|
|
|
169
197
|
tpl
|
data/lib/rdkafka/consumer.rb
CHANGED
|
@@ -14,14 +14,48 @@ module Rdkafka
|
|
|
14
14
|
include Enumerable
|
|
15
15
|
include Helpers::Time
|
|
16
16
|
include Helpers::OAuth
|
|
17
|
+
include Helpers::Metadata
|
|
18
|
+
include Helpers::ListOffsets
|
|
17
19
|
|
|
18
20
|
# @private
|
|
19
21
|
# @param native_kafka [NativeKafka] wrapper around the native Kafka consumer handle
|
|
20
22
|
def initialize(native_kafka)
|
|
21
23
|
@native_kafka = native_kafka
|
|
24
|
+
# Single-element holder shared with the GC finalizer so it can destroy the lazily created
|
|
25
|
+
# consumer queue without capturing `self` (capturing the consumer in its own finalizer would
|
|
26
|
+
# pin it and prevent it from ever being collected).
|
|
27
|
+
@consumer_queue_holder = []
|
|
28
|
+
|
|
29
|
+
# Makes sure the consumer is closed (consumer queue destroyed and native client destroyed)
|
|
30
|
+
# before it gets GCed by Ruby.
|
|
31
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(native_kafka, @consumer_queue_holder))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Builds the GC finalizer for a consumer. It mirrors {#close}: close the consumer, destroy the
|
|
35
|
+
# consumer-queue reference, then destroy the native client. The default `NativeKafka#finalizer`
|
|
36
|
+
# went straight to `rd_kafka_destroy`, leaving the consumer-queue reference (from
|
|
37
|
+
# `rd_kafka_queue_get_consumer`, taken by `poll_batch`) dangling - which can make
|
|
38
|
+
# `rd_kafka_destroy` block inside the finalizer (process hang at GC/shutdown) or leak the
|
|
39
|
+
# handle.
|
|
40
|
+
#
|
|
41
|
+
# @private
|
|
42
|
+
# @param native_kafka [NativeKafka] the wrapped native client
|
|
43
|
+
# @param queue_holder [Array] single-element holder carrying the consumer queue pointer
|
|
44
|
+
# (or empty)
|
|
45
|
+
# @return [Proc] finalizer proc that must not reference the consumer instance
|
|
46
|
+
def self.finalizer(native_kafka, queue_holder)
|
|
47
|
+
proc do
|
|
48
|
+
next if native_kafka.closed?
|
|
49
|
+
|
|
50
|
+
native_kafka.synchronize do |inner|
|
|
51
|
+
Rdkafka::Bindings.rd_kafka_consumer_close(inner)
|
|
52
|
+
|
|
53
|
+
queue = queue_holder[0]
|
|
54
|
+
Rdkafka::Bindings.rd_kafka_queue_destroy(queue) if queue
|
|
55
|
+
end
|
|
22
56
|
|
|
23
|
-
|
|
24
|
-
|
|
57
|
+
native_kafka.close
|
|
58
|
+
end
|
|
25
59
|
end
|
|
26
60
|
|
|
27
61
|
# Starts the native Kafka polling thread and kicks off the init polling
|
|
@@ -93,11 +127,6 @@ module Rdkafka
|
|
|
93
127
|
# @return [nil]
|
|
94
128
|
# @raise [Rdkafka::ClosedConsumerError] if called on a closed consumer
|
|
95
129
|
#
|
|
96
|
-
# @note This method holds the inner lock until the queue is empty or `:stop` is returned.
|
|
97
|
-
# Other consumer operations will wait until this method returns.
|
|
98
|
-
# @note This method is thread-safe as it uses @native_kafka.with_inner synchronization
|
|
99
|
-
# @note Do NOT use this if `consumer_poll_set` was set to `true`
|
|
100
|
-
#
|
|
101
130
|
# @example Drain all pending events
|
|
102
131
|
# consumer.events_poll_nb_each { |_count| }
|
|
103
132
|
#
|
|
@@ -106,6 +135,10 @@ module Rdkafka
|
|
|
106
135
|
# consumer.events_poll_nb_each do |_count|
|
|
107
136
|
# :stop if monotonic_now >= deadline
|
|
108
137
|
# end
|
|
138
|
+
# @note This method holds the inner lock until the queue is empty or `:stop` is returned.
|
|
139
|
+
# Other consumer operations will wait until this method returns.
|
|
140
|
+
# @note This method is thread-safe as it uses @native_kafka.with_inner synchronization
|
|
141
|
+
# @note Do NOT use this if `consumer_poll_set` was set to `true`
|
|
109
142
|
def events_poll_nb_each
|
|
110
143
|
closed_consumer_check(__method__)
|
|
111
144
|
|
|
@@ -131,14 +164,6 @@ module Rdkafka
|
|
|
131
164
|
# @raise [Rdkafka::ClosedConsumerError] if called on a closed consumer
|
|
132
165
|
# @raise [Rdkafka::RdkafkaError] if a Kafka error occurs while polling
|
|
133
166
|
#
|
|
134
|
-
# @note This method uses `rd_kafka_consumer_poll` to fetch messages, unlike
|
|
135
|
-
# `events_poll_nb_each` which uses `rd_kafka_poll` for event callbacks (delivery reports,
|
|
136
|
-
# statistics, etc.). For consumers, use this method to receive messages and
|
|
137
|
-
# `events_poll_nb_each` for processing background events.
|
|
138
|
-
# @note This method holds the inner lock for the duration. Other consumer operations
|
|
139
|
-
# will wait until this method returns.
|
|
140
|
-
# @note Timeout/max_messages logic should be implemented by the caller
|
|
141
|
-
#
|
|
142
167
|
# @example Process messages until queue is empty
|
|
143
168
|
# consumer.poll_nb_each do |message|
|
|
144
169
|
# process(message)
|
|
@@ -151,6 +176,13 @@ module Rdkafka
|
|
|
151
176
|
# count += 1
|
|
152
177
|
# :stop if count >= 10
|
|
153
178
|
# end
|
|
179
|
+
# @note This method uses `rd_kafka_consumer_poll` to fetch messages, unlike
|
|
180
|
+
# `events_poll_nb_each` which uses `rd_kafka_poll` for event callbacks (delivery reports,
|
|
181
|
+
# statistics, etc.). For consumers, use this method to receive messages and
|
|
182
|
+
# `events_poll_nb_each` for processing background events.
|
|
183
|
+
# @note This method holds the inner lock for the duration. Other consumer operations
|
|
184
|
+
# will wait until this method returns.
|
|
185
|
+
# @note Timeout/max_messages logic should be implemented by the caller
|
|
154
186
|
def poll_nb_each
|
|
155
187
|
closed_consumer_check(__method__)
|
|
156
188
|
|
|
@@ -187,6 +219,7 @@ module Rdkafka
|
|
|
187
219
|
if @consumer_queue
|
|
188
220
|
Rdkafka::Bindings.rd_kafka_queue_destroy(@consumer_queue)
|
|
189
221
|
@consumer_queue = nil
|
|
222
|
+
@consumer_queue_holder[0] = nil
|
|
190
223
|
end
|
|
191
224
|
end
|
|
192
225
|
|
|
@@ -414,14 +447,18 @@ module Rdkafka
|
|
|
414
447
|
end
|
|
415
448
|
|
|
416
449
|
# Return the current positions (offsets) for topics and partitions.
|
|
417
|
-
# The offset field of each requested partition will be set to the offset of the last consumed
|
|
450
|
+
# The offset field of each requested partition will be set to the offset of the last consumed
|
|
451
|
+
# message + 1, or nil in case there was no previous message.
|
|
418
452
|
#
|
|
419
|
-
# @param list [TopicPartitionList, nil] The topic with partitions to get the offsets for or nil
|
|
453
|
+
# @param list [TopicPartitionList, nil] The topic with partitions to get the offsets for or nil
|
|
454
|
+
# to use the current subscription.
|
|
420
455
|
#
|
|
421
456
|
# @return [TopicPartitionList]
|
|
422
457
|
#
|
|
423
458
|
# @raise [RdkafkaError] When getting the positions fails.
|
|
424
459
|
def position(list = nil)
|
|
460
|
+
closed_consumer_check(__method__)
|
|
461
|
+
|
|
425
462
|
if list.nil?
|
|
426
463
|
list = assignment
|
|
427
464
|
elsif !list.is_a?(TopicPartitionList)
|
|
@@ -481,39 +518,76 @@ module Rdkafka
|
|
|
481
518
|
# possible to create one yourself, in this case you have to provide a list that
|
|
482
519
|
# already contains all the partitions you need the lag for.
|
|
483
520
|
#
|
|
521
|
+
# The end offsets of all requested partitions are fetched in a single batched
|
|
522
|
+
# {#list_offsets} query - librdkafka fans it out to the involved partition leaders
|
|
523
|
+
# internally and concurrently - instead of one blocking {#query_watermark_offsets}
|
|
524
|
+
# broker roundtrip per partition.
|
|
525
|
+
#
|
|
484
526
|
# @param topic_partition_list [TopicPartitionList] The list to calculate lag for.
|
|
485
|
-
# @param watermark_timeout_ms [Integer] The timeout for
|
|
527
|
+
# @param watermark_timeout_ms [Integer] The timeout for the batched end-offsets query.
|
|
486
528
|
# @return [Hash{String => Hash{Integer => Integer}}] A hash containing all topics with the lag
|
|
487
529
|
# per partition
|
|
530
|
+
# @raise [ClosedConsumerError] when the consumer is closed
|
|
488
531
|
# @raise [RdkafkaError] When querying the broker fails.
|
|
489
532
|
def lag(topic_partition_list, watermark_timeout_ms = Defaults::CONSUMER_LAG_TIMEOUT_MS)
|
|
533
|
+
closed_consumer_check(__method__)
|
|
534
|
+
|
|
490
535
|
out = {}
|
|
536
|
+
request = {}
|
|
537
|
+
partitions_by_topic = topic_partition_list.to_h
|
|
538
|
+
|
|
539
|
+
partitions_by_topic.each do |topic, partitions|
|
|
540
|
+
out[topic] = {}
|
|
491
541
|
|
|
492
|
-
topic_partition_list.to_h.each do |topic, partitions|
|
|
493
|
-
# Query high watermarks for this topic's partitions
|
|
494
|
-
# and compare to the offset in the list.
|
|
495
|
-
topic_out = {}
|
|
496
542
|
partitions.each do |p|
|
|
497
543
|
next if p.offset.nil?
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
p.partition,
|
|
501
|
-
watermark_timeout_ms
|
|
502
|
-
)
|
|
503
|
-
topic_out[p.partition] = high - p.offset
|
|
544
|
+
|
|
545
|
+
(request[topic] ||= []) << { partition: p.partition, offset: :latest }
|
|
504
546
|
end
|
|
505
|
-
out[topic] = topic_out
|
|
506
547
|
end
|
|
548
|
+
|
|
549
|
+
return out if request.empty?
|
|
550
|
+
|
|
551
|
+
report = begin
|
|
552
|
+
# The isolation level is forwarded so the end offsets match what the old per-partition
|
|
553
|
+
# watermark query returned: librdkafka resolves that query with the consumer's configured
|
|
554
|
+
# `isolation.level` (LSO for the default read_committed), while the admin-style batched
|
|
555
|
+
# query would otherwise default to read_uncommitted (true high watermark).
|
|
556
|
+
list_offsets(request, isolation_level: isolation_level)
|
|
557
|
+
.wait(max_wait_timeout_ms: watermark_timeout_ms)
|
|
558
|
+
rescue AbstractHandle::WaitTimeoutError
|
|
559
|
+
# Keep the pre-batching contract: a slow broker surfaced as a timed-out RdkafkaError
|
|
560
|
+
# from the per-partition watermark query, not as a handle wait timeout.
|
|
561
|
+
raise RdkafkaError.new(
|
|
562
|
+
Rdkafka::Bindings::RD_KAFKA_RESP_ERR__TIMED_OUT,
|
|
563
|
+
"Error querying watermark offsets of '#{request.keys.join(", ")}'"
|
|
564
|
+
)
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
end_offsets = {}
|
|
568
|
+
report.offsets.each do |result|
|
|
569
|
+
(end_offsets[result[:topic]] ||= {})[result[:partition]] = result[:offset]
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
partitions_by_topic.each do |topic, partitions|
|
|
573
|
+
partitions.each do |p|
|
|
574
|
+
next if p.offset.nil?
|
|
575
|
+
|
|
576
|
+
out[topic][p.partition] = end_offsets.fetch(topic).fetch(p.partition) - p.offset
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
507
580
|
out
|
|
508
581
|
end
|
|
509
582
|
|
|
510
583
|
# Returns the ClusterId as reported in broker metadata.
|
|
511
584
|
#
|
|
585
|
+
# @param timeout_ms [Integer] timeout in milliseconds to wait for the cluster id
|
|
512
586
|
# @return [String, nil]
|
|
513
|
-
def cluster_id
|
|
587
|
+
def cluster_id(timeout_ms = Defaults::CONSUMER_CLUSTER_ID_TIMEOUT_MS)
|
|
514
588
|
closed_consumer_check(__method__)
|
|
515
589
|
@native_kafka.with_inner do |inner|
|
|
516
|
-
Rdkafka::Bindings.rd_kafka_clusterid(inner)
|
|
590
|
+
read_and_free_native_string(inner, Rdkafka::Bindings.rd_kafka_clusterid(inner, timeout_ms))
|
|
517
591
|
end
|
|
518
592
|
end
|
|
519
593
|
|
|
@@ -525,7 +599,7 @@ module Rdkafka
|
|
|
525
599
|
def member_id
|
|
526
600
|
closed_consumer_check(__method__)
|
|
527
601
|
@native_kafka.with_inner do |inner|
|
|
528
|
-
Rdkafka::Bindings.rd_kafka_memberid(inner)
|
|
602
|
+
read_and_free_native_string(inner, Rdkafka::Bindings.rd_kafka_memberid(inner))
|
|
529
603
|
end
|
|
530
604
|
end
|
|
531
605
|
|
|
@@ -534,16 +608,34 @@ module Rdkafka
|
|
|
534
608
|
# When using this `enable.auto.offset.store` should be set to `false` in the config.
|
|
535
609
|
#
|
|
536
610
|
# @param message [Rdkafka::Consumer::Message] The message which offset will be stored
|
|
611
|
+
# @param metadata [String, nil] commit metadata string to store alongside the offset
|
|
537
612
|
# @return [nil]
|
|
538
613
|
# @raise [RdkafkaError] When storing the offset fails
|
|
539
|
-
def store_offset(message)
|
|
614
|
+
def store_offset(message, metadata = nil)
|
|
540
615
|
closed_consumer_check(__method__)
|
|
541
616
|
|
|
542
617
|
list = TopicPartitionList.new
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
618
|
+
|
|
619
|
+
# For metadata aware commits we build the partition reference directly to save on
|
|
620
|
+
# objects allocations
|
|
621
|
+
if metadata
|
|
622
|
+
list.add_topic_and_partitions_with_offsets(
|
|
623
|
+
message.topic,
|
|
624
|
+
[
|
|
625
|
+
Consumer::Partition.new(
|
|
626
|
+
message.partition,
|
|
627
|
+
message.offset + 1,
|
|
628
|
+
0,
|
|
629
|
+
metadata
|
|
630
|
+
)
|
|
631
|
+
]
|
|
632
|
+
)
|
|
633
|
+
else
|
|
634
|
+
list.add_topic_and_partitions_with_offsets(
|
|
635
|
+
message.topic,
|
|
636
|
+
message.partition => message.offset + 1
|
|
637
|
+
)
|
|
638
|
+
end
|
|
547
639
|
|
|
548
640
|
tpl = list.to_native_tpl
|
|
549
641
|
|
|
@@ -572,8 +664,7 @@ module Rdkafka
|
|
|
572
664
|
end
|
|
573
665
|
|
|
574
666
|
# Seek to a particular message by providing the topic, partition and offset.
|
|
575
|
-
# The next poll on the topic/partition will return the
|
|
576
|
-
# message at the given offset.
|
|
667
|
+
# The next poll on the topic/partition will return the message at the given offset.
|
|
577
668
|
#
|
|
578
669
|
# @param topic [String] The topic in which to seek
|
|
579
670
|
# @param partition [Integer] The partition number to seek
|
|
@@ -641,8 +732,7 @@ module Rdkafka
|
|
|
641
732
|
|
|
642
733
|
# Manually commit the current offsets of this consumer.
|
|
643
734
|
#
|
|
644
|
-
# To use this set `enable.auto.commit`to `false` to disable automatic triggering
|
|
645
|
-
# of commits.
|
|
735
|
+
# To use this set `enable.auto.commit`to `false` to disable automatic triggering of commits.
|
|
646
736
|
#
|
|
647
737
|
# If `enable.auto.offset.store` is set to `true` the offset of the last consumed
|
|
648
738
|
# message for every partition is used. If set to `false` you can use {store_offset} to
|
|
@@ -796,7 +886,7 @@ module Rdkafka
|
|
|
796
886
|
# returns without further waiting.
|
|
797
887
|
#
|
|
798
888
|
# Error events (e.g. `:partition_eof`) are returned inline as {RdkafkaError} objects
|
|
799
|
-
# rather than raised, so callers receive the complete batch
|
|
889
|
+
# rather than raised, so callers receive the complete batch - both messages and errors -
|
|
800
890
|
# and can decide how to handle each. This is particularly useful when multiple partitions
|
|
801
891
|
# signal EOF simultaneously: all signals appear in the returned array rather than only
|
|
802
892
|
# the first one being raised and the rest silently discarded.
|
|
@@ -841,8 +931,17 @@ module Rdkafka
|
|
|
841
931
|
next
|
|
842
932
|
end
|
|
843
933
|
|
|
844
|
-
|
|
845
|
-
|
|
934
|
+
begin
|
|
935
|
+
results << Rdkafka::Consumer::Message.new(native_message)
|
|
936
|
+
rescue Rdkafka::RdkafkaError => e
|
|
937
|
+
# A message that fails to build (e.g. a header read error) is surfaced inline as an
|
|
938
|
+
# error event rather than discarding the whole batch - including the messages already
|
|
939
|
+
# built - and raising, which silently lost them once their offsets had been stored.
|
|
940
|
+
results << e
|
|
941
|
+
ensure
|
|
942
|
+
Rdkafka::Bindings.rd_kafka_message_destroy(ptr)
|
|
943
|
+
end
|
|
944
|
+
|
|
846
945
|
i += 1
|
|
847
946
|
end
|
|
848
947
|
ensure
|
|
@@ -862,15 +961,15 @@ module Rdkafka
|
|
|
862
961
|
# particularly useful in fiber scheduler contexts where GVL release/reacquire
|
|
863
962
|
# overhead is wasteful since we don't expect to wait.
|
|
864
963
|
#
|
|
964
|
+
# @param timeout_ms [Integer] Timeout waiting for the first message
|
|
965
|
+
# (default: 0 for non-blocking)
|
|
966
|
+
# @param max_items [Integer] Maximum number of messages to return per call
|
|
967
|
+
# @return [Array<Message, RdkafkaError>] Batch of messages and/or error events in arrival order
|
|
968
|
+
# @raise [ClosedConsumerError] When called on a closed consumer
|
|
865
969
|
# @note Since the GVL is not released, a non-zero timeout_ms will block all Ruby
|
|
866
970
|
# threads/fibers for the duration. Use {#poll_batch} if you need a blocking wait.
|
|
867
971
|
#
|
|
868
972
|
# Error events are returned inline as {RdkafkaError} objects; see {#poll_batch} for details.
|
|
869
|
-
#
|
|
870
|
-
# @param timeout_ms [Integer] Timeout waiting for the first message (default: 0 for non-blocking)
|
|
871
|
-
# @param max_items [Integer] Maximum number of messages to return per call
|
|
872
|
-
# @return [Array<Message, RdkafkaError>] Batch of messages and/or error events in arrival order
|
|
873
|
-
# @raise [ClosedConsumerError] When called on a closed consumer
|
|
874
973
|
def poll_batch_nb(timeout_ms = 0, max_items: 100)
|
|
875
974
|
closed_consumer_check(__method__)
|
|
876
975
|
|
|
@@ -907,8 +1006,17 @@ module Rdkafka
|
|
|
907
1006
|
next
|
|
908
1007
|
end
|
|
909
1008
|
|
|
910
|
-
|
|
911
|
-
|
|
1009
|
+
begin
|
|
1010
|
+
results << Rdkafka::Consumer::Message.new(native_message)
|
|
1011
|
+
rescue Rdkafka::RdkafkaError => e
|
|
1012
|
+
# A message that fails to build (e.g. a header read error) is surfaced inline as an
|
|
1013
|
+
# error event rather than discarding the whole batch - including the messages already
|
|
1014
|
+
# built - and raising, which silently lost them once their offsets had been stored.
|
|
1015
|
+
results << e
|
|
1016
|
+
ensure
|
|
1017
|
+
Rdkafka::Bindings.rd_kafka_message_destroy(ptr)
|
|
1018
|
+
end
|
|
1019
|
+
|
|
912
1020
|
i += 1
|
|
913
1021
|
end
|
|
914
1022
|
ensure
|
|
@@ -994,12 +1102,63 @@ module Rdkafka
|
|
|
994
1102
|
def closed_consumer_check(method)
|
|
995
1103
|
raise Rdkafka::ClosedConsumerError.new(method) if closed?
|
|
996
1104
|
end
|
|
1105
|
+
alias_method :closed_check, :closed_consumer_check
|
|
1106
|
+
|
|
1107
|
+
# Reads a librdkafka-allocated string and frees the underlying native buffer.
|
|
1108
|
+
#
|
|
1109
|
+
# librdkafka returns heap-allocated, caller-owned strings from functions like
|
|
1110
|
+
# `rd_kafka_clusterid`/`rd_kafka_memberid`. The buffer must be released with
|
|
1111
|
+
# `rd_kafka_mem_free`, otherwise it leaks on every call.
|
|
1112
|
+
#
|
|
1113
|
+
# @param inner [FFI::Pointer] the native client handle used to allocate the string
|
|
1114
|
+
# @param ptr [FFI::Pointer] the native string pointer (may be null)
|
|
1115
|
+
# @return [String, nil] the copied Ruby string, or nil when the pointer is null
|
|
1116
|
+
def read_and_free_native_string(inner, ptr)
|
|
1117
|
+
return nil if ptr.null?
|
|
1118
|
+
|
|
1119
|
+
ptr.read_string
|
|
1120
|
+
ensure
|
|
1121
|
+
Rdkafka::Bindings.rd_kafka_mem_free(inner, ptr) unless ptr.null?
|
|
1122
|
+
end
|
|
1123
|
+
|
|
1124
|
+
# Reads this consumer's effective `isolation.level` from the live librdkafka configuration
|
|
1125
|
+
# and maps it to the numeric isolation level constant. Memoized: the value cannot change
|
|
1126
|
+
# after client creation.
|
|
1127
|
+
#
|
|
1128
|
+
# @return [Integer] `RD_KAFKA_ISOLATION_LEVEL_READ_COMMITTED` or
|
|
1129
|
+
# `RD_KAFKA_ISOLATION_LEVEL_READ_UNCOMMITTED`
|
|
1130
|
+
# @raise [Rdkafka::Config::ConfigError] when the property cannot be read
|
|
1131
|
+
def isolation_level
|
|
1132
|
+
@isolation_level ||= @native_kafka.with_inner do |inner|
|
|
1133
|
+
conf = Rdkafka::Bindings.rd_kafka_conf(inner)
|
|
1134
|
+
|
|
1135
|
+
size_ptr = Rdkafka::Bindings::SizePtr.new
|
|
1136
|
+
size_ptr[:value] = 64
|
|
1137
|
+
value_ptr = FFI::MemoryPointer.new(:char, 64)
|
|
1138
|
+
|
|
1139
|
+
result = Rdkafka::Bindings.rd_kafka_conf_get(conf, "isolation.level", value_ptr, size_ptr)
|
|
1140
|
+
|
|
1141
|
+
if result != :config_ok
|
|
1142
|
+
raise Rdkafka::Config::ConfigError.new("Could not read isolation.level: #{result}")
|
|
1143
|
+
end
|
|
1144
|
+
|
|
1145
|
+
if value_ptr.read_string == "read_committed"
|
|
1146
|
+
Rdkafka::Bindings::RD_KAFKA_ISOLATION_LEVEL_READ_COMMITTED
|
|
1147
|
+
else
|
|
1148
|
+
Rdkafka::Bindings::RD_KAFKA_ISOLATION_LEVEL_READ_UNCOMMITTED
|
|
1149
|
+
end
|
|
1150
|
+
end
|
|
1151
|
+
end
|
|
997
1152
|
|
|
998
1153
|
# Returns the consumer queue pointer, lazily initialized
|
|
999
1154
|
# @return [FFI::Pointer] consumer queue handle
|
|
1000
1155
|
def consumer_queue
|
|
1001
1156
|
@consumer_queue ||= @native_kafka.with_inner do |inner|
|
|
1002
|
-
Rdkafka::Bindings.rd_kafka_queue_get_consumer(inner)
|
|
1157
|
+
queue = Rdkafka::Bindings.rd_kafka_queue_get_consumer(inner)
|
|
1158
|
+
# Share the pointer with the finalizer so it is destroyed even if the consumer is GC'd
|
|
1159
|
+
# without an explicit close.
|
|
1160
|
+
@consumer_queue_holder[0] = queue
|
|
1161
|
+
queue
|
|
1003
1162
|
end
|
|
1004
1163
|
end
|
|
1005
1164
|
|
data/lib/rdkafka/defaults.rb
CHANGED
|
@@ -3,19 +3,17 @@
|
|
|
3
3
|
module Rdkafka
|
|
4
4
|
# Default timeout and timing values used throughout rdkafka-ruby.
|
|
5
5
|
#
|
|
6
|
-
# All timeout values can be overridden per-call via method parameters.
|
|
7
|
-
#
|
|
8
|
-
# the default values used across the library.
|
|
9
|
-
#
|
|
10
|
-
# @note These are rdkafka-ruby defaults, not librdkafka configuration options.
|
|
11
|
-
# For librdkafka options, see:
|
|
12
|
-
# https://github.com/confluentinc/librdkafka/blob/master/CONFIGURATION.md
|
|
6
|
+
# All timeout values can be overridden per-call via method parameters. These constants provide
|
|
7
|
+
# a central place to understand and reference the default values used across the library.
|
|
13
8
|
#
|
|
14
9
|
# @example Overriding a timeout per-call
|
|
15
10
|
# consumer.committed(timeout_ms: 5_000) # Use 5 seconds instead of default 2 seconds
|
|
16
11
|
#
|
|
17
12
|
# @example Checking the default value
|
|
18
13
|
# Rdkafka::Defaults::CONSUMER_COMMITTED_TIMEOUT_MS # => 2000
|
|
14
|
+
# @note These are rdkafka-ruby defaults, not librdkafka configuration options.
|
|
15
|
+
# For librdkafka options, see:
|
|
16
|
+
# https://github.com/confluentinc/librdkafka/blob/master/CONFIGURATION.md
|
|
19
17
|
module Defaults
|
|
20
18
|
# Consumer timeouts (in milliseconds)
|
|
21
19
|
|
|
@@ -23,6 +21,10 @@ module Rdkafka
|
|
|
23
21
|
# @see Consumer#committed
|
|
24
22
|
CONSUMER_COMMITTED_TIMEOUT_MS = 2_000
|
|
25
23
|
|
|
24
|
+
# Default timeout for fetching the cluster id
|
|
25
|
+
# @see Consumer#cluster_id
|
|
26
|
+
CONSUMER_CLUSTER_ID_TIMEOUT_MS = 1_000
|
|
27
|
+
|
|
26
28
|
# Default timeout for querying watermark offsets
|
|
27
29
|
# @see Consumer#query_watermark_offsets
|
|
28
30
|
CONSUMER_QUERY_WATERMARK_TIMEOUT_MS = 1_000
|
|
@@ -91,6 +93,16 @@ module Rdkafka
|
|
|
91
93
|
# @see Metadata#initialize
|
|
92
94
|
METADATA_RETRY_BACKOFF_BASE_MS = 100
|
|
93
95
|
|
|
96
|
+
# Maximum backoff time between metadata retries. Caps the exponential backoff so a long retry
|
|
97
|
+
# sequence against an unhealthy cluster cannot block the calling thread for minutes.
|
|
98
|
+
# @see Metadata#initialize
|
|
99
|
+
METADATA_RETRY_BACKOFF_MAX_MS = 1_000
|
|
100
|
+
|
|
101
|
+
# Soft wall-clock budget for the whole metadata retry loop; past it (and past
|
|
102
|
+
# METADATA_MIN_ATTEMPTS) the loop stops so a synchronous fetch cannot block the caller for long
|
|
103
|
+
# @see Metadata#initialize
|
|
104
|
+
METADATA_RETRY_BUDGET_MS = 5_000
|
|
105
|
+
|
|
94
106
|
# Cache settings (in milliseconds)
|
|
95
107
|
|
|
96
108
|
# Default time-to-live for cached partition counts
|
|
@@ -102,5 +114,10 @@ module Rdkafka
|
|
|
102
114
|
# Maximum number of metadata fetch retry attempts
|
|
103
115
|
# @see Metadata#initialize
|
|
104
116
|
METADATA_MAX_RETRIES = 10
|
|
117
|
+
|
|
118
|
+
# Minimum metadata fetch attempts before the retry budget may end the loop, so a slow broker
|
|
119
|
+
# (whose requests each consume the full timeout) still gets a few tries
|
|
120
|
+
# @see Metadata#initialize
|
|
121
|
+
METADATA_MIN_ATTEMPTS = 3
|
|
105
122
|
end
|
|
106
123
|
end
|