rdkafka 0.22.2 → 0.29.0
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 +101 -3
- data/README.md +21 -14
- data/Rakefile +21 -21
- data/bin/verify_kafka_warnings +39 -0
- data/dist/{librdkafka-2.8.0.tar.gz → librdkafka-2.14.2.tar.gz} +0 -0
- data/docker-compose-ssl.yml +35 -0
- data/docker-compose.yml +2 -2
- data/ext/Rakefile +27 -27
- data/lib/rdkafka/abstract_handle.rb +54 -7
- data/lib/rdkafka/admin/acl_binding_result.rb +5 -5
- data/lib/rdkafka/admin/config_binding_result.rb +2 -2
- data/lib/rdkafka/admin/config_resource_binding_result.rb +1 -0
- data/lib/rdkafka/admin/create_acl_handle.rb +10 -6
- data/lib/rdkafka/admin/create_acl_report.rb +3 -2
- data/lib/rdkafka/admin/create_partitions_handle.rb +8 -7
- data/lib/rdkafka/admin/create_partitions_report.rb +1 -0
- data/lib/rdkafka/admin/create_topic_handle.rb +8 -7
- data/lib/rdkafka/admin/create_topic_report.rb +3 -0
- data/lib/rdkafka/admin/delete_acl_handle.rb +9 -8
- data/lib/rdkafka/admin/delete_acl_report.rb +5 -3
- data/lib/rdkafka/admin/delete_groups_handle.rb +10 -7
- data/lib/rdkafka/admin/delete_groups_report.rb +3 -0
- data/lib/rdkafka/admin/delete_topic_handle.rb +8 -7
- data/lib/rdkafka/admin/delete_topic_report.rb +3 -0
- data/lib/rdkafka/admin/describe_acl_handle.rb +9 -8
- data/lib/rdkafka/admin/describe_acl_report.rb +5 -3
- data/lib/rdkafka/admin/describe_configs_handle.rb +7 -10
- data/lib/rdkafka/admin/describe_configs_report.rb +8 -6
- data/lib/rdkafka/admin/incremental_alter_configs_handle.rb +7 -10
- data/lib/rdkafka/admin/incremental_alter_configs_report.rb +8 -6
- data/lib/rdkafka/admin/list_offsets_handle.rb +31 -0
- data/lib/rdkafka/admin/list_offsets_report.rb +51 -0
- data/lib/rdkafka/admin.rb +357 -159
- data/lib/rdkafka/bindings.rb +205 -111
- 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_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_offsets_handler.rb +42 -0
- data/lib/rdkafka/callbacks.rb +125 -216
- data/lib/rdkafka/config.rb +126 -66
- data/lib/rdkafka/consumer/headers.rb +22 -7
- data/lib/rdkafka/consumer/message.rb +12 -11
- data/lib/rdkafka/consumer/partition.rb +15 -4
- data/lib/rdkafka/consumer/topic_partition_list.rb +64 -38
- data/lib/rdkafka/consumer.rb +513 -59
- data/lib/rdkafka/defaults.rb +125 -0
- data/lib/rdkafka/error.rb +40 -14
- data/lib/rdkafka/helpers/metadata.rb +29 -0
- data/lib/rdkafka/helpers/oauth.rb +45 -13
- data/lib/rdkafka/helpers/time.rb +5 -0
- data/lib/rdkafka/metadata.rb +128 -37
- data/lib/rdkafka/native_kafka.rb +91 -8
- data/lib/rdkafka/producer/delivery_handle.rb +6 -6
- data/lib/rdkafka/producer/delivery_report.rb +10 -6
- data/lib/rdkafka/producer/partitions_count_cache.rb +51 -55
- data/lib/rdkafka/producer.rb +193 -95
- data/lib/rdkafka/version.rb +6 -3
- data/lib/rdkafka.rb +15 -0
- data/package-lock.json +331 -0
- data/package.json +9 -0
- data/rdkafka.gemspec +58 -36
- data/renovate.json +39 -23
- metadata +39 -125
- data/.github/CODEOWNERS +0 -3
- data/.github/FUNDING.yml +0 -1
- data/.github/workflows/ci_linux_x86_64_gnu.yml +0 -271
- data/.github/workflows/ci_linux_x86_64_musl.yml +0 -194
- data/.github/workflows/ci_macos_arm64.yml +0 -284
- data/.github/workflows/push_linux_x86_64_gnu.yml +0 -65
- data/.github/workflows/push_linux_x86_64_musl.yml +0 -79
- data/.github/workflows/push_macos_arm64.yml +0 -54
- data/.github/workflows/push_ruby.yml +0 -37
- data/.github/workflows/verify-action-pins.yml +0 -16
- data/.gitignore +0 -14
- data/.rspec +0 -2
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.yardopts +0 -2
- data/Gemfile +0 -5
- data/ext/README.md +0 -19
- data/ext/build_common.sh +0 -361
- data/ext/build_linux_x86_64_gnu.sh +0 -306
- data/ext/build_linux_x86_64_musl.sh +0 -763
- data/ext/build_macos_arm64.sh +0 -550
- data/spec/rdkafka/abstract_handle_spec.rb +0 -117
- data/spec/rdkafka/admin/create_acl_handle_spec.rb +0 -56
- data/spec/rdkafka/admin/create_acl_report_spec.rb +0 -18
- data/spec/rdkafka/admin/create_topic_handle_spec.rb +0 -52
- data/spec/rdkafka/admin/create_topic_report_spec.rb +0 -16
- data/spec/rdkafka/admin/delete_acl_handle_spec.rb +0 -85
- data/spec/rdkafka/admin/delete_acl_report_spec.rb +0 -72
- data/spec/rdkafka/admin/delete_topic_handle_spec.rb +0 -52
- data/spec/rdkafka/admin/delete_topic_report_spec.rb +0 -16
- data/spec/rdkafka/admin/describe_acl_handle_spec.rb +0 -85
- data/spec/rdkafka/admin/describe_acl_report_spec.rb +0 -73
- data/spec/rdkafka/admin_spec.rb +0 -971
- data/spec/rdkafka/bindings_spec.rb +0 -199
- data/spec/rdkafka/callbacks_spec.rb +0 -20
- data/spec/rdkafka/config_spec.rb +0 -258
- data/spec/rdkafka/consumer/headers_spec.rb +0 -73
- data/spec/rdkafka/consumer/message_spec.rb +0 -139
- data/spec/rdkafka/consumer/partition_spec.rb +0 -57
- data/spec/rdkafka/consumer/topic_partition_list_spec.rb +0 -248
- data/spec/rdkafka/consumer_spec.rb +0 -1274
- data/spec/rdkafka/error_spec.rb +0 -89
- data/spec/rdkafka/metadata_spec.rb +0 -79
- data/spec/rdkafka/native_kafka_spec.rb +0 -130
- data/spec/rdkafka/producer/delivery_handle_spec.rb +0 -45
- data/spec/rdkafka/producer/delivery_report_spec.rb +0 -25
- data/spec/rdkafka/producer/partitions_count_cache_spec.rb +0 -359
- data/spec/rdkafka/producer_spec.rb +0 -1345
- data/spec/spec_helper.rb +0 -195
data/lib/rdkafka/consumer.rb
CHANGED
|
@@ -14,10 +14,45 @@ module Rdkafka
|
|
|
14
14
|
include Enumerable
|
|
15
15
|
include Helpers::Time
|
|
16
16
|
include Helpers::OAuth
|
|
17
|
+
include Helpers::Metadata
|
|
17
18
|
|
|
18
19
|
# @private
|
|
20
|
+
# @param native_kafka [NativeKafka] wrapper around the native Kafka consumer handle
|
|
19
21
|
def initialize(native_kafka)
|
|
20
22
|
@native_kafka = native_kafka
|
|
23
|
+
# Single-element holder shared with the GC finalizer so it can destroy the lazily created
|
|
24
|
+
# consumer queue without capturing `self` (capturing the consumer in its own finalizer would
|
|
25
|
+
# pin it and prevent it from ever being collected).
|
|
26
|
+
@consumer_queue_holder = []
|
|
27
|
+
|
|
28
|
+
# Makes sure the consumer is closed (consumer queue destroyed and native client destroyed)
|
|
29
|
+
# before it gets GCed by Ruby.
|
|
30
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(native_kafka, @consumer_queue_holder))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Builds the GC finalizer for a consumer. It mirrors {#close}: close the consumer, destroy the
|
|
34
|
+
# consumer-queue reference, then destroy the native client. The default `NativeKafka#finalizer`
|
|
35
|
+
# went straight to `rd_kafka_destroy`, leaving the consumer-queue reference (from
|
|
36
|
+
# `rd_kafka_queue_get_consumer`, taken by `poll_batch`) dangling - which can make
|
|
37
|
+
# `rd_kafka_destroy` block inside the finalizer (process hang at GC/shutdown) or leak the handle.
|
|
38
|
+
#
|
|
39
|
+
# @private
|
|
40
|
+
# @param native_kafka [NativeKafka] the wrapped native client
|
|
41
|
+
# @param queue_holder [Array] single-element holder carrying the consumer queue pointer (or empty)
|
|
42
|
+
# @return [Proc] finalizer proc that must not reference the consumer instance
|
|
43
|
+
def self.finalizer(native_kafka, queue_holder)
|
|
44
|
+
proc do
|
|
45
|
+
next if native_kafka.closed?
|
|
46
|
+
|
|
47
|
+
native_kafka.synchronize do |inner|
|
|
48
|
+
Rdkafka::Bindings.rd_kafka_consumer_close(inner)
|
|
49
|
+
|
|
50
|
+
queue = queue_holder[0]
|
|
51
|
+
Rdkafka::Bindings.rd_kafka_queue_destroy(queue) if queue
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
native_kafka.close
|
|
55
|
+
end
|
|
21
56
|
end
|
|
22
57
|
|
|
23
58
|
# Starts the native Kafka polling thread and kicks off the init polling
|
|
@@ -33,8 +68,142 @@ module Rdkafka
|
|
|
33
68
|
end
|
|
34
69
|
end
|
|
35
70
|
|
|
36
|
-
|
|
37
|
-
|
|
71
|
+
# Enable IO event notifications for fiber scheduler integration
|
|
72
|
+
# When the consumer queue has messages, librdkafka will write to your FD
|
|
73
|
+
#
|
|
74
|
+
# @param fd [Integer] file descriptor to signal (from IO.pipe or eventfd)
|
|
75
|
+
# @param payload [String] data to write to fd (default: "\x01")
|
|
76
|
+
# @return [nil]
|
|
77
|
+
# @raise [ClosedInnerError] when the consumer is closed
|
|
78
|
+
#
|
|
79
|
+
# @example Using with fiber scheduler
|
|
80
|
+
# consumer = config.consumer
|
|
81
|
+
# consumer.subscribe("topic")
|
|
82
|
+
#
|
|
83
|
+
# # Create notification FD
|
|
84
|
+
# signal_r, signal_w = IO.pipe
|
|
85
|
+
#
|
|
86
|
+
# # Enable librdkafka to signal when messages arrive
|
|
87
|
+
# consumer.enable_queue_io_events(signal_w.fileno)
|
|
88
|
+
#
|
|
89
|
+
# # Monitor with select/poll
|
|
90
|
+
# loop do
|
|
91
|
+
# readable, = IO.select([signal_r], nil, nil, timeout)
|
|
92
|
+
# if readable
|
|
93
|
+
# signal_r.read_nonblock(1024) rescue nil # Drain signal
|
|
94
|
+
# while msg = consumer.poll(0)
|
|
95
|
+
# process(msg)
|
|
96
|
+
# end
|
|
97
|
+
# end
|
|
98
|
+
# end
|
|
99
|
+
def enable_queue_io_events(fd, payload = "\x01")
|
|
100
|
+
@native_kafka.enable_main_queue_io_events(fd, payload)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Enable IO event notifications for background events
|
|
104
|
+
# @param fd [Integer] file descriptor to signal (from IO.pipe or eventfd)
|
|
105
|
+
# @param payload [String] data to write to fd (default: "\x01")
|
|
106
|
+
# @return [nil]
|
|
107
|
+
# @raise [ClosedInnerError] when the consumer is closed
|
|
108
|
+
def enable_background_queue_io_events(fd, payload = "\x01")
|
|
109
|
+
@native_kafka.enable_background_queue_io_events(fd, payload)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Polls for events in a non-blocking loop, yielding the count after each iteration.
|
|
113
|
+
#
|
|
114
|
+
# This method processes events (stats, errors, etc.) in a single GVL/mutex session,
|
|
115
|
+
# which is more efficient than repeated individual polls. It uses non-blocking polls
|
|
116
|
+
# internally (no GVL release between polls).
|
|
117
|
+
#
|
|
118
|
+
# Yields the count of events processed after each poll iteration, allowing the caller
|
|
119
|
+
# to implement timeout or other termination logic by returning `:stop`.
|
|
120
|
+
#
|
|
121
|
+
# @yield [count] Called after each poll iteration
|
|
122
|
+
# @yieldparam count [Integer] Number of events processed in this iteration
|
|
123
|
+
# @yieldreturn [Symbol, Object] Return `:stop` to break the loop, any other value continues
|
|
124
|
+
# @return [nil]
|
|
125
|
+
# @raise [Rdkafka::ClosedConsumerError] if called on a closed consumer
|
|
126
|
+
#
|
|
127
|
+
# @note This method holds the inner lock until the queue is empty or `:stop` is returned.
|
|
128
|
+
# Other consumer operations will wait until this method returns.
|
|
129
|
+
# @note This method is thread-safe as it uses @native_kafka.with_inner synchronization
|
|
130
|
+
# @note Do NOT use this if `consumer_poll_set` was set to `true`
|
|
131
|
+
#
|
|
132
|
+
# @example Drain all pending events
|
|
133
|
+
# consumer.events_poll_nb_each { |_count| }
|
|
134
|
+
#
|
|
135
|
+
# @example With timeout control
|
|
136
|
+
# deadline = monotonic_now + timeout_ms
|
|
137
|
+
# consumer.events_poll_nb_each do |_count|
|
|
138
|
+
# :stop if monotonic_now >= deadline
|
|
139
|
+
# end
|
|
140
|
+
def events_poll_nb_each
|
|
141
|
+
closed_consumer_check(__method__)
|
|
142
|
+
|
|
143
|
+
@native_kafka.with_inner do |inner|
|
|
144
|
+
loop do
|
|
145
|
+
count = Rdkafka::Bindings.rd_kafka_poll_nb(inner, 0)
|
|
146
|
+
break if count.zero?
|
|
147
|
+
break if yield(count) == :stop
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Polls for messages in a non-blocking loop, yielding each message to the caller.
|
|
153
|
+
#
|
|
154
|
+
# This method processes messages in a single GVL/mutex session until the queue is empty
|
|
155
|
+
# or the caller returns `:stop`. It handles the message pointer lifecycle internally,
|
|
156
|
+
# ensuring proper cleanup via `rd_kafka_message_destroy`.
|
|
157
|
+
#
|
|
158
|
+
# @yield [message] Called for each message received
|
|
159
|
+
# @yieldparam message [Consumer::Message] The received message
|
|
160
|
+
# @yieldreturn [Symbol, Object] Return `:stop` to break the loop, any other value continues
|
|
161
|
+
# @return [nil]
|
|
162
|
+
# @raise [Rdkafka::ClosedConsumerError] if called on a closed consumer
|
|
163
|
+
# @raise [Rdkafka::RdkafkaError] if a Kafka error occurs while polling
|
|
164
|
+
#
|
|
165
|
+
# @note This method uses `rd_kafka_consumer_poll` to fetch messages, unlike
|
|
166
|
+
# `events_poll_nb_each` which uses `rd_kafka_poll` for event callbacks (delivery reports,
|
|
167
|
+
# statistics, etc.). For consumers, use this method to receive messages and
|
|
168
|
+
# `events_poll_nb_each` for processing background events.
|
|
169
|
+
# @note This method holds the inner lock for the duration. Other consumer operations
|
|
170
|
+
# will wait until this method returns.
|
|
171
|
+
# @note Timeout/max_messages logic should be implemented by the caller
|
|
172
|
+
#
|
|
173
|
+
# @example Process messages until queue is empty
|
|
174
|
+
# consumer.poll_nb_each do |message|
|
|
175
|
+
# process(message)
|
|
176
|
+
# end
|
|
177
|
+
#
|
|
178
|
+
# @example Process with early termination
|
|
179
|
+
# count = 0
|
|
180
|
+
# consumer.poll_nb_each do |message|
|
|
181
|
+
# process(message)
|
|
182
|
+
# count += 1
|
|
183
|
+
# :stop if count >= 10
|
|
184
|
+
# end
|
|
185
|
+
def poll_nb_each
|
|
186
|
+
closed_consumer_check(__method__)
|
|
187
|
+
|
|
188
|
+
@native_kafka.with_inner do |inner|
|
|
189
|
+
loop do
|
|
190
|
+
message_ptr = Rdkafka::Bindings.rd_kafka_consumer_poll_nb(inner, 0)
|
|
191
|
+
break if message_ptr.null?
|
|
192
|
+
|
|
193
|
+
begin
|
|
194
|
+
native_message = Rdkafka::Bindings::Message.new(message_ptr)
|
|
195
|
+
|
|
196
|
+
if native_message[:err] != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
197
|
+
raise Rdkafka::RdkafkaError.new(native_message[:err])
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
result = yield Consumer::Message.new(native_message)
|
|
201
|
+
break if result == :stop
|
|
202
|
+
ensure
|
|
203
|
+
Rdkafka::Bindings.rd_kafka_message_destroy(message_ptr)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
38
207
|
end
|
|
39
208
|
|
|
40
209
|
# Close this consumer
|
|
@@ -45,6 +214,12 @@ module Rdkafka
|
|
|
45
214
|
|
|
46
215
|
@native_kafka.synchronize do |inner|
|
|
47
216
|
Rdkafka::Bindings.rd_kafka_consumer_close(inner)
|
|
217
|
+
|
|
218
|
+
if @consumer_queue
|
|
219
|
+
Rdkafka::Bindings.rd_kafka_queue_destroy(@consumer_queue)
|
|
220
|
+
@consumer_queue = nil
|
|
221
|
+
@consumer_queue_holder[0] = nil
|
|
222
|
+
end
|
|
48
223
|
end
|
|
49
224
|
|
|
50
225
|
@native_kafka.close
|
|
@@ -67,15 +242,15 @@ module Rdkafka
|
|
|
67
242
|
tpl = Rdkafka::Bindings.rd_kafka_topic_partition_list_new(topics.length)
|
|
68
243
|
|
|
69
244
|
topics.each do |topic|
|
|
70
|
-
Rdkafka::Bindings.rd_kafka_topic_partition_list_add(tpl, topic,
|
|
245
|
+
Rdkafka::Bindings.rd_kafka_topic_partition_list_add(tpl, topic, Rdkafka::Bindings::RD_KAFKA_PARTITION_UA)
|
|
71
246
|
end
|
|
72
247
|
|
|
73
248
|
# Subscribe to topic partition list and check this was successful
|
|
74
249
|
response = @native_kafka.with_inner do |inner|
|
|
75
250
|
Rdkafka::Bindings.rd_kafka_subscribe(inner, tpl)
|
|
76
251
|
end
|
|
77
|
-
if response !=
|
|
78
|
-
raise Rdkafka::RdkafkaError.new(response, "Error subscribing to '#{topics.join(
|
|
252
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
253
|
+
raise Rdkafka::RdkafkaError.new(response, "Error subscribing to '#{topics.join(", ")}'")
|
|
79
254
|
end
|
|
80
255
|
ensure
|
|
81
256
|
Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl) unless tpl.nil?
|
|
@@ -91,7 +266,7 @@ module Rdkafka
|
|
|
91
266
|
response = @native_kafka.with_inner do |inner|
|
|
92
267
|
Rdkafka::Bindings.rd_kafka_unsubscribe(inner)
|
|
93
268
|
end
|
|
94
|
-
if response !=
|
|
269
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
95
270
|
raise Rdkafka::RdkafkaError.new(response)
|
|
96
271
|
end
|
|
97
272
|
end
|
|
@@ -115,7 +290,7 @@ module Rdkafka
|
|
|
115
290
|
Rdkafka::Bindings.rd_kafka_pause_partitions(inner, tpl)
|
|
116
291
|
end
|
|
117
292
|
|
|
118
|
-
if response !=
|
|
293
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
119
294
|
list = TopicPartitionList.from_native_tpl(tpl)
|
|
120
295
|
raise Rdkafka::RdkafkaTopicPartitionListError.new(response, list, "Error pausing '#{list.to_h}'")
|
|
121
296
|
end
|
|
@@ -142,7 +317,7 @@ module Rdkafka
|
|
|
142
317
|
response = @native_kafka.with_inner do |inner|
|
|
143
318
|
Rdkafka::Bindings.rd_kafka_resume_partitions(inner, tpl)
|
|
144
319
|
end
|
|
145
|
-
if response !=
|
|
320
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
146
321
|
raise Rdkafka::RdkafkaError.new(response, "Error resume '#{list.to_h}'")
|
|
147
322
|
end
|
|
148
323
|
ensure
|
|
@@ -162,7 +337,7 @@ module Rdkafka
|
|
|
162
337
|
Rdkafka::Bindings.rd_kafka_subscription(inner, ptr)
|
|
163
338
|
end
|
|
164
339
|
|
|
165
|
-
if response !=
|
|
340
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
166
341
|
raise Rdkafka::RdkafkaError.new(response)
|
|
167
342
|
end
|
|
168
343
|
|
|
@@ -192,7 +367,7 @@ module Rdkafka
|
|
|
192
367
|
response = @native_kafka.with_inner do |inner|
|
|
193
368
|
Rdkafka::Bindings.rd_kafka_assign(inner, tpl)
|
|
194
369
|
end
|
|
195
|
-
if response !=
|
|
370
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
196
371
|
raise Rdkafka::RdkafkaError.new(response, "Error assigning '#{list.to_h}'")
|
|
197
372
|
end
|
|
198
373
|
ensure
|
|
@@ -211,7 +386,7 @@ module Rdkafka
|
|
|
211
386
|
response = @native_kafka.with_inner do |inner|
|
|
212
387
|
Rdkafka::Bindings.rd_kafka_assignment(inner, ptr)
|
|
213
388
|
end
|
|
214
|
-
if response !=
|
|
389
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
215
390
|
raise Rdkafka::RdkafkaError.new(response)
|
|
216
391
|
end
|
|
217
392
|
|
|
@@ -225,7 +400,7 @@ module Rdkafka
|
|
|
225
400
|
end
|
|
226
401
|
end
|
|
227
402
|
ensure
|
|
228
|
-
ptr
|
|
403
|
+
ptr&.free
|
|
229
404
|
end
|
|
230
405
|
|
|
231
406
|
# @return [Boolean] true if our current assignment has been lost involuntarily.
|
|
@@ -246,7 +421,7 @@ module Rdkafka
|
|
|
246
421
|
# @param timeout_ms [Integer] The timeout for fetching this information.
|
|
247
422
|
# @return [TopicPartitionList]
|
|
248
423
|
# @raise [RdkafkaError] When getting the committed positions fails.
|
|
249
|
-
def committed(list=nil, timeout_ms=
|
|
424
|
+
def committed(list = nil, timeout_ms = Defaults::CONSUMER_COMMITTED_TIMEOUT_MS)
|
|
250
425
|
closed_consumer_check(__method__)
|
|
251
426
|
|
|
252
427
|
if list.nil?
|
|
@@ -261,7 +436,7 @@ module Rdkafka
|
|
|
261
436
|
response = @native_kafka.with_inner do |inner|
|
|
262
437
|
Rdkafka::Bindings.rd_kafka_committed(inner, tpl, timeout_ms)
|
|
263
438
|
end
|
|
264
|
-
if response !=
|
|
439
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
265
440
|
raise Rdkafka::RdkafkaError.new(response)
|
|
266
441
|
end
|
|
267
442
|
TopicPartitionList.from_native_tpl(tpl)
|
|
@@ -278,7 +453,9 @@ module Rdkafka
|
|
|
278
453
|
# @return [TopicPartitionList]
|
|
279
454
|
#
|
|
280
455
|
# @raise [RdkafkaError] When getting the positions fails.
|
|
281
|
-
def position(list=nil)
|
|
456
|
+
def position(list = nil)
|
|
457
|
+
closed_consumer_check(__method__)
|
|
458
|
+
|
|
282
459
|
if list.nil?
|
|
283
460
|
list = assignment
|
|
284
461
|
elsif !list.is_a?(TopicPartitionList)
|
|
@@ -291,11 +468,13 @@ module Rdkafka
|
|
|
291
468
|
Rdkafka::Bindings.rd_kafka_position(inner, tpl)
|
|
292
469
|
end
|
|
293
470
|
|
|
294
|
-
if response !=
|
|
471
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
295
472
|
raise Rdkafka::RdkafkaError.new(response)
|
|
296
473
|
end
|
|
297
474
|
|
|
298
475
|
TopicPartitionList.from_native_tpl(tpl)
|
|
476
|
+
ensure
|
|
477
|
+
Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl) if tpl
|
|
299
478
|
end
|
|
300
479
|
|
|
301
480
|
# Query broker for low (oldest/beginning) and high (newest/end) offsets for a partition.
|
|
@@ -305,7 +484,7 @@ module Rdkafka
|
|
|
305
484
|
# @param timeout_ms [Integer] The timeout for querying the broker
|
|
306
485
|
# @return [Integer] The low and high watermark
|
|
307
486
|
# @raise [RdkafkaError] When querying the broker fails.
|
|
308
|
-
def query_watermark_offsets(topic, partition, timeout_ms=
|
|
487
|
+
def query_watermark_offsets(topic, partition, timeout_ms = Defaults::CONSUMER_QUERY_WATERMARK_TIMEOUT_MS)
|
|
309
488
|
closed_consumer_check(__method__)
|
|
310
489
|
|
|
311
490
|
low = FFI::MemoryPointer.new(:int64, 1)
|
|
@@ -318,17 +497,17 @@ module Rdkafka
|
|
|
318
497
|
partition,
|
|
319
498
|
low,
|
|
320
499
|
high,
|
|
321
|
-
timeout_ms
|
|
500
|
+
timeout_ms
|
|
322
501
|
)
|
|
323
502
|
end
|
|
324
|
-
if response !=
|
|
503
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
325
504
|
raise Rdkafka::RdkafkaError.new(response, "Error querying watermark offsets for partition #{partition} of #{topic}")
|
|
326
505
|
end
|
|
327
506
|
|
|
328
|
-
|
|
507
|
+
[low.read_array_of_int64(1).first, high.read_array_of_int64(1).first]
|
|
329
508
|
ensure
|
|
330
|
-
low
|
|
331
|
-
high
|
|
509
|
+
low&.free
|
|
510
|
+
high&.free
|
|
332
511
|
end
|
|
333
512
|
|
|
334
513
|
# Calculate the consumer lag per partition for the provided topic partition list.
|
|
@@ -338,19 +517,18 @@ module Rdkafka
|
|
|
338
517
|
#
|
|
339
518
|
# @param topic_partition_list [TopicPartitionList] The list to calculate lag for.
|
|
340
519
|
# @param watermark_timeout_ms [Integer] The timeout for each query watermark call.
|
|
341
|
-
# @return [Hash
|
|
520
|
+
# @return [Hash{String => Hash{Integer => Integer}}] A hash containing all topics with the lag
|
|
342
521
|
# per partition
|
|
343
522
|
# @raise [RdkafkaError] When querying the broker fails.
|
|
344
|
-
def lag(topic_partition_list, watermark_timeout_ms=
|
|
523
|
+
def lag(topic_partition_list, watermark_timeout_ms = Defaults::CONSUMER_LAG_TIMEOUT_MS)
|
|
345
524
|
out = {}
|
|
346
525
|
|
|
347
526
|
topic_partition_list.to_h.each do |topic, partitions|
|
|
348
|
-
# Query high watermarks for this topic's partitions
|
|
349
|
-
# and compare to the offset in the list.
|
|
527
|
+
# Query high watermarks for this topic's partitions and compare to the offset in the list.
|
|
350
528
|
topic_out = {}
|
|
351
529
|
partitions.each do |p|
|
|
352
530
|
next if p.offset.nil?
|
|
353
|
-
|
|
531
|
+
_low, high = query_watermark_offsets(
|
|
354
532
|
topic,
|
|
355
533
|
p.partition,
|
|
356
534
|
watermark_timeout_ms
|
|
@@ -364,11 +542,12 @@ module Rdkafka
|
|
|
364
542
|
|
|
365
543
|
# Returns the ClusterId as reported in broker metadata.
|
|
366
544
|
#
|
|
545
|
+
# @param timeout_ms [Integer] timeout in milliseconds to wait for the cluster id
|
|
367
546
|
# @return [String, nil]
|
|
368
|
-
def cluster_id
|
|
547
|
+
def cluster_id(timeout_ms = Defaults::CONSUMER_CLUSTER_ID_TIMEOUT_MS)
|
|
369
548
|
closed_consumer_check(__method__)
|
|
370
549
|
@native_kafka.with_inner do |inner|
|
|
371
|
-
Rdkafka::Bindings.rd_kafka_clusterid(inner)
|
|
550
|
+
read_and_free_native_string(inner, Rdkafka::Bindings.rd_kafka_clusterid(inner, timeout_ms))
|
|
372
551
|
end
|
|
373
552
|
end
|
|
374
553
|
|
|
@@ -380,7 +559,7 @@ module Rdkafka
|
|
|
380
559
|
def member_id
|
|
381
560
|
closed_consumer_check(__method__)
|
|
382
561
|
@native_kafka.with_inner do |inner|
|
|
383
|
-
Rdkafka::Bindings.rd_kafka_memberid(inner)
|
|
562
|
+
read_and_free_native_string(inner, Rdkafka::Bindings.rd_kafka_memberid(inner))
|
|
384
563
|
end
|
|
385
564
|
end
|
|
386
565
|
|
|
@@ -389,16 +568,34 @@ module Rdkafka
|
|
|
389
568
|
# When using this `enable.auto.offset.store` should be set to `false` in the config.
|
|
390
569
|
#
|
|
391
570
|
# @param message [Rdkafka::Consumer::Message] The message which offset will be stored
|
|
571
|
+
# @param metadata [String, nil] commit metadata string to store alongside the offset
|
|
392
572
|
# @return [nil]
|
|
393
573
|
# @raise [RdkafkaError] When storing the offset fails
|
|
394
|
-
def store_offset(message)
|
|
574
|
+
def store_offset(message, metadata = nil)
|
|
395
575
|
closed_consumer_check(__method__)
|
|
396
576
|
|
|
397
577
|
list = TopicPartitionList.new
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
578
|
+
|
|
579
|
+
# For metadata aware commits we build the partition reference directly to save on
|
|
580
|
+
# objects allocations
|
|
581
|
+
if metadata
|
|
582
|
+
list.add_topic_and_partitions_with_offsets(
|
|
583
|
+
message.topic,
|
|
584
|
+
[
|
|
585
|
+
Consumer::Partition.new(
|
|
586
|
+
message.partition,
|
|
587
|
+
message.offset + 1,
|
|
588
|
+
0,
|
|
589
|
+
metadata
|
|
590
|
+
)
|
|
591
|
+
]
|
|
592
|
+
)
|
|
593
|
+
else
|
|
594
|
+
list.add_topic_and_partitions_with_offsets(
|
|
595
|
+
message.topic,
|
|
596
|
+
message.partition => message.offset + 1
|
|
597
|
+
)
|
|
598
|
+
end
|
|
402
599
|
|
|
403
600
|
tpl = list.to_native_tpl
|
|
404
601
|
|
|
@@ -409,7 +606,7 @@ module Rdkafka
|
|
|
409
606
|
)
|
|
410
607
|
end
|
|
411
608
|
|
|
412
|
-
if response !=
|
|
609
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
413
610
|
raise Rdkafka::RdkafkaError.new(response)
|
|
414
611
|
end
|
|
415
612
|
ensure
|
|
@@ -427,8 +624,7 @@ module Rdkafka
|
|
|
427
624
|
end
|
|
428
625
|
|
|
429
626
|
# Seek to a particular message by providing the topic, partition and offset.
|
|
430
|
-
# The next poll on the topic/partition will return the
|
|
431
|
-
# message at the given offset.
|
|
627
|
+
# The next poll on the topic/partition will return the message at the given offset.
|
|
432
628
|
#
|
|
433
629
|
# @param topic [String] The topic in which to seek
|
|
434
630
|
# @param partition [Integer] The partition number to seek
|
|
@@ -451,9 +647,9 @@ module Rdkafka
|
|
|
451
647
|
native_topic,
|
|
452
648
|
partition,
|
|
453
649
|
offset,
|
|
454
|
-
|
|
650
|
+
Defaults::CONSUMER_SEEK_TIMEOUT_MS
|
|
455
651
|
)
|
|
456
|
-
if response !=
|
|
652
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
457
653
|
raise Rdkafka::RdkafkaError.new(response)
|
|
458
654
|
end
|
|
459
655
|
ensure
|
|
@@ -465,11 +661,10 @@ module Rdkafka
|
|
|
465
661
|
# Lookup offset for the given partitions by timestamp.
|
|
466
662
|
#
|
|
467
663
|
# @param list [TopicPartitionList] The TopicPartitionList with timestamps instead of offsets
|
|
468
|
-
#
|
|
664
|
+
# @param timeout_ms [Integer] timeout in milliseconds for the operation
|
|
469
665
|
# @return [TopicPartitionList]
|
|
470
|
-
#
|
|
471
666
|
# @raise [RdKafkaError] When the OffsetForTimes lookup fails
|
|
472
|
-
def offsets_for_times(list, timeout_ms =
|
|
667
|
+
def offsets_for_times(list, timeout_ms = Defaults::CONSUMER_OFFSETS_FOR_TIMES_TIMEOUT_MS)
|
|
473
668
|
closed_consumer_check(__method__)
|
|
474
669
|
|
|
475
670
|
if !list.is_a?(TopicPartitionList)
|
|
@@ -486,7 +681,7 @@ module Rdkafka
|
|
|
486
681
|
)
|
|
487
682
|
end
|
|
488
683
|
|
|
489
|
-
if response !=
|
|
684
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
490
685
|
raise Rdkafka::RdkafkaError.new(response)
|
|
491
686
|
end
|
|
492
687
|
|
|
@@ -497,8 +692,7 @@ module Rdkafka
|
|
|
497
692
|
|
|
498
693
|
# Manually commit the current offsets of this consumer.
|
|
499
694
|
#
|
|
500
|
-
# To use this set `enable.auto.commit`to `false` to disable automatic triggering
|
|
501
|
-
# of commits.
|
|
695
|
+
# To use this set `enable.auto.commit`to `false` to disable automatic triggering of commits.
|
|
502
696
|
#
|
|
503
697
|
# If `enable.auto.offset.store` is set to `true` the offset of the last consumed
|
|
504
698
|
# message for every partition is used. If set to `false` you can use {store_offset} to
|
|
@@ -508,20 +702,20 @@ module Rdkafka
|
|
|
508
702
|
# @param async [Boolean] Whether to commit async or wait for the commit to finish
|
|
509
703
|
# @return [nil]
|
|
510
704
|
# @raise [RdkafkaError] When committing fails
|
|
511
|
-
def commit(list=nil, async=false)
|
|
705
|
+
def commit(list = nil, async = false)
|
|
512
706
|
closed_consumer_check(__method__)
|
|
513
707
|
|
|
514
708
|
if !list.nil? && !list.is_a?(TopicPartitionList)
|
|
515
709
|
raise TypeError.new("list has to be nil or a TopicPartitionList")
|
|
516
710
|
end
|
|
517
711
|
|
|
518
|
-
tpl = list
|
|
712
|
+
tpl = list&.to_native_tpl
|
|
519
713
|
|
|
520
714
|
begin
|
|
521
715
|
response = @native_kafka.with_inner do |inner|
|
|
522
716
|
Rdkafka::Bindings.rd_kafka_commit(inner, tpl, async)
|
|
523
717
|
end
|
|
524
|
-
if response !=
|
|
718
|
+
if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
525
719
|
raise Rdkafka::RdkafkaError.new(response)
|
|
526
720
|
end
|
|
527
721
|
ensure
|
|
@@ -546,7 +740,48 @@ module Rdkafka
|
|
|
546
740
|
# Create struct wrapper
|
|
547
741
|
native_message = Rdkafka::Bindings::Message.new(message_ptr)
|
|
548
742
|
# Raise error if needed
|
|
549
|
-
if native_message[:err] !=
|
|
743
|
+
if native_message[:err] != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
744
|
+
raise Rdkafka::RdkafkaError.new(native_message[:err])
|
|
745
|
+
end
|
|
746
|
+
# Create a message to pass out
|
|
747
|
+
Rdkafka::Consumer::Message.new(native_message)
|
|
748
|
+
end
|
|
749
|
+
ensure
|
|
750
|
+
# Clean up rdkafka message if there is one
|
|
751
|
+
if message_ptr && !message_ptr.null?
|
|
752
|
+
Rdkafka::Bindings.rd_kafka_message_destroy(message_ptr)
|
|
753
|
+
end
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
# Poll for the next message without releasing the GVL (Global VM Lock).
|
|
757
|
+
#
|
|
758
|
+
# This is more efficient than regular polling for non-blocking poll(0) calls,
|
|
759
|
+
# particularly useful in fiber scheduler contexts where GVL release/reacquire
|
|
760
|
+
# overhead is wasteful since we don't expect to wait.
|
|
761
|
+
#
|
|
762
|
+
# @param timeout_ms [Integer] Timeout of this poll (default: 0 for non-blocking)
|
|
763
|
+
# @return [Message, nil] A message or nil if there was no new message within the timeout
|
|
764
|
+
# @raise [RdkafkaError] When polling fails
|
|
765
|
+
#
|
|
766
|
+
# @example Using with fiber scheduler
|
|
767
|
+
# # After receiving IO notification that messages are available
|
|
768
|
+
# while msg = consumer.poll_nb
|
|
769
|
+
# process(msg)
|
|
770
|
+
# end
|
|
771
|
+
def poll_nb(timeout_ms = 0)
|
|
772
|
+
closed_consumer_check(__method__)
|
|
773
|
+
|
|
774
|
+
message_ptr = @native_kafka.with_inner do |inner|
|
|
775
|
+
Rdkafka::Bindings.rd_kafka_consumer_poll_nb(inner, timeout_ms)
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
if message_ptr.null?
|
|
779
|
+
nil
|
|
780
|
+
else
|
|
781
|
+
# Create struct wrapper
|
|
782
|
+
native_message = Rdkafka::Bindings::Message.new(message_ptr)
|
|
783
|
+
# Raise error if needed
|
|
784
|
+
if native_message[:err] != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
550
785
|
raise Rdkafka::RdkafkaError.new(native_message[:err])
|
|
551
786
|
end
|
|
552
787
|
# Create a message to pass out
|
|
@@ -579,37 +814,212 @@ module Rdkafka
|
|
|
579
814
|
# @note This method technically should be called `#poll` and the current `#poll` should be
|
|
580
815
|
# called `#consumer_poll` though we keep the current naming convention to make it backward
|
|
581
816
|
# compatible.
|
|
582
|
-
def events_poll(timeout_ms =
|
|
817
|
+
def events_poll(timeout_ms = Defaults::CONSUMER_EVENTS_POLL_TIMEOUT_MS)
|
|
583
818
|
@native_kafka.with_inner do |inner|
|
|
584
819
|
Rdkafka::Bindings.rd_kafka_poll(inner, timeout_ms)
|
|
585
820
|
end
|
|
586
821
|
end
|
|
587
822
|
|
|
823
|
+
# Polls the main rdkafka queue without releasing the GVL (Global VM Lock).
|
|
824
|
+
#
|
|
825
|
+
# This is more efficient than regular events_poll for non-blocking poll(0) calls,
|
|
826
|
+
# particularly useful in fiber scheduler contexts where GVL release/reacquire
|
|
827
|
+
# overhead is wasteful since we don't expect to wait.
|
|
828
|
+
#
|
|
829
|
+
# @param timeout_ms [Integer] poll timeout (default: 0 for non-blocking)
|
|
830
|
+
# @return [Integer] the number of events served
|
|
831
|
+
#
|
|
832
|
+
# @see #events_poll for more details on when to use this method
|
|
833
|
+
def events_poll_nb(timeout_ms = 0)
|
|
834
|
+
@native_kafka.with_inner do |inner|
|
|
835
|
+
Rdkafka::Bindings.rd_kafka_poll_nb(inner, timeout_ms)
|
|
836
|
+
end
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
# Poll for a batch of messages from the consumer queue in a single FFI call.
|
|
840
|
+
#
|
|
841
|
+
# This is more efficient than calling {#poll} in a loop because it crosses the FFI
|
|
842
|
+
# boundary only once to fetch up to `max_items` messages.
|
|
843
|
+
#
|
|
844
|
+
# The timeout controls how long to wait for the **first** message. Once any message
|
|
845
|
+
# is available, librdkafka fills the buffer with whatever is immediately ready and
|
|
846
|
+
# returns without further waiting.
|
|
847
|
+
#
|
|
848
|
+
# Error events (e.g. `:partition_eof`) are returned inline as {RdkafkaError} objects
|
|
849
|
+
# rather than raised, so callers receive the complete batch — both messages and errors —
|
|
850
|
+
# and can decide how to handle each. This is particularly useful when multiple partitions
|
|
851
|
+
# signal EOF simultaneously: all signals appear in the returned array rather than only
|
|
852
|
+
# the first one being raised and the rest silently discarded.
|
|
853
|
+
#
|
|
854
|
+
# @param timeout_ms [Integer] Timeout waiting for the first message (-1 for infinite)
|
|
855
|
+
# @param max_items [Integer] Maximum number of messages to return per call
|
|
856
|
+
# @return [Array<Message, RdkafkaError>] Batch of messages and/or error events in arrival order
|
|
857
|
+
# @raise [ClosedConsumerError] When called on a closed consumer
|
|
858
|
+
def poll_batch(timeout_ms, max_items: 100)
|
|
859
|
+
closed_consumer_check(__method__)
|
|
860
|
+
|
|
861
|
+
buffer = batch_buffer(max_items)
|
|
862
|
+
results = []
|
|
863
|
+
|
|
864
|
+
count = @native_kafka.with_inner do |_inner|
|
|
865
|
+
Rdkafka::Bindings.rd_kafka_consume_batch_queue(
|
|
866
|
+
consumer_queue,
|
|
867
|
+
timeout_ms,
|
|
868
|
+
buffer,
|
|
869
|
+
max_items
|
|
870
|
+
)
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
return results if count <= 0
|
|
874
|
+
|
|
875
|
+
i = 0
|
|
876
|
+
begin
|
|
877
|
+
while i < count
|
|
878
|
+
ptr = buffer.get_pointer(i * FFI::Pointer.size)
|
|
879
|
+
|
|
880
|
+
if ptr.null?
|
|
881
|
+
i += 1
|
|
882
|
+
next
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
native_message = Rdkafka::Bindings::Message.new(ptr)
|
|
886
|
+
|
|
887
|
+
if native_message[:err] != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
888
|
+
results << Rdkafka::RdkafkaError.new(native_message[:err])
|
|
889
|
+
Rdkafka::Bindings.rd_kafka_message_destroy(ptr)
|
|
890
|
+
i += 1
|
|
891
|
+
next
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
begin
|
|
895
|
+
results << Rdkafka::Consumer::Message.new(native_message)
|
|
896
|
+
rescue Rdkafka::RdkafkaError => e
|
|
897
|
+
# A message that fails to build (e.g. a header read error) is surfaced inline as an
|
|
898
|
+
# error event rather than discarding the whole batch - including the messages already
|
|
899
|
+
# built - and raising, which silently lost them once their offsets had been stored.
|
|
900
|
+
results << e
|
|
901
|
+
ensure
|
|
902
|
+
Rdkafka::Bindings.rd_kafka_message_destroy(ptr)
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
i += 1
|
|
906
|
+
end
|
|
907
|
+
ensure
|
|
908
|
+
while i < count
|
|
909
|
+
ptr = buffer.get_pointer(i * FFI::Pointer.size)
|
|
910
|
+
Rdkafka::Bindings.rd_kafka_message_destroy(ptr) unless ptr.null?
|
|
911
|
+
i += 1
|
|
912
|
+
end
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
results
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
# Poll for a batch of messages without releasing the GVL (Global VM Lock).
|
|
919
|
+
#
|
|
920
|
+
# This is more efficient than {#poll_batch} for non-blocking poll(0) calls,
|
|
921
|
+
# particularly useful in fiber scheduler contexts where GVL release/reacquire
|
|
922
|
+
# overhead is wasteful since we don't expect to wait.
|
|
923
|
+
#
|
|
924
|
+
# @note Since the GVL is not released, a non-zero timeout_ms will block all Ruby
|
|
925
|
+
# threads/fibers for the duration. Use {#poll_batch} if you need a blocking wait.
|
|
926
|
+
#
|
|
927
|
+
# Error events are returned inline as {RdkafkaError} objects; see {#poll_batch} for details.
|
|
928
|
+
#
|
|
929
|
+
# @param timeout_ms [Integer] Timeout waiting for the first message (default: 0 for non-blocking)
|
|
930
|
+
# @param max_items [Integer] Maximum number of messages to return per call
|
|
931
|
+
# @return [Array<Message, RdkafkaError>] Batch of messages and/or error events in arrival order
|
|
932
|
+
# @raise [ClosedConsumerError] When called on a closed consumer
|
|
933
|
+
def poll_batch_nb(timeout_ms = 0, max_items: 100)
|
|
934
|
+
closed_consumer_check(__method__)
|
|
935
|
+
|
|
936
|
+
buffer = batch_buffer(max_items)
|
|
937
|
+
results = []
|
|
938
|
+
|
|
939
|
+
count = @native_kafka.with_inner do |_inner|
|
|
940
|
+
Rdkafka::Bindings.rd_kafka_consume_batch_queue_nb(
|
|
941
|
+
consumer_queue,
|
|
942
|
+
timeout_ms,
|
|
943
|
+
buffer,
|
|
944
|
+
max_items
|
|
945
|
+
)
|
|
946
|
+
end
|
|
947
|
+
|
|
948
|
+
return results if count <= 0
|
|
949
|
+
|
|
950
|
+
i = 0
|
|
951
|
+
begin
|
|
952
|
+
while i < count
|
|
953
|
+
ptr = buffer.get_pointer(i * FFI::Pointer.size)
|
|
954
|
+
|
|
955
|
+
if ptr.null?
|
|
956
|
+
i += 1
|
|
957
|
+
next
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
native_message = Rdkafka::Bindings::Message.new(ptr)
|
|
961
|
+
|
|
962
|
+
if native_message[:err] != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
963
|
+
results << Rdkafka::RdkafkaError.new(native_message[:err])
|
|
964
|
+
Rdkafka::Bindings.rd_kafka_message_destroy(ptr)
|
|
965
|
+
i += 1
|
|
966
|
+
next
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
begin
|
|
970
|
+
results << Rdkafka::Consumer::Message.new(native_message)
|
|
971
|
+
rescue Rdkafka::RdkafkaError => e
|
|
972
|
+
# A message that fails to build (e.g. a header read error) is surfaced inline as an
|
|
973
|
+
# error event rather than discarding the whole batch - including the messages already
|
|
974
|
+
# built - and raising, which silently lost them once their offsets had been stored.
|
|
975
|
+
results << e
|
|
976
|
+
ensure
|
|
977
|
+
Rdkafka::Bindings.rd_kafka_message_destroy(ptr)
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
i += 1
|
|
981
|
+
end
|
|
982
|
+
ensure
|
|
983
|
+
while i < count
|
|
984
|
+
ptr = buffer.get_pointer(i * FFI::Pointer.size)
|
|
985
|
+
Rdkafka::Bindings.rd_kafka_message_destroy(ptr) unless ptr.null?
|
|
986
|
+
i += 1
|
|
987
|
+
end
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
results
|
|
991
|
+
end
|
|
992
|
+
|
|
588
993
|
# Poll for new messages and yield for each received one. Iteration
|
|
589
994
|
# will end when the consumer is closed.
|
|
590
995
|
#
|
|
591
996
|
# If `enable.partition.eof` is turned on in the config this will raise an error when an eof is
|
|
592
997
|
# reached, so you probably want to disable that when using this method of iteration.
|
|
593
998
|
#
|
|
999
|
+
# @param timeout_ms [Integer] Timeout for each poll iteration
|
|
594
1000
|
# @yieldparam message [Message] Received message
|
|
595
1001
|
# @return [nil]
|
|
596
1002
|
# @raise [RdkafkaError] When polling fails
|
|
597
|
-
def each
|
|
1003
|
+
def each(timeout_ms: Defaults::CONSUMER_POLL_TIMEOUT_MS)
|
|
598
1004
|
loop do
|
|
599
|
-
message = poll(
|
|
1005
|
+
message = poll(timeout_ms)
|
|
600
1006
|
if message
|
|
601
1007
|
yield(message)
|
|
1008
|
+
elsif closed?
|
|
1009
|
+
break
|
|
602
1010
|
else
|
|
603
|
-
|
|
604
|
-
break
|
|
605
|
-
else
|
|
606
|
-
next
|
|
607
|
-
end
|
|
1011
|
+
next
|
|
608
1012
|
end
|
|
609
1013
|
end
|
|
610
1014
|
end
|
|
611
1015
|
|
|
612
|
-
#
|
|
1016
|
+
# @deprecated This method has been removed due to data consistency concerns
|
|
1017
|
+
# @param max_items [Integer] unused
|
|
1018
|
+
# @param bytes_threshold [Numeric] unused
|
|
1019
|
+
# @param timeout_ms [Integer] unused
|
|
1020
|
+
# @param yield_on_error [Boolean] unused
|
|
1021
|
+
# @param block [Proc] unused block
|
|
1022
|
+
# @raise [NotImplementedError] Always raises as this method is no longer supported
|
|
613
1023
|
def each_batch(max_items: 100, bytes_threshold: Float::INFINITY, timeout_ms: 250, yield_on_error: false, &block)
|
|
614
1024
|
raise NotImplementedError, <<~ERROR
|
|
615
1025
|
`each_batch` has been removed due to data consistency concerns.
|
|
@@ -646,8 +1056,52 @@ module Rdkafka
|
|
|
646
1056
|
|
|
647
1057
|
private
|
|
648
1058
|
|
|
1059
|
+
# Checks if the consumer is closed and raises an error if so
|
|
1060
|
+
# @param method [Symbol] name of the calling method for error context
|
|
1061
|
+
# @raise [ClosedConsumerError] when the consumer is closed
|
|
649
1062
|
def closed_consumer_check(method)
|
|
650
1063
|
raise Rdkafka::ClosedConsumerError.new(method) if closed?
|
|
651
1064
|
end
|
|
1065
|
+
alias_method :closed_check, :closed_consumer_check
|
|
1066
|
+
|
|
1067
|
+
# Reads a librdkafka-allocated string and frees the underlying native buffer.
|
|
1068
|
+
#
|
|
1069
|
+
# librdkafka returns heap-allocated, caller-owned strings from functions like
|
|
1070
|
+
# `rd_kafka_clusterid`/`rd_kafka_memberid`. The buffer must be released with
|
|
1071
|
+
# `rd_kafka_mem_free`, otherwise it leaks on every call.
|
|
1072
|
+
#
|
|
1073
|
+
# @param inner [FFI::Pointer] the native client handle used to allocate the string
|
|
1074
|
+
# @param ptr [FFI::Pointer] the native string pointer (may be null)
|
|
1075
|
+
# @return [String, nil] the copied Ruby string, or nil when the pointer is null
|
|
1076
|
+
def read_and_free_native_string(inner, ptr)
|
|
1077
|
+
return nil if ptr.null?
|
|
1078
|
+
|
|
1079
|
+
ptr.read_string
|
|
1080
|
+
ensure
|
|
1081
|
+
Rdkafka::Bindings.rd_kafka_mem_free(inner, ptr) unless ptr.null?
|
|
1082
|
+
end
|
|
1083
|
+
|
|
1084
|
+
# Returns the consumer queue pointer, lazily initialized
|
|
1085
|
+
# @return [FFI::Pointer] consumer queue handle
|
|
1086
|
+
def consumer_queue
|
|
1087
|
+
@consumer_queue ||= @native_kafka.with_inner do |inner|
|
|
1088
|
+
queue = Rdkafka::Bindings.rd_kafka_queue_get_consumer(inner)
|
|
1089
|
+
# Share the pointer with the finalizer so it is destroyed even if the consumer is GC'd
|
|
1090
|
+
# without an explicit close.
|
|
1091
|
+
@consumer_queue_holder[0] = queue
|
|
1092
|
+
queue
|
|
1093
|
+
end
|
|
1094
|
+
end
|
|
1095
|
+
|
|
1096
|
+
# Returns a reusable FFI buffer for batch polling, growing if needed
|
|
1097
|
+
# @param max_items [Integer] minimum buffer capacity
|
|
1098
|
+
# @return [FFI::MemoryPointer] pointer buffer
|
|
1099
|
+
def batch_buffer(max_items)
|
|
1100
|
+
if @batch_buffer.nil? || @batch_buffer_size < max_items
|
|
1101
|
+
@batch_buffer = FFI::MemoryPointer.new(:pointer, max_items)
|
|
1102
|
+
@batch_buffer_size = max_items
|
|
1103
|
+
end
|
|
1104
|
+
@batch_buffer
|
|
1105
|
+
end
|
|
652
1106
|
end
|
|
653
1107
|
end
|