rdkafka 0.25.0-aarch64-linux-gnu → 0.26.0-aarch64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/Gemfile +5 -6
  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/bin/verify_kafka_warnings +2 -0
  9. data/docker-compose-ssl.yml +2 -2
  10. data/docker-compose.yml +2 -2
  11. data/ext/librdkafka.so +0 -0
  12. data/lib/rdkafka/admin/acl_binding_result.rb +4 -4
  13. data/lib/rdkafka/admin/create_acl_handle.rb +4 -4
  14. data/lib/rdkafka/admin/create_acl_report.rb +0 -2
  15. data/lib/rdkafka/admin/create_partitions_handle.rb +5 -5
  16. data/lib/rdkafka/admin/create_topic_handle.rb +5 -5
  17. data/lib/rdkafka/admin/delete_acl_handle.rb +6 -6
  18. data/lib/rdkafka/admin/delete_acl_report.rb +2 -3
  19. data/lib/rdkafka/admin/delete_groups_handle.rb +5 -5
  20. data/lib/rdkafka/admin/delete_topic_handle.rb +5 -5
  21. data/lib/rdkafka/admin/describe_acl_handle.rb +6 -6
  22. data/lib/rdkafka/admin/describe_acl_report.rb +2 -3
  23. data/lib/rdkafka/admin/describe_configs_handle.rb +4 -4
  24. data/lib/rdkafka/admin/describe_configs_report.rb +1 -1
  25. data/lib/rdkafka/admin/incremental_alter_configs_handle.rb +4 -4
  26. data/lib/rdkafka/admin/incremental_alter_configs_report.rb +1 -1
  27. data/lib/rdkafka/admin/list_offsets_handle.rb +36 -0
  28. data/lib/rdkafka/admin/list_offsets_report.rb +51 -0
  29. data/lib/rdkafka/admin.rb +189 -24
  30. data/lib/rdkafka/bindings.rb +121 -84
  31. data/lib/rdkafka/callbacks.rb +53 -10
  32. data/lib/rdkafka/config.rb +20 -20
  33. data/lib/rdkafka/consumer/message.rb +5 -8
  34. data/lib/rdkafka/consumer/partition.rb +2 -2
  35. data/lib/rdkafka/consumer/topic_partition_list.rb +10 -10
  36. data/lib/rdkafka/consumer.rb +208 -18
  37. data/lib/rdkafka/error.rb +25 -14
  38. data/lib/rdkafka/helpers/oauth.rb +0 -1
  39. data/lib/rdkafka/helpers/time.rb +5 -0
  40. data/lib/rdkafka/metadata.rb +16 -16
  41. data/lib/rdkafka/native_kafka.rb +63 -2
  42. data/lib/rdkafka/producer/delivery_handle.rb +5 -5
  43. data/lib/rdkafka/producer/delivery_report.rb +1 -1
  44. data/lib/rdkafka/producer/partitions_count_cache.rb +6 -6
  45. data/lib/rdkafka/producer.rb +117 -57
  46. data/lib/rdkafka/version.rb +3 -3
  47. data/lib/rdkafka.rb +2 -0
  48. data/package-lock.json +331 -0
  49. data/package.json +9 -0
  50. data/rdkafka.gemspec +39 -40
  51. data/renovate.json +21 -0
  52. metadata +8 -2
@@ -53,13 +53,11 @@ module Rdkafka
53
53
  # Set timestamp
54
54
  raw_timestamp = Rdkafka::Bindings.rd_kafka_message_timestamp(native_message, nil)
55
55
  @timestamp = if raw_timestamp && raw_timestamp > -1
56
- # Calculate seconds and microseconds
57
- seconds = raw_timestamp / 1000
58
- milliseconds = (raw_timestamp - seconds * 1000) * 1000
59
- Time.at(seconds, milliseconds)
60
- else
61
- nil
62
- end
56
+ # Calculate seconds and microseconds
57
+ seconds = raw_timestamp / 1000
58
+ milliseconds = (raw_timestamp - seconds * 1000) * 1000
59
+ Time.at(seconds, milliseconds)
60
+ end
63
61
 
64
62
  @headers = Headers.from_native(native_message)
65
63
  end
@@ -84,7 +82,6 @@ module Rdkafka
84
82
  string
85
83
  end
86
84
  end
87
-
88
85
  end
89
86
  end
90
87
  end
@@ -47,8 +47,8 @@ module Rdkafka
47
47
  # @return [Boolean]
48
48
  def ==(other)
49
49
  self.class == other.class &&
50
- self.partition == other.partition &&
51
- self.offset == other.offset
50
+ partition == other.partition &&
51
+ offset == other.offset
52
52
  end
53
53
  end
54
54
  end
@@ -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,7 +49,7 @@ 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
- def add_topic(topic, partitions=nil)
52
+ def add_topic(topic, partitions = nil)
53
53
  if partitions.nil?
54
54
  @data[topic.to_s] = nil
55
55
  else
@@ -90,7 +90,7 @@ module Rdkafka
90
90
  # @param other [TopicPartitionList] object to compare with
91
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.
@@ -114,10 +114,10 @@ module Rdkafka
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
@@ -19,6 +19,9 @@ module Rdkafka
19
19
  # @param native_kafka [NativeKafka] wrapper around the native Kafka consumer handle
20
20
  def initialize(native_kafka)
21
21
  @native_kafka = native_kafka
22
+
23
+ # Makes sure, that native kafka gets closed before it gets GCed by Ruby
24
+ ObjectSpace.define_finalizer(self, native_kafka.finalizer)
22
25
  end
23
26
 
24
27
  # Starts the native Kafka polling thread and kicks off the init polling
@@ -34,10 +37,142 @@ module Rdkafka
34
37
  end
35
38
  end
36
39
 
37
- # @return [Proc] finalizer proc for closing the consumer
38
- # @private
39
- def finalizer
40
- ->(_) { close }
40
+ # Enable IO event notifications for fiber scheduler integration
41
+ # When the consumer queue has messages, librdkafka will write to your FD
42
+ #
43
+ # @param fd [Integer] file descriptor to signal (from IO.pipe or eventfd)
44
+ # @param payload [String] data to write to fd (default: "\x01")
45
+ # @return [nil]
46
+ # @raise [ClosedInnerError] when the consumer is closed
47
+ #
48
+ # @example Using with fiber scheduler
49
+ # consumer = config.consumer
50
+ # consumer.subscribe("topic")
51
+ #
52
+ # # Create notification FD
53
+ # signal_r, signal_w = IO.pipe
54
+ #
55
+ # # Enable librdkafka to signal when messages arrive
56
+ # consumer.enable_queue_io_events(signal_w.fileno)
57
+ #
58
+ # # Monitor with select/poll
59
+ # loop do
60
+ # readable, = IO.select([signal_r], nil, nil, timeout)
61
+ # if readable
62
+ # signal_r.read_nonblock(1024) rescue nil # Drain signal
63
+ # while msg = consumer.poll(0)
64
+ # process(msg)
65
+ # end
66
+ # end
67
+ # end
68
+ def enable_queue_io_events(fd, payload = "\x01")
69
+ @native_kafka.enable_main_queue_io_events(fd, payload)
70
+ end
71
+
72
+ # Enable IO event notifications for background events
73
+ # @param fd [Integer] file descriptor to signal (from IO.pipe or eventfd)
74
+ # @param payload [String] data to write to fd (default: "\x01")
75
+ # @return [nil]
76
+ # @raise [ClosedInnerError] when the consumer is closed
77
+ def enable_background_queue_io_events(fd, payload = "\x01")
78
+ @native_kafka.enable_background_queue_io_events(fd, payload)
79
+ end
80
+
81
+ # Polls for events in a non-blocking loop, yielding the count after each iteration.
82
+ #
83
+ # This method processes events (stats, errors, etc.) in a single GVL/mutex session,
84
+ # which is more efficient than repeated individual polls. It uses non-blocking polls
85
+ # internally (no GVL release between polls).
86
+ #
87
+ # Yields the count of events processed after each poll iteration, allowing the caller
88
+ # to implement timeout or other termination logic by returning `:stop`.
89
+ #
90
+ # @yield [count] Called after each poll iteration
91
+ # @yieldparam count [Integer] Number of events processed in this iteration
92
+ # @yieldreturn [Symbol, Object] Return `:stop` to break the loop, any other value continues
93
+ # @return [nil]
94
+ # @raise [Rdkafka::ClosedConsumerError] if called on a closed consumer
95
+ #
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
+ # @example Drain all pending events
102
+ # consumer.events_poll_nb_each { |_count| }
103
+ #
104
+ # @example With timeout control
105
+ # deadline = monotonic_now + timeout_ms
106
+ # consumer.events_poll_nb_each do |_count|
107
+ # :stop if monotonic_now >= deadline
108
+ # end
109
+ def events_poll_nb_each
110
+ closed_consumer_check(__method__)
111
+
112
+ @native_kafka.with_inner do |inner|
113
+ loop do
114
+ count = Rdkafka::Bindings.rd_kafka_poll_nb(inner, 0)
115
+ break if count.zero?
116
+ break if yield(count) == :stop
117
+ end
118
+ end
119
+ end
120
+
121
+ # Polls for messages in a non-blocking loop, yielding each message to the caller.
122
+ #
123
+ # This method processes messages in a single GVL/mutex session until the queue is empty
124
+ # or the caller returns `:stop`. It handles the message pointer lifecycle internally,
125
+ # ensuring proper cleanup via `rd_kafka_message_destroy`.
126
+ #
127
+ # @yield [message] Called for each message received
128
+ # @yieldparam message [Consumer::Message] The received message
129
+ # @yieldreturn [Symbol, Object] Return `:stop` to break the loop, any other value continues
130
+ # @return [nil]
131
+ # @raise [Rdkafka::ClosedConsumerError] if called on a closed consumer
132
+ # @raise [Rdkafka::RdkafkaError] if a Kafka error occurs while polling
133
+ #
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
+ # @example Process messages until queue is empty
143
+ # consumer.poll_nb_each do |message|
144
+ # process(message)
145
+ # end
146
+ #
147
+ # @example Process with early termination
148
+ # count = 0
149
+ # consumer.poll_nb_each do |message|
150
+ # process(message)
151
+ # count += 1
152
+ # :stop if count >= 10
153
+ # end
154
+ def poll_nb_each
155
+ closed_consumer_check(__method__)
156
+
157
+ @native_kafka.with_inner do |inner|
158
+ loop do
159
+ message_ptr = Rdkafka::Bindings.rd_kafka_consumer_poll_nb(inner, 0)
160
+ break if message_ptr.null?
161
+
162
+ begin
163
+ native_message = Rdkafka::Bindings::Message.new(message_ptr)
164
+
165
+ if native_message[:err] != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
166
+ raise Rdkafka::RdkafkaError.new(native_message[:err])
167
+ end
168
+
169
+ result = yield Consumer::Message.new(native_message)
170
+ break if result == :stop
171
+ ensure
172
+ Rdkafka::Bindings.rd_kafka_message_destroy(message_ptr)
173
+ end
174
+ end
175
+ end
41
176
  end
42
177
 
43
178
  # Close this consumer
@@ -78,7 +213,7 @@ module Rdkafka
78
213
  Rdkafka::Bindings.rd_kafka_subscribe(inner, tpl)
79
214
  end
80
215
  if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
81
- raise Rdkafka::RdkafkaError.new(response, "Error subscribing to '#{topics.join(', ')}'")
216
+ raise Rdkafka::RdkafkaError.new(response, "Error subscribing to '#{topics.join(", ")}'")
82
217
  end
83
218
  ensure
84
219
  Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl) unless tpl.nil?
@@ -228,7 +363,7 @@ module Rdkafka
228
363
  end
229
364
  end
230
365
  ensure
231
- ptr.free unless ptr.nil?
366
+ ptr&.free
232
367
  end
233
368
 
234
369
  # @return [Boolean] true if our current assignment has been lost involuntarily.
@@ -281,7 +416,7 @@ module Rdkafka
281
416
  # @return [TopicPartitionList]
282
417
  #
283
418
  # @raise [RdkafkaError] When getting the positions fails.
284
- def position(list=nil)
419
+ def position(list = nil)
285
420
  if list.nil?
286
421
  list = assignment
287
422
  elsif !list.is_a?(TopicPartitionList)
@@ -321,17 +456,17 @@ module Rdkafka
321
456
  partition,
322
457
  low,
323
458
  high,
324
- timeout_ms,
459
+ timeout_ms
325
460
  )
326
461
  end
327
462
  if response != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
328
463
  raise Rdkafka::RdkafkaError.new(response, "Error querying watermark offsets for partition #{partition} of #{topic}")
329
464
  end
330
465
 
331
- return low.read_array_of_int64(1).first, high.read_array_of_int64(1).first
466
+ [low.read_array_of_int64(1).first, high.read_array_of_int64(1).first]
332
467
  ensure
333
- low.free unless low.nil?
334
- high.free unless high.nil?
468
+ low&.free
469
+ high&.free
335
470
  end
336
471
 
337
472
  # Calculate the consumer lag per partition for the provided topic partition list.
@@ -510,14 +645,14 @@ module Rdkafka
510
645
  # @param async [Boolean] Whether to commit async or wait for the commit to finish
511
646
  # @return [nil]
512
647
  # @raise [RdkafkaError] When committing fails
513
- def commit(list=nil, async=false)
648
+ def commit(list = nil, async = false)
514
649
  closed_consumer_check(__method__)
515
650
 
516
651
  if !list.nil? && !list.is_a?(TopicPartitionList)
517
652
  raise TypeError.new("list has to be nil or a TopicPartitionList")
518
653
  end
519
654
 
520
- tpl = list ? list.to_native_tpl : nil
655
+ tpl = list&.to_native_tpl
521
656
 
522
657
  begin
523
658
  response = @native_kafka.with_inner do |inner|
@@ -561,6 +696,47 @@ module Rdkafka
561
696
  end
562
697
  end
563
698
 
699
+ # Poll for the next message without releasing the GVL (Global VM Lock).
700
+ #
701
+ # This is more efficient than regular polling for non-blocking poll(0) calls,
702
+ # particularly useful in fiber scheduler contexts where GVL release/reacquire
703
+ # overhead is wasteful since we don't expect to wait.
704
+ #
705
+ # @param timeout_ms [Integer] Timeout of this poll (default: 0 for non-blocking)
706
+ # @return [Message, nil] A message or nil if there was no new message within the timeout
707
+ # @raise [RdkafkaError] When polling fails
708
+ #
709
+ # @example Using with fiber scheduler
710
+ # # After receiving IO notification that messages are available
711
+ # while msg = consumer.poll_nb
712
+ # process(msg)
713
+ # end
714
+ def poll_nb(timeout_ms = 0)
715
+ closed_consumer_check(__method__)
716
+
717
+ message_ptr = @native_kafka.with_inner do |inner|
718
+ Rdkafka::Bindings.rd_kafka_consumer_poll_nb(inner, timeout_ms)
719
+ end
720
+
721
+ if message_ptr.null?
722
+ nil
723
+ else
724
+ # Create struct wrapper
725
+ native_message = Rdkafka::Bindings::Message.new(message_ptr)
726
+ # Raise error if needed
727
+ if native_message[:err] != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
728
+ raise Rdkafka::RdkafkaError.new(native_message[:err])
729
+ end
730
+ # Create a message to pass out
731
+ Rdkafka::Consumer::Message.new(native_message)
732
+ end
733
+ ensure
734
+ # Clean up rdkafka message if there is one
735
+ if message_ptr && !message_ptr.null?
736
+ Rdkafka::Bindings.rd_kafka_message_destroy(message_ptr)
737
+ end
738
+ end
739
+
564
740
  # Polls the main rdkafka queue (not the consumer one). Do **NOT** use it if `consumer_poll_set`
565
741
  # was set to `true`.
566
742
  #
@@ -587,6 +763,22 @@ module Rdkafka
587
763
  end
588
764
  end
589
765
 
766
+ # Polls the main rdkafka queue without releasing the GVL (Global VM Lock).
767
+ #
768
+ # This is more efficient than regular events_poll for non-blocking poll(0) calls,
769
+ # particularly useful in fiber scheduler contexts where GVL release/reacquire
770
+ # overhead is wasteful since we don't expect to wait.
771
+ #
772
+ # @param timeout_ms [Integer] poll timeout (default: 0 for non-blocking)
773
+ # @return [Integer] the number of events served
774
+ #
775
+ # @see #events_poll for more details on when to use this method
776
+ def events_poll_nb(timeout_ms = 0)
777
+ @native_kafka.with_inner do |inner|
778
+ Rdkafka::Bindings.rd_kafka_poll_nb(inner, timeout_ms)
779
+ end
780
+ end
781
+
590
782
  # Poll for new messages and yield for each received one. Iteration
591
783
  # will end when the consumer is closed.
592
784
  #
@@ -602,12 +794,10 @@ module Rdkafka
602
794
  message = poll(timeout_ms)
603
795
  if message
604
796
  yield(message)
797
+ elsif closed?
798
+ break
605
799
  else
606
- if closed?
607
- break
608
- else
609
- next
610
- end
800
+ next
611
801
  end
612
802
  end
613
803
  end
data/lib/rdkafka/error.rb CHANGED
@@ -18,15 +18,21 @@ module Rdkafka
18
18
  # @return [String]
19
19
  attr_reader :broker_message
20
20
 
21
+ # The name of the rdkafka instance that generated this error
22
+ # @return [String, nil]
23
+ attr_reader :instance_name
24
+
21
25
  # @private
22
26
  # @param response [Integer] the raw error response code from librdkafka
23
27
  # @param message_prefix [String, nil] optional prefix for error messages
24
28
  # @param broker_message [String, nil] optional error message from the broker
25
- def initialize(response, message_prefix=nil, broker_message: nil)
29
+ # @param instance_name [String, nil] optional name of the rdkafka instance
30
+ def initialize(response, message_prefix = nil, broker_message: nil, instance_name: nil)
26
31
  raise TypeError.new("Response has to be an integer") unless response.is_a? Integer
27
32
  @rdkafka_response = response
28
33
  @message_prefix = message_prefix
29
34
  @broker_message = broker_message
35
+ @instance_name = instance_name
30
36
  end
31
37
 
32
38
  # This error's code, for example `:partition_eof`, `:msg_size_too_large`.
@@ -34,7 +40,7 @@ module Rdkafka
34
40
  def code
35
41
  code = Rdkafka::Bindings.rd_kafka_err2name(@rdkafka_response).downcase
36
42
  if code[0] == "_"
37
- code[1..-1].to_sym
43
+ code[1..].to_sym
38
44
  else
39
45
  code.to_sym
40
46
  end
@@ -44,11 +50,16 @@ module Rdkafka
44
50
  # @return [String]
45
51
  def to_s
46
52
  message_prefix_part = if message_prefix
47
- "#{message_prefix} - "
48
- else
49
- ''
50
- end
51
- "#{message_prefix_part}#{Rdkafka::Bindings.rd_kafka_err2str(@rdkafka_response)} (#{code})"
53
+ "#{message_prefix} - "
54
+ else
55
+ ""
56
+ end
57
+ instance_name_part = if instance_name
58
+ " [#{instance_name}]"
59
+ else
60
+ ""
61
+ end
62
+ "#{message_prefix_part}#{Rdkafka::Bindings.rd_kafka_err2str(@rdkafka_response)} (#{code})#{instance_name_part}"
52
63
  end
53
64
 
54
65
  # Whether this error indicates the partition is EOF.
@@ -58,10 +69,10 @@ module Rdkafka
58
69
  end
59
70
 
60
71
  # Error comparison
61
- # @param another_error [Object] object to compare with
72
+ # @param other [Object] object to compare with
62
73
  # @return [Boolean]
63
- def ==(another_error)
64
- another_error.is_a?(self.class) && (self.to_s == another_error.to_s)
74
+ def ==(other)
75
+ other.is_a?(self.class) && (to_s == other.to_s)
65
76
  end
66
77
  end
67
78
 
@@ -74,7 +85,7 @@ module Rdkafka
74
85
  # @param response [Integer] the raw error response code from librdkafka
75
86
  # @param topic_partition_list [TopicPartitionList] the topic partition list with error info
76
87
  # @param message_prefix [String, nil] optional prefix for error messages
77
- def initialize(response, topic_partition_list, message_prefix=nil)
88
+ def initialize(response, topic_partition_list, message_prefix = nil)
78
89
  super(response, message_prefix)
79
90
  @topic_partition_list = topic_partition_list
80
91
  end
@@ -84,7 +95,7 @@ module Rdkafka
84
95
  class ClosedConsumerError < BaseError
85
96
  # @param method [Symbol] the method that was called
86
97
  def initialize(method)
87
- super("Illegal call to #{method.to_s} on a closed consumer")
98
+ super("Illegal call to #{method} on a closed consumer")
88
99
  end
89
100
  end
90
101
 
@@ -92,7 +103,7 @@ module Rdkafka
92
103
  class ClosedProducerError < BaseError
93
104
  # @param method [Symbol] the method that was called
94
105
  def initialize(method)
95
- super("Illegal call to #{method.to_s} on a closed producer")
106
+ super("Illegal call to #{method} on a closed producer")
96
107
  end
97
108
  end
98
109
 
@@ -100,7 +111,7 @@ module Rdkafka
100
111
  class ClosedAdminError < BaseError
101
112
  # @param method [Symbol] the method that was called
102
113
  def initialize(method)
103
- super("Illegal call to #{method.to_s} on a closed admin")
114
+ super("Illegal call to #{method} on a closed admin")
104
115
  end
105
116
  end
106
117
 
@@ -2,7 +2,6 @@ module Rdkafka
2
2
  module Helpers
3
3
  # OAuth helper methods for setting and refreshing SASL/OAUTHBEARER tokens
4
4
  module OAuth
5
-
6
5
  # Set the OAuthBearer token
7
6
  #
8
7
  # @param token [String] the mandatory token value to set, often (but not necessarily) a JWS compact serialization as per https://tools.ietf.org/html/rfc7515#section-3.1.
@@ -9,6 +9,11 @@ module Rdkafka
9
9
  def monotonic_now
10
10
  ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
11
11
  end
12
+
13
+ # @return [Integer] current monotonic time in milliseconds
14
+ def monotonic_now_ms
15
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
16
+ end
12
17
  end
13
18
  end
14
19
  end
@@ -100,40 +100,40 @@ module Rdkafka
100
100
  # FFI struct for rd_kafka_metadata_t
101
101
  class Metadata < CustomFFIStruct
102
102
  layout :brokers_count, :int,
103
- :brokers_metadata, :pointer,
104
- :topics_count, :int,
105
- :topics_metadata, :pointer,
106
- :broker_id, :int32,
107
- :broker_name, :string
103
+ :brokers_metadata, :pointer,
104
+ :topics_count, :int,
105
+ :topics_metadata, :pointer,
106
+ :broker_id, :int32,
107
+ :broker_name, :string
108
108
  end
109
109
 
110
110
  # @private
111
111
  # FFI struct for rd_kafka_metadata_broker_t
112
112
  class BrokerMetadata < CustomFFIStruct
113
113
  layout :broker_id, :int32,
114
- :broker_name, :string,
115
- :broker_port, :int
114
+ :broker_name, :string,
115
+ :broker_port, :int
116
116
  end
117
117
 
118
118
  # @private
119
119
  # FFI struct for rd_kafka_metadata_topic_t
120
120
  class TopicMetadata < CustomFFIStruct
121
121
  layout :topic_name, :string,
122
- :partition_count, :int,
123
- :partitions_metadata, :pointer,
124
- :rd_kafka_resp_err, :int
122
+ :partition_count, :int,
123
+ :partitions_metadata, :pointer,
124
+ :rd_kafka_resp_err, :int
125
125
  end
126
126
 
127
127
  # @private
128
128
  # FFI struct for rd_kafka_metadata_partition_t
129
129
  class PartitionMetadata < CustomFFIStruct
130
130
  layout :partition_id, :int32,
131
- :rd_kafka_resp_err, :int,
132
- :leader, :int32,
133
- :replica_count, :int,
134
- :replicas, :pointer,
135
- :in_sync_replica_brokers, :int,
136
- :isrs, :pointer
131
+ :rd_kafka_resp_err, :int,
132
+ :leader, :int32,
133
+ :replica_count, :int,
134
+ :replicas, :pointer,
135
+ :in_sync_replica_brokers, :int,
136
+ :isrs, :pointer
137
137
  end
138
138
  end
139
139
  end
@@ -70,7 +70,7 @@ module Rdkafka
70
70
  end
71
71
  end
72
72
 
73
- @polling_thread.name = "rdkafka.native_kafka##{Rdkafka::Bindings.rd_kafka_name(@inner).gsub('rdkafka', '')}"
73
+ @polling_thread.name = "rdkafka.native_kafka##{Rdkafka::Bindings.rd_kafka_name(@inner).gsub("rdkafka", "")}"
74
74
  @polling_thread.abort_on_exception = true
75
75
  @polling_thread[:closing] = false
76
76
  end
@@ -121,11 +121,72 @@ module Rdkafka
121
121
  @closing || @inner.nil?
122
122
  end
123
123
 
124
+ # Enable IO event notifications on the main queue
125
+ # Librdkafka will write to your FD when the queue transitions from empty to non-empty
126
+ #
127
+ # @note This method is incompatible with background polling threads.
128
+ # If background polling is enabled, use manual polling instead (e.g., consumer.poll)
129
+ #
130
+ # @param fd [Integer] your file descriptor (from IO.pipe or eventfd)
131
+ # @param payload [String] data to write to fd when queue has data (default: "\x01")
132
+ # @return [nil]
133
+ # @raise [ClosedInnerError] when the handle is closed
134
+ # @raise [RuntimeError] when background polling thread is active
135
+ #
136
+ # @example
137
+ # # Create your own signaling FD
138
+ # signal_r, signal_w = IO.pipe
139
+ # native_kafka.enable_main_queue_io_events(signal_w.fileno)
140
+ #
141
+ # # Monitor it with select
142
+ # readable, = IO.select([signal_r], nil, nil, timeout)
143
+ # if readable
144
+ # consumer.poll(0) # Get messages
145
+ # end
146
+ def enable_main_queue_io_events(fd, payload = "\x01")
147
+ if @run_polling_thread
148
+ raise "Cannot enable IO events while background polling thread is active. " \
149
+ "Either disable background polling by setting run_polling_thread: false, " \
150
+ "or use manual polling with consumer.poll() instead of the FD API."
151
+ end
152
+
153
+ with_inner do |inner|
154
+ queue_ptr = Bindings.rd_kafka_queue_get_main(inner)
155
+ Bindings.rd_kafka_queue_io_event_enable(queue_ptr, fd, payload, payload.bytesize)
156
+ Bindings.rd_kafka_queue_destroy(queue_ptr)
157
+ end
158
+ end
159
+
160
+ # Enable IO event notifications on the background queue
161
+ # Librdkafka will write to your FD when the background queue transitions from empty to non-empty
162
+ #
163
+ # @note This method is incompatible with background polling threads.
164
+ # If background polling is enabled, use manual polling instead (e.g., consumer.poll)
165
+ #
166
+ # @param fd [Integer] your file descriptor (from IO.pipe or eventfd)
167
+ # @param payload [String] data to write to fd when queue has data (default: "\x01")
168
+ # @return [nil]
169
+ # @raise [ClosedInnerError] when the handle is closed
170
+ # @raise [RuntimeError] when background polling thread is active
171
+ def enable_background_queue_io_events(fd, payload = "\x01")
172
+ if @run_polling_thread
173
+ raise "Cannot enable IO events while background polling thread is active. " \
174
+ "Either disable background polling by setting run_polling_thread: false, " \
175
+ "or use manual polling with consumer.poll() instead of the FD API."
176
+ end
177
+
178
+ with_inner do |inner|
179
+ queue_ptr = Bindings.rd_kafka_queue_get_background(inner)
180
+ Bindings.rd_kafka_queue_io_event_enable(queue_ptr, fd, payload, payload.bytesize)
181
+ Bindings.rd_kafka_queue_destroy(queue_ptr)
182
+ end
183
+ end
184
+
124
185
  # Closes the native Kafka handle and cleans up resources
125
186
  # @param object_id [Integer, nil] optional object ID (unused, for finalizer compatibility)
126
187
  # @yield optional block to execute before destroying the handle
127
188
  # @return [nil]
128
- def close(object_id=nil)
189
+ def close(object_id = nil)
129
190
  return if closed?
130
191
 
131
192
  synchronize do