rdkafka 0.29.0-aarch64-linux-gnu → 0.29.1-aarch64-linux-gnu
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -24
- data/README.md +2 -2
- data/bin/verify_kafka_warnings +5 -0
- data/docker-compose-ssl.yml +3 -0
- data/docker-compose.yml +3 -0
- data/ext/librdkafka.so +0 -0
- data/lib/rdkafka/admin/create_acl_report.rb +2 -1
- data/lib/rdkafka/admin/delete_records_handle.rb +31 -0
- data/lib/rdkafka/admin/delete_records_report.rb +25 -0
- data/lib/rdkafka/admin/describe_acl_report.rb +2 -1
- data/lib/rdkafka/admin/list_consumer_groups_handle.rb +31 -0
- data/lib/rdkafka/admin/list_consumer_groups_report.rb +83 -0
- data/lib/rdkafka/admin/list_offsets_report.rb +5 -2
- data/lib/rdkafka/admin.rb +178 -117
- data/lib/rdkafka/bindings.rb +52 -12
- data/lib/rdkafka/callbacks/delete_records_handler.rb +38 -0
- data/lib/rdkafka/callbacks/list_consumer_groups_handler.rb +39 -0
- data/lib/rdkafka/callbacks.rb +4 -2
- data/lib/rdkafka/config.rb +24 -17
- data/lib/rdkafka/consumer/topic_partition_list.rb +14 -8
- data/lib/rdkafka/consumer.rb +103 -34
- data/lib/rdkafka/defaults.rb +5 -7
- data/lib/rdkafka/helpers/list_offsets.rb +127 -0
- data/lib/rdkafka/helpers/oauth.rb +10 -5
- data/lib/rdkafka/native_kafka.rb +20 -7
- data/lib/rdkafka/producer/delivery_handle.rb +1 -2
- data/lib/rdkafka/producer.rb +25 -17
- data/lib/rdkafka/version.rb +1 -1
- data/lib/rdkafka.rb +7 -0
- data/package-lock.json +3 -3
- metadata +9 -2
data/lib/rdkafka/config.rb
CHANGED
|
@@ -3,24 +3,24 @@
|
|
|
3
3
|
module Rdkafka
|
|
4
4
|
# Configuration for a Kafka consumer or producer. You can create an instance and use
|
|
5
5
|
# the consumer and producer methods to create a client. Documentation of the available
|
|
6
|
-
# configuration options is available on
|
|
6
|
+
# configuration options is available on
|
|
7
|
+
# https://github.com/confluentinc/librdkafka/blob/master/CONFIGURATION.md.
|
|
7
8
|
class Config
|
|
8
|
-
#
|
|
9
|
+
# @!visibility private
|
|
9
10
|
@@logger = Logger.new($stdout)
|
|
10
|
-
#
|
|
11
|
+
# @!visibility private
|
|
11
12
|
@@statistics_callback = nil
|
|
12
|
-
#
|
|
13
|
+
# @!visibility private
|
|
13
14
|
@@error_callback = nil
|
|
14
|
-
#
|
|
15
|
+
# @!visibility private
|
|
15
16
|
@@opaques = ObjectSpace::WeakMap.new
|
|
16
|
-
#
|
|
17
|
+
# @!visibility private
|
|
17
18
|
@@log_queue = Queue.new
|
|
18
|
-
# We memoize thread on
|
|
19
|
-
# This allows us also to restart logger thread on forks
|
|
19
|
+
# We memoize thread on first log flush. This allows us also to restart logger thread on forks
|
|
20
20
|
@@log_thread = nil
|
|
21
|
-
#
|
|
21
|
+
# @!visibility private
|
|
22
22
|
@@log_mutex = Mutex.new
|
|
23
|
-
#
|
|
23
|
+
# @!visibility private
|
|
24
24
|
@@oauthbearer_token_refresh_callback = nil
|
|
25
25
|
|
|
26
26
|
# Returns the current logger, by default this is a logger to stdout.
|
|
@@ -69,7 +69,8 @@ module Rdkafka
|
|
|
69
69
|
|
|
70
70
|
# Set a callback that will be called every time the underlying client emits statistics.
|
|
71
71
|
# You can configure if and how often this happens using `statistics.interval.ms`.
|
|
72
|
-
# The callback is called with a hash that's documented here:
|
|
72
|
+
# The callback is called with a hash that's documented here:
|
|
73
|
+
# https://github.com/confluentinc/librdkafka/blob/master/STATISTICS.md
|
|
73
74
|
#
|
|
74
75
|
# @param callback [Proc, #call, nil] callable object or nil to clear
|
|
75
76
|
# @return [nil]
|
|
@@ -86,7 +87,8 @@ module Rdkafka
|
|
|
86
87
|
end
|
|
87
88
|
|
|
88
89
|
# Set a callback that will be called every time the underlying client emits an error.
|
|
89
|
-
# If this callback is not set, global errors such as brokers becoming unavailable will only be
|
|
90
|
+
# If this callback is not set, global errors such as brokers becoming unavailable will only be
|
|
91
|
+
# sent to the logger, as defined by librdkafka.
|
|
90
92
|
# The callback is called with an instance of RdKafka::Error.
|
|
91
93
|
#
|
|
92
94
|
# @param callback [Proc, #call, nil] callable object to handle errors or nil to clear
|
|
@@ -197,6 +199,11 @@ module Rdkafka
|
|
|
197
199
|
opaque = Opaque.new
|
|
198
200
|
config = native_config(opaque)
|
|
199
201
|
|
|
202
|
+
# Set callback to receive background events, so admin-style operations issued on the
|
|
203
|
+
# consumer handle (e.g. Consumer#list_offsets) get their results dispatched back to their
|
|
204
|
+
# handles. librdkafka spawns its internally-managed background thread because of this.
|
|
205
|
+
Rdkafka::Bindings.rd_kafka_conf_set_background_event_cb(config, Rdkafka::Callbacks::BackgroundEventCallbackFunction)
|
|
206
|
+
|
|
200
207
|
if @consumer_rebalance_listener
|
|
201
208
|
opaque.consumer_rebalance_listener = @consumer_rebalance_listener
|
|
202
209
|
Rdkafka::Bindings.rd_kafka_conf_set_rebalance_cb(config, Rdkafka::Bindings::RebalanceCallback)
|
|
@@ -290,6 +297,9 @@ module Rdkafka
|
|
|
290
297
|
# Uses `rd_kafka_conf_dump` to retrieve every property (including defaults and
|
|
291
298
|
# internal properties like `client.software.name`) as a flat Hash.
|
|
292
299
|
#
|
|
300
|
+
# @return [Hash{Symbol => String}] property names mapped to their current values
|
|
301
|
+
#
|
|
302
|
+
# @raise [ConfigError] When the configuration contains invalid options
|
|
293
303
|
# @note The librdkafka C API does not distinguish between producer-only, consumer-only,
|
|
294
304
|
# and global properties at the configuration level. All properties are returned
|
|
295
305
|
# regardless of the intended client type.
|
|
@@ -297,10 +307,6 @@ module Rdkafka
|
|
|
297
307
|
# @note The returned Hash may include sensitive values such as authentication
|
|
298
308
|
# credentials and key passwords. Do not log or serialize the returned data
|
|
299
309
|
# unless you have explicitly redacted secret entries.
|
|
300
|
-
#
|
|
301
|
-
# @return [Hash{Symbol => String}] property names mapped to their current values
|
|
302
|
-
#
|
|
303
|
-
# @raise [ConfigError] When the configuration contains invalid options
|
|
304
310
|
def describe_properties
|
|
305
311
|
config = nil
|
|
306
312
|
dump_ptr = nil
|
|
@@ -325,7 +331,8 @@ module Rdkafka
|
|
|
325
331
|
Rdkafka::Bindings.rd_kafka_conf_destroy(config) if config
|
|
326
332
|
end
|
|
327
333
|
|
|
328
|
-
# Error that is returned by the underlying rdkafka error if an invalid configuration option is
|
|
334
|
+
# Error that is returned by the underlying rdkafka error if an invalid configuration option is
|
|
335
|
+
# present.
|
|
329
336
|
class ConfigError < RuntimeError; end
|
|
330
337
|
|
|
331
338
|
# Error that is returned by the underlying rdkafka library if the client cannot be created.
|
|
@@ -6,7 +6,8 @@ module Rdkafka
|
|
|
6
6
|
class TopicPartitionList
|
|
7
7
|
# Create a topic partition list.
|
|
8
8
|
#
|
|
9
|
-
# @param data [Hash{String => nil,Partition}] The topic and partition data or nil to create an
|
|
9
|
+
# @param data [Hash{String => nil,Partition}] The topic and partition data or nil to create an
|
|
10
|
+
# empty list
|
|
10
11
|
#
|
|
11
12
|
# @return [TopicPartitionList]
|
|
12
13
|
def initialize(data = nil)
|
|
@@ -34,10 +35,12 @@ module Rdkafka
|
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
# Add a topic with optionally partitions to the list.
|
|
37
|
-
# Calling this method multiple times for the same topic will overwrite the previous
|
|
38
|
+
# Calling this method multiple times for the same topic will overwrite the previous
|
|
39
|
+
# configuraton.
|
|
38
40
|
#
|
|
39
41
|
# @param topic [String] The topic's name
|
|
40
|
-
# @param partitions [Array<Integer>, Range<Integer>, Integer] The topic's partitions or
|
|
42
|
+
# @param partitions [Array<Integer>, Range<Integer>, Integer] The topic's partitions or
|
|
43
|
+
# partition count
|
|
41
44
|
#
|
|
42
45
|
# @return [nil]
|
|
43
46
|
#
|
|
@@ -61,11 +64,12 @@ module Rdkafka
|
|
|
61
64
|
end
|
|
62
65
|
|
|
63
66
|
# Add a topic with partitions and offsets set to the list
|
|
64
|
-
# Calling this method multiple times for the same topic will overwrite the previous
|
|
67
|
+
# Calling this method multiple times for the same topic will overwrite the previous
|
|
68
|
+
# configuraton.
|
|
65
69
|
#
|
|
66
70
|
# @param topic [String] The topic's name
|
|
67
|
-
# @param partitions_with_offsets [Hash{Integer => Integer}, Array<Consumer::Partition>] The
|
|
68
|
-
# partitions and offsets (Hash) or partitions with offsets and metadata (Array)
|
|
71
|
+
# @param partitions_with_offsets [Hash{Integer => Integer}, Array<Consumer::Partition>] The
|
|
72
|
+
# topic's partitions and offsets (Hash) or partitions with offsets and metadata (Array)
|
|
69
73
|
# @return [nil]
|
|
70
74
|
def add_topic_and_partitions_with_offsets(topic, partitions_with_offsets)
|
|
71
75
|
@data[topic.to_s] = partitions_with_offsets.map do |p, o|
|
|
@@ -73,7 +77,8 @@ module Rdkafka
|
|
|
73
77
|
end
|
|
74
78
|
end
|
|
75
79
|
|
|
76
|
-
# Return a `Hash` with the topics as keys and and an array of partition information as the
|
|
80
|
+
# Return a `Hash` with the topics as keys and and an array of partition information as the
|
|
81
|
+
# value if present.
|
|
77
82
|
#
|
|
78
83
|
# @return [Hash{String => Array<Partition>,nil}]
|
|
79
84
|
def to_h
|
|
@@ -97,7 +102,8 @@ module Rdkafka
|
|
|
97
102
|
#
|
|
98
103
|
# @private
|
|
99
104
|
#
|
|
100
|
-
# @param pointer [FFI::Pointer] Optional pointer to an existing native list. Its contents will
|
|
105
|
+
# @param pointer [FFI::Pointer] Optional pointer to an existing native list. Its contents will
|
|
106
|
+
# be copied.
|
|
101
107
|
#
|
|
102
108
|
# @return [TopicPartitionList]
|
|
103
109
|
def self.from_native_tpl(pointer)
|
data/lib/rdkafka/consumer.rb
CHANGED
|
@@ -15,6 +15,7 @@ module Rdkafka
|
|
|
15
15
|
include Helpers::Time
|
|
16
16
|
include Helpers::OAuth
|
|
17
17
|
include Helpers::Metadata
|
|
18
|
+
include Helpers::ListOffsets
|
|
18
19
|
|
|
19
20
|
# @private
|
|
20
21
|
# @param native_kafka [NativeKafka] wrapper around the native Kafka consumer handle
|
|
@@ -34,11 +35,13 @@ module Rdkafka
|
|
|
34
35
|
# consumer-queue reference, then destroy the native client. The default `NativeKafka#finalizer`
|
|
35
36
|
# went straight to `rd_kafka_destroy`, leaving the consumer-queue reference (from
|
|
36
37
|
# `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
|
|
38
|
+
# `rd_kafka_destroy` block inside the finalizer (process hang at GC/shutdown) or leak the
|
|
39
|
+
# handle.
|
|
38
40
|
#
|
|
39
41
|
# @private
|
|
40
42
|
# @param native_kafka [NativeKafka] the wrapped native client
|
|
41
|
-
# @param queue_holder [Array] single-element holder carrying the consumer queue pointer
|
|
43
|
+
# @param queue_holder [Array] single-element holder carrying the consumer queue pointer
|
|
44
|
+
# (or empty)
|
|
42
45
|
# @return [Proc] finalizer proc that must not reference the consumer instance
|
|
43
46
|
def self.finalizer(native_kafka, queue_holder)
|
|
44
47
|
proc do
|
|
@@ -124,11 +127,6 @@ module Rdkafka
|
|
|
124
127
|
# @return [nil]
|
|
125
128
|
# @raise [Rdkafka::ClosedConsumerError] if called on a closed consumer
|
|
126
129
|
#
|
|
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
130
|
# @example Drain all pending events
|
|
133
131
|
# consumer.events_poll_nb_each { |_count| }
|
|
134
132
|
#
|
|
@@ -137,6 +135,10 @@ module Rdkafka
|
|
|
137
135
|
# consumer.events_poll_nb_each do |_count|
|
|
138
136
|
# :stop if monotonic_now >= deadline
|
|
139
137
|
# end
|
|
138
|
+
# @note This method holds the inner lock until the queue is empty or `:stop` is returned.
|
|
139
|
+
# Other consumer operations will wait until this method returns.
|
|
140
|
+
# @note This method is thread-safe as it uses @native_kafka.with_inner synchronization
|
|
141
|
+
# @note Do NOT use this if `consumer_poll_set` was set to `true`
|
|
140
142
|
def events_poll_nb_each
|
|
141
143
|
closed_consumer_check(__method__)
|
|
142
144
|
|
|
@@ -162,14 +164,6 @@ module Rdkafka
|
|
|
162
164
|
# @raise [Rdkafka::ClosedConsumerError] if called on a closed consumer
|
|
163
165
|
# @raise [Rdkafka::RdkafkaError] if a Kafka error occurs while polling
|
|
164
166
|
#
|
|
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
167
|
# @example Process messages until queue is empty
|
|
174
168
|
# consumer.poll_nb_each do |message|
|
|
175
169
|
# process(message)
|
|
@@ -182,6 +176,13 @@ module Rdkafka
|
|
|
182
176
|
# count += 1
|
|
183
177
|
# :stop if count >= 10
|
|
184
178
|
# end
|
|
179
|
+
# @note This method uses `rd_kafka_consumer_poll` to fetch messages, unlike
|
|
180
|
+
# `events_poll_nb_each` which uses `rd_kafka_poll` for event callbacks (delivery reports,
|
|
181
|
+
# statistics, etc.). For consumers, use this method to receive messages and
|
|
182
|
+
# `events_poll_nb_each` for processing background events.
|
|
183
|
+
# @note This method holds the inner lock for the duration. Other consumer operations
|
|
184
|
+
# will wait until this method returns.
|
|
185
|
+
# @note Timeout/max_messages logic should be implemented by the caller
|
|
185
186
|
def poll_nb_each
|
|
186
187
|
closed_consumer_check(__method__)
|
|
187
188
|
|
|
@@ -446,9 +447,11 @@ module Rdkafka
|
|
|
446
447
|
end
|
|
447
448
|
|
|
448
449
|
# Return the current positions (offsets) for topics and partitions.
|
|
449
|
-
# The offset field of each requested partition will be set to the offset of the last consumed
|
|
450
|
+
# The offset field of each requested partition will be set to the offset of the last consumed
|
|
451
|
+
# message + 1, or nil in case there was no previous message.
|
|
450
452
|
#
|
|
451
|
-
# @param list [TopicPartitionList, nil] The topic with partitions to get the offsets for or nil
|
|
453
|
+
# @param list [TopicPartitionList, nil] The topic with partitions to get the offsets for or nil
|
|
454
|
+
# to use the current subscription.
|
|
452
455
|
#
|
|
453
456
|
# @return [TopicPartitionList]
|
|
454
457
|
#
|
|
@@ -515,28 +518,65 @@ module Rdkafka
|
|
|
515
518
|
# possible to create one yourself, in this case you have to provide a list that
|
|
516
519
|
# already contains all the partitions you need the lag for.
|
|
517
520
|
#
|
|
521
|
+
# The end offsets of all requested partitions are fetched in a single batched
|
|
522
|
+
# {#list_offsets} query - librdkafka fans it out to the involved partition leaders
|
|
523
|
+
# internally and concurrently - instead of one blocking {#query_watermark_offsets}
|
|
524
|
+
# broker roundtrip per partition.
|
|
525
|
+
#
|
|
518
526
|
# @param topic_partition_list [TopicPartitionList] The list to calculate lag for.
|
|
519
|
-
# @param watermark_timeout_ms [Integer] The timeout for
|
|
527
|
+
# @param watermark_timeout_ms [Integer] The timeout for the batched end-offsets query.
|
|
520
528
|
# @return [Hash{String => Hash{Integer => Integer}}] A hash containing all topics with the lag
|
|
521
529
|
# per partition
|
|
530
|
+
# @raise [ClosedConsumerError] when the consumer is closed
|
|
522
531
|
# @raise [RdkafkaError] When querying the broker fails.
|
|
523
532
|
def lag(topic_partition_list, watermark_timeout_ms = Defaults::CONSUMER_LAG_TIMEOUT_MS)
|
|
533
|
+
closed_consumer_check(__method__)
|
|
534
|
+
|
|
524
535
|
out = {}
|
|
536
|
+
request = {}
|
|
537
|
+
partitions_by_topic = topic_partition_list.to_h
|
|
538
|
+
|
|
539
|
+
partitions_by_topic.each do |topic, partitions|
|
|
540
|
+
out[topic] = {}
|
|
525
541
|
|
|
526
|
-
topic_partition_list.to_h.each do |topic, partitions|
|
|
527
|
-
# Query high watermarks for this topic's partitions and compare to the offset in the list.
|
|
528
|
-
topic_out = {}
|
|
529
542
|
partitions.each do |p|
|
|
530
543
|
next if p.offset.nil?
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
544
|
+
|
|
545
|
+
(request[topic] ||= []) << { partition: p.partition, offset: :latest }
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
return out if request.empty?
|
|
550
|
+
|
|
551
|
+
report = begin
|
|
552
|
+
# The isolation level is forwarded so the end offsets match what the old per-partition
|
|
553
|
+
# watermark query returned: librdkafka resolves that query with the consumer's configured
|
|
554
|
+
# `isolation.level` (LSO for the default read_committed), while the admin-style batched
|
|
555
|
+
# query would otherwise default to read_uncommitted (true high watermark).
|
|
556
|
+
list_offsets(request, isolation_level: isolation_level)
|
|
557
|
+
.wait(max_wait_timeout_ms: watermark_timeout_ms)
|
|
558
|
+
rescue AbstractHandle::WaitTimeoutError
|
|
559
|
+
# Keep the pre-batching contract: a slow broker surfaced as a timed-out RdkafkaError
|
|
560
|
+
# from the per-partition watermark query, not as a handle wait timeout.
|
|
561
|
+
raise RdkafkaError.new(
|
|
562
|
+
Rdkafka::Bindings::RD_KAFKA_RESP_ERR__TIMED_OUT,
|
|
563
|
+
"Error querying watermark offsets of '#{request.keys.join(", ")}'"
|
|
564
|
+
)
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
end_offsets = {}
|
|
568
|
+
report.offsets.each do |result|
|
|
569
|
+
(end_offsets[result[:topic]] ||= {})[result[:partition]] = result[:offset]
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
partitions_by_topic.each do |topic, partitions|
|
|
573
|
+
partitions.each do |p|
|
|
574
|
+
next if p.offset.nil?
|
|
575
|
+
|
|
576
|
+
out[topic][p.partition] = end_offsets.fetch(topic).fetch(p.partition) - p.offset
|
|
537
577
|
end
|
|
538
|
-
out[topic] = topic_out
|
|
539
578
|
end
|
|
579
|
+
|
|
540
580
|
out
|
|
541
581
|
end
|
|
542
582
|
|
|
@@ -846,7 +886,7 @@ module Rdkafka
|
|
|
846
886
|
# returns without further waiting.
|
|
847
887
|
#
|
|
848
888
|
# Error events (e.g. `:partition_eof`) are returned inline as {RdkafkaError} objects
|
|
849
|
-
# rather than raised, so callers receive the complete batch
|
|
889
|
+
# rather than raised, so callers receive the complete batch - both messages and errors -
|
|
850
890
|
# and can decide how to handle each. This is particularly useful when multiple partitions
|
|
851
891
|
# signal EOF simultaneously: all signals appear in the returned array rather than only
|
|
852
892
|
# the first one being raised and the rest silently discarded.
|
|
@@ -921,15 +961,15 @@ module Rdkafka
|
|
|
921
961
|
# particularly useful in fiber scheduler contexts where GVL release/reacquire
|
|
922
962
|
# overhead is wasteful since we don't expect to wait.
|
|
923
963
|
#
|
|
964
|
+
# @param timeout_ms [Integer] Timeout waiting for the first message
|
|
965
|
+
# (default: 0 for non-blocking)
|
|
966
|
+
# @param max_items [Integer] Maximum number of messages to return per call
|
|
967
|
+
# @return [Array<Message, RdkafkaError>] Batch of messages and/or error events in arrival order
|
|
968
|
+
# @raise [ClosedConsumerError] When called on a closed consumer
|
|
924
969
|
# @note Since the GVL is not released, a non-zero timeout_ms will block all Ruby
|
|
925
970
|
# threads/fibers for the duration. Use {#poll_batch} if you need a blocking wait.
|
|
926
971
|
#
|
|
927
972
|
# 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
973
|
def poll_batch_nb(timeout_ms = 0, max_items: 100)
|
|
934
974
|
closed_consumer_check(__method__)
|
|
935
975
|
|
|
@@ -1081,6 +1121,35 @@ module Rdkafka
|
|
|
1081
1121
|
Rdkafka::Bindings.rd_kafka_mem_free(inner, ptr) unless ptr.null?
|
|
1082
1122
|
end
|
|
1083
1123
|
|
|
1124
|
+
# Reads this consumer's effective `isolation.level` from the live librdkafka configuration
|
|
1125
|
+
# and maps it to the numeric isolation level constant. Memoized: the value cannot change
|
|
1126
|
+
# after client creation.
|
|
1127
|
+
#
|
|
1128
|
+
# @return [Integer] `RD_KAFKA_ISOLATION_LEVEL_READ_COMMITTED` or
|
|
1129
|
+
# `RD_KAFKA_ISOLATION_LEVEL_READ_UNCOMMITTED`
|
|
1130
|
+
# @raise [Rdkafka::Config::ConfigError] when the property cannot be read
|
|
1131
|
+
def isolation_level
|
|
1132
|
+
@isolation_level ||= @native_kafka.with_inner do |inner|
|
|
1133
|
+
conf = Rdkafka::Bindings.rd_kafka_conf(inner)
|
|
1134
|
+
|
|
1135
|
+
size_ptr = Rdkafka::Bindings::SizePtr.new
|
|
1136
|
+
size_ptr[:value] = 64
|
|
1137
|
+
value_ptr = FFI::MemoryPointer.new(:char, 64)
|
|
1138
|
+
|
|
1139
|
+
result = Rdkafka::Bindings.rd_kafka_conf_get(conf, "isolation.level", value_ptr, size_ptr)
|
|
1140
|
+
|
|
1141
|
+
if result != :config_ok
|
|
1142
|
+
raise Rdkafka::Config::ConfigError.new("Could not read isolation.level: #{result}")
|
|
1143
|
+
end
|
|
1144
|
+
|
|
1145
|
+
if value_ptr.read_string == "read_committed"
|
|
1146
|
+
Rdkafka::Bindings::RD_KAFKA_ISOLATION_LEVEL_READ_COMMITTED
|
|
1147
|
+
else
|
|
1148
|
+
Rdkafka::Bindings::RD_KAFKA_ISOLATION_LEVEL_READ_UNCOMMITTED
|
|
1149
|
+
end
|
|
1150
|
+
end
|
|
1151
|
+
end
|
|
1152
|
+
|
|
1084
1153
|
# Returns the consumer queue pointer, lazily initialized
|
|
1085
1154
|
# @return [FFI::Pointer] consumer queue handle
|
|
1086
1155
|
def consumer_queue
|
data/lib/rdkafka/defaults.rb
CHANGED
|
@@ -3,19 +3,17 @@
|
|
|
3
3
|
module Rdkafka
|
|
4
4
|
# Default timeout and timing values used throughout rdkafka-ruby.
|
|
5
5
|
#
|
|
6
|
-
# All timeout values can be overridden per-call via method parameters.
|
|
7
|
-
#
|
|
8
|
-
# the default values used across the library.
|
|
9
|
-
#
|
|
10
|
-
# @note These are rdkafka-ruby defaults, not librdkafka configuration options.
|
|
11
|
-
# For librdkafka options, see:
|
|
12
|
-
# https://github.com/confluentinc/librdkafka/blob/master/CONFIGURATION.md
|
|
6
|
+
# All timeout values can be overridden per-call via method parameters. These constants provide
|
|
7
|
+
# a central place to understand and reference the default values used across the library.
|
|
13
8
|
#
|
|
14
9
|
# @example Overriding a timeout per-call
|
|
15
10
|
# consumer.committed(timeout_ms: 5_000) # Use 5 seconds instead of default 2 seconds
|
|
16
11
|
#
|
|
17
12
|
# @example Checking the default value
|
|
18
13
|
# Rdkafka::Defaults::CONSUMER_COMMITTED_TIMEOUT_MS # => 2000
|
|
14
|
+
# @note These are rdkafka-ruby defaults, not librdkafka configuration options.
|
|
15
|
+
# For librdkafka options, see:
|
|
16
|
+
# https://github.com/confluentinc/librdkafka/blob/master/CONFIGURATION.md
|
|
19
17
|
module Defaults
|
|
20
18
|
# Consumer timeouts (in milliseconds)
|
|
21
19
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
module Helpers
|
|
5
|
+
# Shared `#list_offsets` implementation for Admin and Consumer.
|
|
6
|
+
#
|
|
7
|
+
# The librdkafka Admin API is handle-agnostic: `rd_kafka_ListOffsets()` can be issued on any
|
|
8
|
+
# `rd_kafka_t` instance regardless of its type, so this Ruby-level implementation is identical
|
|
9
|
+
# for admin and consumer clients. Includers must provide a private `#closed_check(method)`
|
|
10
|
+
# that raises their own `Closed*Error`, and must have the background event callback
|
|
11
|
+
# registered on their native config (see `Config#admin` and `Config#consumer`) so the result
|
|
12
|
+
# event is dispatched back to the returned handle.
|
|
13
|
+
module ListOffsets
|
|
14
|
+
# Queries partition offsets by specification (earliest, latest, max_timestamp, or by
|
|
15
|
+
# timestamp) without requiring a consumer group.
|
|
16
|
+
#
|
|
17
|
+
# The query is batched: all requested partitions are carried in one `ListOffsets` request
|
|
18
|
+
# that librdkafka fans out to the involved partition leaders internally and concurrently.
|
|
19
|
+
#
|
|
20
|
+
# @param topic_partition_offsets [Hash{String => Array<Hash>}] hash mapping topic names to
|
|
21
|
+
# arrays of partition offset specifications. Each specification is a hash with:
|
|
22
|
+
# - `:partition` [Integer] partition number
|
|
23
|
+
# - `:offset` [Symbol, Integer] offset specification - `:earliest`, `:latest`,
|
|
24
|
+
# `:max_timestamp`, or an integer timestamp in milliseconds
|
|
25
|
+
# @param isolation_level [Integer, nil] optional isolation level:
|
|
26
|
+
# - `RD_KAFKA_ISOLATION_LEVEL_READ_UNCOMMITTED` (0) - default
|
|
27
|
+
# - `RD_KAFKA_ISOLATION_LEVEL_READ_COMMITTED` (1)
|
|
28
|
+
#
|
|
29
|
+
# @return [Admin::ListOffsetsHandle] handle that can be used to wait for the result
|
|
30
|
+
#
|
|
31
|
+
# @raise [ClosedAdminError, ClosedConsumerError] when the client is closed
|
|
32
|
+
# @raise [ConfigError] when the background queue is unavailable
|
|
33
|
+
#
|
|
34
|
+
# @example Query earliest and latest offsets
|
|
35
|
+
# handle = client.list_offsets(
|
|
36
|
+
# { "my_topic" => [
|
|
37
|
+
# { partition: 0, offset: :earliest },
|
|
38
|
+
# { partition: 1, offset: :latest }
|
|
39
|
+
# ] }
|
|
40
|
+
# )
|
|
41
|
+
# report = handle.wait(max_wait_timeout_ms: 15_000)
|
|
42
|
+
# report.offsets
|
|
43
|
+
# # => [{ topic: "my_topic", partition: 0, offset: 0, ... }, ...]
|
|
44
|
+
def list_offsets(topic_partition_offsets, isolation_level: nil)
|
|
45
|
+
closed_check(__method__)
|
|
46
|
+
|
|
47
|
+
# Parse and validate every offset spec before allocating the native list, so a missing key
|
|
48
|
+
# or an unknown offset specification raises with nothing to clean up. Previously the
|
|
49
|
+
# ArgumentError (or KeyError) was raised after `rd_kafka_topic_partition_list_new`, leaking
|
|
50
|
+
# the native list.
|
|
51
|
+
parsed = topic_partition_offsets.flat_map do |topic, partitions|
|
|
52
|
+
partitions.map do |spec|
|
|
53
|
+
offset = spec.fetch(:offset)
|
|
54
|
+
|
|
55
|
+
native_offset = case offset
|
|
56
|
+
when :earliest then Rdkafka::Bindings::RD_KAFKA_OFFSET_SPEC_EARLIEST
|
|
57
|
+
when :latest then Rdkafka::Bindings::RD_KAFKA_OFFSET_SPEC_LATEST
|
|
58
|
+
when :max_timestamp then Rdkafka::Bindings::RD_KAFKA_OFFSET_SPEC_MAX_TIMESTAMP
|
|
59
|
+
when Integer then offset
|
|
60
|
+
else
|
|
61
|
+
raise ArgumentError, "Unknown offset specification: #{offset.inspect}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
[topic, spec.fetch(:partition), native_offset]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Build native topic partition list
|
|
69
|
+
tpl = Rdkafka::Bindings.rd_kafka_topic_partition_list_new(parsed.size)
|
|
70
|
+
|
|
71
|
+
parsed.each do |topic, partition, native_offset|
|
|
72
|
+
Rdkafka::Bindings.rd_kafka_topic_partition_list_add(tpl, topic, partition)
|
|
73
|
+
Rdkafka::Bindings.rd_kafka_topic_partition_list_set_offset(tpl, topic, partition, native_offset)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get a pointer to the queue that our request will be enqueued on
|
|
77
|
+
queue_ptr = @native_kafka.with_inner do |inner|
|
|
78
|
+
Rdkafka::Bindings.rd_kafka_queue_get_background(inner)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if queue_ptr.null?
|
|
82
|
+
Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl)
|
|
83
|
+
raise Rdkafka::Config::ConfigError.new("rd_kafka_queue_get_background was NULL")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Create and register the handle we will return to the caller
|
|
87
|
+
handle = Admin::ListOffsetsHandle.new
|
|
88
|
+
handle[:pending] = true
|
|
89
|
+
handle[:response] = Rdkafka::Bindings::RD_KAFKA_PARTITION_UA
|
|
90
|
+
|
|
91
|
+
admin_options_ptr = @native_kafka.with_inner do |inner|
|
|
92
|
+
Rdkafka::Bindings.rd_kafka_AdminOptions_new(
|
|
93
|
+
inner,
|
|
94
|
+
Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_LISTOFFSETS
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if isolation_level
|
|
99
|
+
Rdkafka::Bindings.rd_kafka_AdminOptions_set_isolation_level(admin_options_ptr, isolation_level)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
Admin::ListOffsetsHandle.register(handle)
|
|
103
|
+
Rdkafka::Bindings.rd_kafka_AdminOptions_set_opaque(admin_options_ptr, handle.to_ptr)
|
|
104
|
+
|
|
105
|
+
begin
|
|
106
|
+
@native_kafka.with_inner do |inner|
|
|
107
|
+
Rdkafka::Bindings.rd_kafka_ListOffsets(
|
|
108
|
+
inner,
|
|
109
|
+
tpl,
|
|
110
|
+
admin_options_ptr,
|
|
111
|
+
queue_ptr
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
rescue Exception
|
|
115
|
+
Admin::ListOffsetsHandle.remove(handle.to_ptr.address)
|
|
116
|
+
raise
|
|
117
|
+
ensure
|
|
118
|
+
Rdkafka::Bindings.rd_kafka_AdminOptions_destroy(admin_options_ptr)
|
|
119
|
+
Rdkafka::Bindings.rd_kafka_queue_destroy(queue_ptr)
|
|
120
|
+
Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
handle
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -4,10 +4,14 @@ module Rdkafka
|
|
|
4
4
|
module OAuth
|
|
5
5
|
# Set the OAuthBearer token
|
|
6
6
|
#
|
|
7
|
-
# @param token [String] the mandatory token value to set, often (but not necessarily) a JWS
|
|
8
|
-
#
|
|
7
|
+
# @param token [String] the mandatory token value to set, often (but not necessarily) a JWS
|
|
8
|
+
# compact serialization as per https://tools.ietf.org/html/rfc7515#section-3.1.
|
|
9
|
+
# @param lifetime_ms [Integer] when the token expires, in terms of the number of milliseconds
|
|
10
|
+
# since the epoch. See https://currentmillis.com/.
|
|
9
11
|
# @param principal_name [String] the mandatory Kafka principal name associated with the token.
|
|
10
|
-
# @param extensions [Hash] optional SASL extensions key-value pairs to be communicated to the
|
|
12
|
+
# @param extensions [Hash] optional SASL extensions key-value pairs to be communicated to the
|
|
13
|
+
# broker as additional key-value pairs during the initial client response as per
|
|
14
|
+
# https://tools.ietf.org/html/rfc7628#section-3.1.
|
|
11
15
|
# @return [Integer] 0 on success
|
|
12
16
|
def oauthbearer_set_token(token:, lifetime_ms:, principal_name:, extensions: nil)
|
|
13
17
|
error_buffer = FFI::MemoryPointer.from_string(" " * 256)
|
|
@@ -49,7 +53,8 @@ module Rdkafka
|
|
|
49
53
|
# Convert extensions hash to FFI::MemoryPointer (`const char **`).
|
|
50
54
|
#
|
|
51
55
|
# @param extensions [Hash, nil] extension key-value pairs
|
|
52
|
-
# @return [Array<FFI::MemoryPointer, Array<FFI::MemoryPointer>>] array pointer and string
|
|
56
|
+
# @return [Array<FFI::MemoryPointer, Array<FFI::MemoryPointer>>] array pointer and string
|
|
57
|
+
# pointers
|
|
53
58
|
# @note The returned pointers must be freed manually (autorelease = false).
|
|
54
59
|
def map_extensions(extensions)
|
|
55
60
|
return [nil, nil] if extensions.nil? || extensions.empty?
|
|
@@ -80,7 +85,7 @@ module Rdkafka
|
|
|
80
85
|
#
|
|
81
86
|
# @param extensions [Hash, nil] extension key-value pairs
|
|
82
87
|
# @return [Integer] non-negative even number representing keys + values count
|
|
83
|
-
# @see
|
|
88
|
+
# @see github.com/confluentinc/librdkafka/blob/master/src/rdkafka_sasl_oauthbearer.c#L327-L347
|
|
84
89
|
def extension_size(extensions)
|
|
85
90
|
return 0 unless extensions
|
|
86
91
|
extensions.size * 2
|
data/lib/rdkafka/native_kafka.rb
CHANGED
|
@@ -13,6 +13,14 @@ module Rdkafka
|
|
|
13
13
|
def initialize(inner, run_polling_thread:, opaque:, auto_start: true, timeout_ms: Defaults::NATIVE_KAFKA_POLL_TIMEOUT_MS)
|
|
14
14
|
@inner = inner
|
|
15
15
|
@opaque = opaque
|
|
16
|
+
# Process that owns `@inner`. librdkafka is not fork-safe: `fork` copies only the calling
|
|
17
|
+
# thread, so the background/broker threads backing this handle do not exist in a child
|
|
18
|
+
# process. An inherited handle therefore must not be polled or destroyed in the child -
|
|
19
|
+
# `rd_kafka_destroy` would walk thread state that no longer exists (segfault) and the
|
|
20
|
+
# inherited mutexes may have been copied in a locked state (deadlock). We record the creator
|
|
21
|
+
# pid so a forked child can recognise an inherited handle and leave its teardown to the
|
|
22
|
+
# parent, which still owns the running threads.
|
|
23
|
+
@creator_pid = Process.pid
|
|
16
24
|
# Lock around external access
|
|
17
25
|
@access_mutex = Mutex.new
|
|
18
26
|
# Lock around internal polling
|
|
@@ -115,17 +123,21 @@ module Rdkafka
|
|
|
115
123
|
end
|
|
116
124
|
|
|
117
125
|
# Returns whether this native Kafka handle is closed or closing
|
|
126
|
+
#
|
|
127
|
+
# A handle inherited across `fork` is reported as closed in the child: it belongs to another
|
|
128
|
+
# process whose threads back the native client, so it is not usable here and must not be
|
|
129
|
+
# destroyed here (see the `@creator_pid` note in `#initialize`). This makes every `#close`
|
|
130
|
+
# path - including the GC finalizers that run during a child's exit - skip the native teardown
|
|
131
|
+
# for inherited handles, which is what would otherwise segfault the child.
|
|
132
|
+
#
|
|
118
133
|
# @return [Boolean] true if closed or closing
|
|
119
134
|
def closed?
|
|
120
|
-
@closing || @inner.nil?
|
|
135
|
+
@closing || @inner.nil? || @creator_pid != Process.pid
|
|
121
136
|
end
|
|
122
137
|
|
|
123
138
|
# Enable IO event notifications on the main queue
|
|
124
139
|
# Librdkafka will write to your FD when the queue transitions from empty to non-empty
|
|
125
140
|
#
|
|
126
|
-
# @note This method is incompatible with background polling threads.
|
|
127
|
-
# If background polling is enabled, use manual polling instead (e.g., consumer.poll)
|
|
128
|
-
#
|
|
129
141
|
# @param fd [Integer] your file descriptor (from IO.pipe or eventfd)
|
|
130
142
|
# @param payload [String] data to write to fd when queue has data (default: "\x01")
|
|
131
143
|
# @return [nil]
|
|
@@ -142,6 +154,8 @@ module Rdkafka
|
|
|
142
154
|
# if readable
|
|
143
155
|
# consumer.poll(0) # Get messages
|
|
144
156
|
# end
|
|
157
|
+
# @note This method is incompatible with background polling threads.
|
|
158
|
+
# If background polling is enabled, use manual polling instead (e.g., consumer.poll)
|
|
145
159
|
def enable_main_queue_io_events(fd, payload = "\x01")
|
|
146
160
|
if @run_polling_thread
|
|
147
161
|
raise "Cannot enable IO events while background polling thread is active. " \
|
|
@@ -159,14 +173,13 @@ module Rdkafka
|
|
|
159
173
|
# Enable IO event notifications on the background queue
|
|
160
174
|
# Librdkafka will write to your FD when the background queue transitions from empty to non-empty
|
|
161
175
|
#
|
|
162
|
-
# @note This method is incompatible with background polling threads.
|
|
163
|
-
# If background polling is enabled, use manual polling instead (e.g., consumer.poll)
|
|
164
|
-
#
|
|
165
176
|
# @param fd [Integer] your file descriptor (from IO.pipe or eventfd)
|
|
166
177
|
# @param payload [String] data to write to fd when queue has data (default: "\x01")
|
|
167
178
|
# @return [nil]
|
|
168
179
|
# @raise [ClosedInnerError] when the handle is closed
|
|
169
180
|
# @raise [RuntimeError] when background polling thread is active
|
|
181
|
+
# @note This method is incompatible with background polling threads.
|
|
182
|
+
# If background polling is enabled, use manual polling instead (e.g., consumer.poll)
|
|
170
183
|
def enable_background_queue_io_events(fd, payload = "\x01")
|
|
171
184
|
if @run_polling_thread
|
|
172
185
|
raise "Cannot enable IO events while background polling thread is active. " \
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Rdkafka
|
|
4
4
|
class Producer
|
|
5
|
-
# Handle to wait for a delivery report which is returned when
|
|
6
|
-
# producing a message.
|
|
5
|
+
# Handle to wait for a delivery report which is returned when producing a message.
|
|
7
6
|
class DeliveryHandle < Rdkafka::AbstractHandle
|
|
8
7
|
layout :pending, :bool,
|
|
9
8
|
:response, :int,
|