rdkafka 0.24.2-aarch64-linux-gnu → 0.25.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/Gemfile +8 -0
  4. data/Gemfile.lint +14 -0
  5. data/Gemfile.lint.lock +123 -0
  6. data/README.md +2 -1
  7. data/Rakefile +21 -21
  8. data/docker-compose-ssl.yml +1 -1
  9. data/docker-compose.yml +1 -1
  10. data/ext/librdkafka.so +0 -0
  11. data/lib/rdkafka/abstract_handle.rb +23 -5
  12. data/lib/rdkafka/admin/acl_binding_result.rb +5 -5
  13. data/lib/rdkafka/admin/config_resource_binding_result.rb +1 -0
  14. data/lib/rdkafka/admin/create_acl_handle.rb +7 -4
  15. data/lib/rdkafka/admin/create_acl_report.rb +3 -2
  16. data/lib/rdkafka/admin/create_partitions_handle.rb +8 -5
  17. data/lib/rdkafka/admin/create_partitions_report.rb +1 -0
  18. data/lib/rdkafka/admin/create_topic_handle.rb +8 -5
  19. data/lib/rdkafka/admin/create_topic_report.rb +3 -0
  20. data/lib/rdkafka/admin/delete_acl_handle.rb +9 -6
  21. data/lib/rdkafka/admin/delete_acl_report.rb +5 -3
  22. data/lib/rdkafka/admin/delete_groups_handle.rb +10 -5
  23. data/lib/rdkafka/admin/delete_groups_report.rb +3 -0
  24. data/lib/rdkafka/admin/delete_topic_handle.rb +8 -5
  25. data/lib/rdkafka/admin/delete_topic_report.rb +3 -0
  26. data/lib/rdkafka/admin/describe_acl_handle.rb +9 -6
  27. data/lib/rdkafka/admin/describe_acl_report.rb +5 -3
  28. data/lib/rdkafka/admin/describe_configs_handle.rb +7 -4
  29. data/lib/rdkafka/admin/describe_configs_report.rb +7 -1
  30. data/lib/rdkafka/admin/incremental_alter_configs_handle.rb +7 -4
  31. data/lib/rdkafka/admin/incremental_alter_configs_report.rb +7 -1
  32. data/lib/rdkafka/admin.rb +194 -132
  33. data/lib/rdkafka/bindings.rb +155 -107
  34. data/lib/rdkafka/callbacks.rb +81 -21
  35. data/lib/rdkafka/config.rb +36 -24
  36. data/lib/rdkafka/consumer/headers.rb +3 -2
  37. data/lib/rdkafka/consumer/message.rb +12 -11
  38. data/lib/rdkafka/consumer/partition.rb +8 -4
  39. data/lib/rdkafka/consumer/topic_partition_list.rb +18 -18
  40. data/lib/rdkafka/consumer.rb +247 -42
  41. data/lib/rdkafka/defaults.rb +106 -0
  42. data/lib/rdkafka/error.rb +28 -13
  43. data/lib/rdkafka/helpers/oauth.rb +11 -6
  44. data/lib/rdkafka/helpers/time.rb +5 -0
  45. data/lib/rdkafka/metadata.rb +45 -21
  46. data/lib/rdkafka/native_kafka.rb +89 -4
  47. data/lib/rdkafka/producer/delivery_handle.rb +5 -5
  48. data/lib/rdkafka/producer/delivery_report.rb +8 -4
  49. data/lib/rdkafka/producer/partitions_count_cache.rb +29 -19
  50. data/lib/rdkafka/producer.rb +165 -79
  51. data/lib/rdkafka/version.rb +6 -3
  52. data/lib/rdkafka.rb +1 -0
  53. data/package-lock.json +331 -0
  54. data/package.json +9 -0
  55. data/rdkafka.gemspec +39 -47
  56. data/renovate.json +22 -8
  57. metadata +7 -86
@@ -9,7 +9,7 @@ module Rdkafka
9
9
  # @param data [Hash{String => nil,Partition}] The topic and partition data or nil to create an empty list
10
10
  #
11
11
  # @return [TopicPartitionList]
12
- def initialize(data=nil)
12
+ def initialize(data = nil)
13
13
  @data = data || {}
14
14
  end
15
15
 
@@ -18,10 +18,10 @@ module Rdkafka
18
18
  def count
19
19
  i = 0
20
20
  @data.each do |_topic, partitions|
21
- if partitions
22
- i += partitions.count
21
+ i += if partitions
22
+ partitions.count
23
23
  else
24
- i+= 1
24
+ 1
25
25
  end
26
26
  end
27
27
  i
@@ -49,15 +49,14 @@ module Rdkafka
49
49
  #
50
50
  # @example Add a topic with all topics up to a count
51
51
  # tpl.add_topic("topic", 9)
52
- #
53
- def add_topic(topic, partitions=nil)
52
+ def add_topic(topic, partitions = nil)
54
53
  if partitions.nil?
55
54
  @data[topic.to_s] = nil
56
55
  else
57
56
  if partitions.is_a? Integer
58
57
  partitions = (0..partitions - 1)
59
58
  end
60
- @data[topic.to_s] = partitions.map { |p| Partition.new(p, nil, 0) }
59
+ @data[topic.to_s] = partitions.map { |p| Partition.new(p, nil, Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR) }
61
60
  end
62
61
  end
63
62
 
@@ -65,10 +64,8 @@ module Rdkafka
65
64
  # Calling this method multiple times for the same topic will overwrite the previous configuraton.
66
65
  #
67
66
  # @param topic [String] The topic's name
68
- # @param partitions_with_offsets [Hash<Integer, Integer>] The topic's partitions and offsets
69
- # @param partitions_with_offsets [Array<Consumer::Partition>] The topic's partitions with offsets
70
- # and metadata (if any)
71
- #
67
+ # @param partitions_with_offsets [Hash{Integer => Integer}, Array<Consumer::Partition>] The topic's
68
+ # partitions and offsets (Hash) or partitions with offsets and metadata (Array)
72
69
  # @return [nil]
73
70
  def add_topic_and_partitions_with_offsets(topic, partitions_with_offsets)
74
71
  @data[topic.to_s] = partitions_with_offsets.map do |p, o|
@@ -89,8 +86,11 @@ module Rdkafka
89
86
  "<TopicPartitionList: #{to_h}>"
90
87
  end
91
88
 
89
+ # Check equality with another TopicPartitionList
90
+ # @param other [TopicPartitionList] object to compare with
91
+ # @return [Boolean]
92
92
  def ==(other)
93
- self.to_h == other.to_h
93
+ to_h == other.to_h
94
94
  end
95
95
 
96
96
  # Create a new topic partition list based of a native one.
@@ -109,15 +109,15 @@ module Rdkafka
109
109
  native_tpl[:cnt].times do |i|
110
110
  ptr = native_tpl[:elems] + (i * Rdkafka::Bindings::TopicPartition.size)
111
111
  elem = Rdkafka::Bindings::TopicPartition.new(ptr)
112
- if elem[:partition] == -1
112
+ if elem[:partition] == Rdkafka::Bindings::RD_KAFKA_PARTITION_UA
113
113
  data[elem[:topic]] = nil
114
114
  else
115
115
  partitions = data[elem[:topic]] || []
116
116
  offset = if elem[:offset] == Rdkafka::Bindings::RD_KAFKA_OFFSET_INVALID
117
- nil
118
- else
119
- elem[:offset]
120
- end
117
+ nil
118
+ else
119
+ elem[:offset]
120
+ end
121
121
  partition = Partition.new(elem[:partition], offset, elem[:err])
122
122
  partitions.push(partition)
123
123
  data[elem[:topic]] = partitions
@@ -161,7 +161,7 @@ module Rdkafka
161
161
  Rdkafka::Bindings.rd_kafka_topic_partition_list_add(
162
162
  tpl,
163
163
  topic,
164
- -1
164
+ Rdkafka::Bindings::RD_KAFKA_PARTITION_UA
165
165
  )
166
166
  end
167
167
  end
@@ -16,6 +16,7 @@ module Rdkafka
16
16
  include Helpers::OAuth
17
17
 
18
18
  # @private
19
+ # @param native_kafka [NativeKafka] wrapper around the native Kafka consumer handle
19
20
  def initialize(native_kafka)
20
21
  @native_kafka = native_kafka
21
22
  end
@@ -33,6 +34,146 @@ module Rdkafka
33
34
  end
34
35
  end
35
36
 
37
+ # Enable IO event notifications for fiber scheduler integration
38
+ # When the consumer queue has messages, librdkafka will write to your FD
39
+ #
40
+ # @param fd [Integer] file descriptor to signal (from IO.pipe or eventfd)
41
+ # @param payload [String] data to write to fd (default: "\x01")
42
+ # @return [nil]
43
+ # @raise [ClosedInnerError] when the consumer is closed
44
+ #
45
+ # @example Using with fiber scheduler
46
+ # consumer = config.consumer
47
+ # consumer.subscribe("topic")
48
+ #
49
+ # # Create notification FD
50
+ # signal_r, signal_w = IO.pipe
51
+ #
52
+ # # Enable librdkafka to signal when messages arrive
53
+ # consumer.enable_queue_io_events(signal_w.fileno)
54
+ #
55
+ # # Monitor with select/poll
56
+ # loop do
57
+ # readable, = IO.select([signal_r], nil, nil, timeout)
58
+ # if readable
59
+ # signal_r.read_nonblock(1024) rescue nil # Drain signal
60
+ # while msg = consumer.poll(0)
61
+ # process(msg)
62
+ # end
63
+ # end
64
+ # end
65
+ def enable_queue_io_events(fd, payload = "\x01")
66
+ @native_kafka.enable_main_queue_io_events(fd, payload)
67
+ end
68
+
69
+ # Enable IO event notifications for background events
70
+ # @param fd [Integer] file descriptor to signal (from IO.pipe or eventfd)
71
+ # @param payload [String] data to write to fd (default: "\x01")
72
+ # @return [nil]
73
+ # @raise [ClosedInnerError] when the consumer is closed
74
+ def enable_background_queue_io_events(fd, payload = "\x01")
75
+ @native_kafka.enable_background_queue_io_events(fd, payload)
76
+ end
77
+
78
+ # Polls for events in a non-blocking loop, yielding the count after each iteration.
79
+ #
80
+ # This method processes events (stats, errors, etc.) in a single GVL/mutex session,
81
+ # which is more efficient than repeated individual polls. It uses non-blocking polls
82
+ # internally (no GVL release between polls).
83
+ #
84
+ # Yields the count of events processed after each poll iteration, allowing the caller
85
+ # to implement timeout or other termination logic by returning `:stop`.
86
+ #
87
+ # @yield [count] Called after each poll iteration
88
+ # @yieldparam count [Integer] Number of events processed in this iteration
89
+ # @yieldreturn [Symbol, Object] Return `:stop` to break the loop, any other value continues
90
+ # @return [nil]
91
+ # @raise [Rdkafka::ClosedConsumerError] if called on a closed consumer
92
+ #
93
+ # @note This method holds the inner lock until the queue is empty or `:stop` is returned.
94
+ # Other consumer operations will wait until this method returns.
95
+ # @note This method is thread-safe as it uses @native_kafka.with_inner synchronization
96
+ # @note Do NOT use this if `consumer_poll_set` was set to `true`
97
+ #
98
+ # @example Drain all pending events
99
+ # consumer.events_poll_nb_each { |_count| }
100
+ #
101
+ # @example With timeout control
102
+ # deadline = monotonic_now + timeout_ms
103
+ # consumer.events_poll_nb_each do |_count|
104
+ # :stop if monotonic_now >= deadline
105
+ # end
106
+ def events_poll_nb_each
107
+ closed_consumer_check(__method__)
108
+
109
+ @native_kafka.with_inner do |inner|
110
+ loop do
111
+ count = Rdkafka::Bindings.rd_kafka_poll_nb(inner, 0)
112
+ break if count.zero?
113
+ break if yield(count) == :stop
114
+ end
115
+ end
116
+ end
117
+
118
+ # Polls for messages in a non-blocking loop, yielding each message to the caller.
119
+ #
120
+ # This method processes messages in a single GVL/mutex session until the queue is empty
121
+ # or the caller returns `:stop`. It handles the message pointer lifecycle internally,
122
+ # ensuring proper cleanup via `rd_kafka_message_destroy`.
123
+ #
124
+ # @yield [message] Called for each message received
125
+ # @yieldparam message [Consumer::Message] The received message
126
+ # @yieldreturn [Symbol, Object] Return `:stop` to break the loop, any other value continues
127
+ # @return [nil]
128
+ # @raise [Rdkafka::ClosedConsumerError] if called on a closed consumer
129
+ # @raise [Rdkafka::RdkafkaError] if a Kafka error occurs while polling
130
+ #
131
+ # @note This method uses `rd_kafka_consumer_poll` to fetch messages, unlike
132
+ # `events_poll_nb_each` which uses `rd_kafka_poll` for event callbacks (delivery reports,
133
+ # statistics, etc.). For consumers, use this method to receive messages and
134
+ # `events_poll_nb_each` for processing background events.
135
+ # @note This method holds the inner lock for the duration. Other consumer operations
136
+ # will wait until this method returns.
137
+ # @note Timeout/max_messages logic should be implemented by the caller
138
+ #
139
+ # @example Process messages until queue is empty
140
+ # consumer.poll_nb_each do |message|
141
+ # process(message)
142
+ # end
143
+ #
144
+ # @example Process with early termination
145
+ # count = 0
146
+ # consumer.poll_nb_each do |message|
147
+ # process(message)
148
+ # count += 1
149
+ # :stop if count >= 10
150
+ # end
151
+ def poll_nb_each
152
+ closed_consumer_check(__method__)
153
+
154
+ @native_kafka.with_inner do |inner|
155
+ loop do
156
+ message_ptr = Rdkafka::Bindings.rd_kafka_consumer_poll_nb(inner, 0)
157
+ break if message_ptr.null?
158
+
159
+ begin
160
+ native_message = Rdkafka::Bindings::Message.new(message_ptr)
161
+
162
+ if native_message[:err] != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
163
+ raise Rdkafka::RdkafkaError.new(native_message[:err])
164
+ end
165
+
166
+ result = yield Consumer::Message.new(native_message)
167
+ break if result == :stop
168
+ ensure
169
+ Rdkafka::Bindings.rd_kafka_message_destroy(message_ptr)
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ # @return [Proc] finalizer proc for closing the consumer
176
+ # @private
36
177
  def finalizer
37
178
  ->(_) { close }
38
179
  end
@@ -67,15 +208,15 @@ module Rdkafka
67
208
  tpl = Rdkafka::Bindings.rd_kafka_topic_partition_list_new(topics.length)
68
209
 
69
210
  topics.each do |topic|
70
- Rdkafka::Bindings.rd_kafka_topic_partition_list_add(tpl, topic, -1)
211
+ Rdkafka::Bindings.rd_kafka_topic_partition_list_add(tpl, topic, Rdkafka::Bindings::RD_KAFKA_PARTITION_UA)
71
212
  end
72
213
 
73
214
  # Subscribe to topic partition list and check this was successful
74
215
  response = @native_kafka.with_inner do |inner|
75
216
  Rdkafka::Bindings.rd_kafka_subscribe(inner, tpl)
76
217
  end
77
- if response != 0
78
- raise Rdkafka::RdkafkaError.new(response, "Error subscribing to '#{topics.join(', ')}'")
218
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
219
+ raise Rdkafka::RdkafkaError.new(response, "Error subscribing to '#{topics.join(", ")}'")
79
220
  end
80
221
  ensure
81
222
  Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl) unless tpl.nil?
@@ -91,7 +232,7 @@ module Rdkafka
91
232
  response = @native_kafka.with_inner do |inner|
92
233
  Rdkafka::Bindings.rd_kafka_unsubscribe(inner)
93
234
  end
94
- if response != 0
235
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
95
236
  raise Rdkafka::RdkafkaError.new(response)
96
237
  end
97
238
  end
@@ -115,7 +256,7 @@ module Rdkafka
115
256
  Rdkafka::Bindings.rd_kafka_pause_partitions(inner, tpl)
116
257
  end
117
258
 
118
- if response != 0
259
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
119
260
  list = TopicPartitionList.from_native_tpl(tpl)
120
261
  raise Rdkafka::RdkafkaTopicPartitionListError.new(response, list, "Error pausing '#{list.to_h}'")
121
262
  end
@@ -142,7 +283,7 @@ module Rdkafka
142
283
  response = @native_kafka.with_inner do |inner|
143
284
  Rdkafka::Bindings.rd_kafka_resume_partitions(inner, tpl)
144
285
  end
145
- if response != 0
286
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
146
287
  raise Rdkafka::RdkafkaError.new(response, "Error resume '#{list.to_h}'")
147
288
  end
148
289
  ensure
@@ -162,7 +303,7 @@ module Rdkafka
162
303
  Rdkafka::Bindings.rd_kafka_subscription(inner, ptr)
163
304
  end
164
305
 
165
- if response != 0
306
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
166
307
  raise Rdkafka::RdkafkaError.new(response)
167
308
  end
168
309
 
@@ -192,7 +333,7 @@ module Rdkafka
192
333
  response = @native_kafka.with_inner do |inner|
193
334
  Rdkafka::Bindings.rd_kafka_assign(inner, tpl)
194
335
  end
195
- if response != 0
336
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
196
337
  raise Rdkafka::RdkafkaError.new(response, "Error assigning '#{list.to_h}'")
197
338
  end
198
339
  ensure
@@ -211,7 +352,7 @@ module Rdkafka
211
352
  response = @native_kafka.with_inner do |inner|
212
353
  Rdkafka::Bindings.rd_kafka_assignment(inner, ptr)
213
354
  end
214
- if response != 0
355
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
215
356
  raise Rdkafka::RdkafkaError.new(response)
216
357
  end
217
358
 
@@ -225,7 +366,7 @@ module Rdkafka
225
366
  end
226
367
  end
227
368
  ensure
228
- ptr.free unless ptr.nil?
369
+ ptr&.free
229
370
  end
230
371
 
231
372
  # @return [Boolean] true if our current assignment has been lost involuntarily.
@@ -246,7 +387,7 @@ module Rdkafka
246
387
  # @param timeout_ms [Integer] The timeout for fetching this information.
247
388
  # @return [TopicPartitionList]
248
389
  # @raise [RdkafkaError] When getting the committed positions fails.
249
- def committed(list=nil, timeout_ms=2_000)
390
+ def committed(list = nil, timeout_ms = Defaults::CONSUMER_COMMITTED_TIMEOUT_MS)
250
391
  closed_consumer_check(__method__)
251
392
 
252
393
  if list.nil?
@@ -261,7 +402,7 @@ module Rdkafka
261
402
  response = @native_kafka.with_inner do |inner|
262
403
  Rdkafka::Bindings.rd_kafka_committed(inner, tpl, timeout_ms)
263
404
  end
264
- if response != 0
405
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
265
406
  raise Rdkafka::RdkafkaError.new(response)
266
407
  end
267
408
  TopicPartitionList.from_native_tpl(tpl)
@@ -278,7 +419,7 @@ module Rdkafka
278
419
  # @return [TopicPartitionList]
279
420
  #
280
421
  # @raise [RdkafkaError] When getting the positions fails.
281
- def position(list=nil)
422
+ def position(list = nil)
282
423
  if list.nil?
283
424
  list = assignment
284
425
  elsif !list.is_a?(TopicPartitionList)
@@ -291,7 +432,7 @@ module Rdkafka
291
432
  Rdkafka::Bindings.rd_kafka_position(inner, tpl)
292
433
  end
293
434
 
294
- if response != 0
435
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
295
436
  raise Rdkafka::RdkafkaError.new(response)
296
437
  end
297
438
 
@@ -305,7 +446,7 @@ module Rdkafka
305
446
  # @param timeout_ms [Integer] The timeout for querying the broker
306
447
  # @return [Integer] The low and high watermark
307
448
  # @raise [RdkafkaError] When querying the broker fails.
308
- def query_watermark_offsets(topic, partition, timeout_ms=1000)
449
+ def query_watermark_offsets(topic, partition, timeout_ms = Defaults::CONSUMER_QUERY_WATERMARK_TIMEOUT_MS)
309
450
  closed_consumer_check(__method__)
310
451
 
311
452
  low = FFI::MemoryPointer.new(:int64, 1)
@@ -318,17 +459,17 @@ module Rdkafka
318
459
  partition,
319
460
  low,
320
461
  high,
321
- timeout_ms,
462
+ timeout_ms
322
463
  )
323
464
  end
324
- if response != 0
465
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
325
466
  raise Rdkafka::RdkafkaError.new(response, "Error querying watermark offsets for partition #{partition} of #{topic}")
326
467
  end
327
468
 
328
- return low.read_array_of_int64(1).first, high.read_array_of_int64(1).first
469
+ [low.read_array_of_int64(1).first, high.read_array_of_int64(1).first]
329
470
  ensure
330
- low.free unless low.nil?
331
- high.free unless high.nil?
471
+ low&.free
472
+ high&.free
332
473
  end
333
474
 
334
475
  # Calculate the consumer lag per partition for the provided topic partition list.
@@ -338,10 +479,10 @@ module Rdkafka
338
479
  #
339
480
  # @param topic_partition_list [TopicPartitionList] The list to calculate lag for.
340
481
  # @param watermark_timeout_ms [Integer] The timeout for each query watermark call.
341
- # @return [Hash<String, Hash<Integer, Integer>>] A hash containing all topics with the lag
482
+ # @return [Hash{String => Hash{Integer => Integer}}] A hash containing all topics with the lag
342
483
  # per partition
343
484
  # @raise [RdkafkaError] When querying the broker fails.
344
- def lag(topic_partition_list, watermark_timeout_ms=1000)
485
+ def lag(topic_partition_list, watermark_timeout_ms = Defaults::CONSUMER_LAG_TIMEOUT_MS)
345
486
  out = {}
346
487
 
347
488
  topic_partition_list.to_h.each do |topic, partitions|
@@ -409,7 +550,7 @@ module Rdkafka
409
550
  )
410
551
  end
411
552
 
412
- if response != 0
553
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
413
554
  raise Rdkafka::RdkafkaError.new(response)
414
555
  end
415
556
  ensure
@@ -451,9 +592,9 @@ module Rdkafka
451
592
  native_topic,
452
593
  partition,
453
594
  offset,
454
- 0 # timeout
595
+ Defaults::CONSUMER_SEEK_TIMEOUT_MS
455
596
  )
456
- if response != 0
597
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
457
598
  raise Rdkafka::RdkafkaError.new(response)
458
599
  end
459
600
  ensure
@@ -465,11 +606,10 @@ module Rdkafka
465
606
  # Lookup offset for the given partitions by timestamp.
466
607
  #
467
608
  # @param list [TopicPartitionList] The TopicPartitionList with timestamps instead of offsets
468
- #
609
+ # @param timeout_ms [Integer] timeout in milliseconds for the operation
469
610
  # @return [TopicPartitionList]
470
- #
471
611
  # @raise [RdKafkaError] When the OffsetForTimes lookup fails
472
- def offsets_for_times(list, timeout_ms = 1000)
612
+ def offsets_for_times(list, timeout_ms = Defaults::CONSUMER_OFFSETS_FOR_TIMES_TIMEOUT_MS)
473
613
  closed_consumer_check(__method__)
474
614
 
475
615
  if !list.is_a?(TopicPartitionList)
@@ -486,7 +626,7 @@ module Rdkafka
486
626
  )
487
627
  end
488
628
 
489
- if response != 0
629
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
490
630
  raise Rdkafka::RdkafkaError.new(response)
491
631
  end
492
632
 
@@ -508,20 +648,20 @@ module Rdkafka
508
648
  # @param async [Boolean] Whether to commit async or wait for the commit to finish
509
649
  # @return [nil]
510
650
  # @raise [RdkafkaError] When committing fails
511
- def commit(list=nil, async=false)
651
+ def commit(list = nil, async = false)
512
652
  closed_consumer_check(__method__)
513
653
 
514
654
  if !list.nil? && !list.is_a?(TopicPartitionList)
515
655
  raise TypeError.new("list has to be nil or a TopicPartitionList")
516
656
  end
517
657
 
518
- tpl = list ? list.to_native_tpl : nil
658
+ tpl = list&.to_native_tpl
519
659
 
520
660
  begin
521
661
  response = @native_kafka.with_inner do |inner|
522
662
  Rdkafka::Bindings.rd_kafka_commit(inner, tpl, async)
523
663
  end
524
- if response != 0
664
+ if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
525
665
  raise Rdkafka::RdkafkaError.new(response)
526
666
  end
527
667
  ensure
@@ -546,7 +686,48 @@ module Rdkafka
546
686
  # Create struct wrapper
547
687
  native_message = Rdkafka::Bindings::Message.new(message_ptr)
548
688
  # Raise error if needed
549
- if native_message[:err] != 0
689
+ if native_message[:err] != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
690
+ raise Rdkafka::RdkafkaError.new(native_message[:err])
691
+ end
692
+ # Create a message to pass out
693
+ Rdkafka::Consumer::Message.new(native_message)
694
+ end
695
+ ensure
696
+ # Clean up rdkafka message if there is one
697
+ if message_ptr && !message_ptr.null?
698
+ Rdkafka::Bindings.rd_kafka_message_destroy(message_ptr)
699
+ end
700
+ end
701
+
702
+ # Poll for the next message without releasing the GVL (Global VM Lock).
703
+ #
704
+ # This is more efficient than regular polling for non-blocking poll(0) calls,
705
+ # particularly useful in fiber scheduler contexts where GVL release/reacquire
706
+ # overhead is wasteful since we don't expect to wait.
707
+ #
708
+ # @param timeout_ms [Integer] Timeout of this poll (default: 0 for non-blocking)
709
+ # @return [Message, nil] A message or nil if there was no new message within the timeout
710
+ # @raise [RdkafkaError] When polling fails
711
+ #
712
+ # @example Using with fiber scheduler
713
+ # # After receiving IO notification that messages are available
714
+ # while msg = consumer.poll_nb
715
+ # process(msg)
716
+ # end
717
+ def poll_nb(timeout_ms = 0)
718
+ closed_consumer_check(__method__)
719
+
720
+ message_ptr = @native_kafka.with_inner do |inner|
721
+ Rdkafka::Bindings.rd_kafka_consumer_poll_nb(inner, timeout_ms)
722
+ end
723
+
724
+ if message_ptr.null?
725
+ nil
726
+ else
727
+ # Create struct wrapper
728
+ native_message = Rdkafka::Bindings::Message.new(message_ptr)
729
+ # Raise error if needed
730
+ if native_message[:err] != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
550
731
  raise Rdkafka::RdkafkaError.new(native_message[:err])
551
732
  end
552
733
  # Create a message to pass out
@@ -579,37 +760,58 @@ module Rdkafka
579
760
  # @note This method technically should be called `#poll` and the current `#poll` should be
580
761
  # called `#consumer_poll` though we keep the current naming convention to make it backward
581
762
  # compatible.
582
- def events_poll(timeout_ms = 0)
763
+ def events_poll(timeout_ms = Defaults::CONSUMER_EVENTS_POLL_TIMEOUT_MS)
583
764
  @native_kafka.with_inner do |inner|
584
765
  Rdkafka::Bindings.rd_kafka_poll(inner, timeout_ms)
585
766
  end
586
767
  end
587
768
 
769
+ # Polls the main rdkafka queue without releasing the GVL (Global VM Lock).
770
+ #
771
+ # This is more efficient than regular events_poll for non-blocking poll(0) calls,
772
+ # particularly useful in fiber scheduler contexts where GVL release/reacquire
773
+ # overhead is wasteful since we don't expect to wait.
774
+ #
775
+ # @param timeout_ms [Integer] poll timeout (default: 0 for non-blocking)
776
+ # @return [Integer] the number of events served
777
+ #
778
+ # @see #events_poll for more details on when to use this method
779
+ def events_poll_nb(timeout_ms = 0)
780
+ @native_kafka.with_inner do |inner|
781
+ Rdkafka::Bindings.rd_kafka_poll_nb(inner, timeout_ms)
782
+ end
783
+ end
784
+
588
785
  # Poll for new messages and yield for each received one. Iteration
589
786
  # will end when the consumer is closed.
590
787
  #
591
788
  # If `enable.partition.eof` is turned on in the config this will raise an error when an eof is
592
789
  # reached, so you probably want to disable that when using this method of iteration.
593
790
  #
791
+ # @param timeout_ms [Integer] Timeout for each poll iteration
594
792
  # @yieldparam message [Message] Received message
595
793
  # @return [nil]
596
794
  # @raise [RdkafkaError] When polling fails
597
- def each
795
+ def each(timeout_ms: Defaults::CONSUMER_POLL_TIMEOUT_MS)
598
796
  loop do
599
- message = poll(250)
797
+ message = poll(timeout_ms)
600
798
  if message
601
799
  yield(message)
800
+ elsif closed?
801
+ break
602
802
  else
603
- if closed?
604
- break
605
- else
606
- next
607
- end
803
+ next
608
804
  end
609
805
  end
610
806
  end
611
807
 
612
- # Deprecated. Please read the error message for more details.
808
+ # @deprecated This method has been removed due to data consistency concerns
809
+ # @param max_items [Integer] unused
810
+ # @param bytes_threshold [Numeric] unused
811
+ # @param timeout_ms [Integer] unused
812
+ # @param yield_on_error [Boolean] unused
813
+ # @param block [Proc] unused block
814
+ # @raise [NotImplementedError] Always raises as this method is no longer supported
613
815
  def each_batch(max_items: 100, bytes_threshold: Float::INFINITY, timeout_ms: 250, yield_on_error: false, &block)
614
816
  raise NotImplementedError, <<~ERROR
615
817
  `each_batch` has been removed due to data consistency concerns.
@@ -646,6 +848,9 @@ module Rdkafka
646
848
 
647
849
  private
648
850
 
851
+ # Checks if the consumer is closed and raises an error if so
852
+ # @param method [Symbol] name of the calling method for error context
853
+ # @raise [ClosedConsumerError] when the consumer is closed
649
854
  def closed_consumer_check(method)
650
855
  raise Rdkafka::ClosedConsumerError.new(method) if closed?
651
856
  end