karafka-rdkafka 0.28.0-aarch64-linux-gnu → 0.28.2-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 +46 -30
- data/README.md +3 -3
- 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.rb +317 -90
- data/lib/rdkafka/bindings.rb +52 -13
- 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/clients.rb +114 -0
- data/lib/rdkafka/config.rb +103 -56
- data/lib/rdkafka/consumer/allocator.rb +38 -0
- data/lib/rdkafka/consumer/topic_partition_list.rb +16 -12
- data/lib/rdkafka/consumer.rb +81 -76
- data/lib/rdkafka/defaults.rb +11 -2
- data/lib/rdkafka/error.rb +2 -1
- 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/testing.rb +2 -1
- data/lib/rdkafka/producer.rb +28 -20
- data/lib/rdkafka/version.rb +1 -1
- data/lib/rdkafka.rb +10 -0
- data/package-lock.json +6 -6
- data/renovate.json +1 -1
- metadata +10 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
module Callbacks
|
|
5
|
+
# Handles `RD_KAFKA_EVENT_DELETERECORDS_RESULT` events
|
|
6
|
+
# @private
|
|
7
|
+
class DeleteRecordsHandler < BaseHandler
|
|
8
|
+
class << self
|
|
9
|
+
# Resolves the delete-records handle from its result event
|
|
10
|
+
# @param event_ptr [FFI::Pointer] pointer to the event
|
|
11
|
+
# @return [void]
|
|
12
|
+
def call(event_ptr)
|
|
13
|
+
result_ptr = Rdkafka::Bindings.rd_kafka_event_DeleteRecords_result(event_ptr)
|
|
14
|
+
handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
|
|
15
|
+
|
|
16
|
+
return unless (handle = Rdkafka::Admin::DeleteRecordsHandle.remove(handle_ptr.address))
|
|
17
|
+
|
|
18
|
+
# An operation-level error (e.g. timeout or a closed client) is delivered on the event
|
|
19
|
+
# itself, with no per-partition result to parse.
|
|
20
|
+
return if resolve_operation_error(event_ptr, handle)
|
|
21
|
+
|
|
22
|
+
handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
23
|
+
|
|
24
|
+
# Parsing must copy everything out of event-owned memory before the event is destroyed.
|
|
25
|
+
# An exception here is captured and re-raised on the waiting thread, since it cannot
|
|
26
|
+
# unwind through librdkafka native frames.
|
|
27
|
+
handle.result = begin
|
|
28
|
+
Rdkafka::Admin::DeleteRecordsReport.new(result_ptr)
|
|
29
|
+
rescue => e
|
|
30
|
+
e
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
handle.unlock
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
module Callbacks
|
|
5
|
+
# Handles `RD_KAFKA_EVENT_LISTCONSUMERGROUPS_RESULT` events
|
|
6
|
+
# @private
|
|
7
|
+
class ListConsumerGroupsHandler < BaseHandler
|
|
8
|
+
class << self
|
|
9
|
+
# Resolves the list-consumer-groups handle from its result event
|
|
10
|
+
# @param event_ptr [FFI::Pointer] pointer to the event
|
|
11
|
+
# @return [void]
|
|
12
|
+
def call(event_ptr)
|
|
13
|
+
result_ptr = Rdkafka::Bindings.rd_kafka_event_ListConsumerGroups_result(event_ptr)
|
|
14
|
+
handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
|
|
15
|
+
|
|
16
|
+
return unless (handle = Rdkafka::Admin::ListConsumerGroupsHandle.remove(handle_ptr.address))
|
|
17
|
+
|
|
18
|
+
# An operation-level error (e.g. timeout or a closed client) is delivered on the event
|
|
19
|
+
# itself; per the source feature's all-or-nothing contract we surface it as the result
|
|
20
|
+
# rather than returning a partial listing.
|
|
21
|
+
return if resolve_operation_error(event_ptr, handle)
|
|
22
|
+
|
|
23
|
+
handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
24
|
+
|
|
25
|
+
# Parsing must copy everything out of event-owned memory before the event is destroyed.
|
|
26
|
+
# An exception here is captured and re-raised on the waiting thread, since it cannot
|
|
27
|
+
# unwind through librdkafka native frames.
|
|
28
|
+
handle.result = begin
|
|
29
|
+
Rdkafka::Admin::ListConsumerGroupsReport.new(result_ptr)
|
|
30
|
+
rescue => e
|
|
31
|
+
e
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
handle.unlock
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/rdkafka/callbacks.rb
CHANGED
|
@@ -219,12 +219,14 @@ module Rdkafka
|
|
|
219
219
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETETOPICS_RESULT then DeleteTopicHandler
|
|
220
220
|
when Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_CREATEPARTITIONS_RESULT then CreatePartitionsHandler
|
|
221
221
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEGROUPS_RESULT then DeleteGroupsHandler
|
|
222
|
+
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETERECORDS_RESULT then DeleteRecordsHandler
|
|
222
223
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_CREATEACLS_RESULT then CreateAclHandler
|
|
223
224
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEACLS_RESULT then DeleteAclHandler
|
|
224
225
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBEACLS_RESULT then DescribeAclHandler
|
|
225
226
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBECONFIGS_RESULT then DescribeConfigsHandler
|
|
226
227
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_INCREMENTALALTERCONFIGS_RESULT then IncrementalAlterConfigsHandler
|
|
227
228
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_LISTOFFSETS_RESULT then ListOffsetsHandler
|
|
229
|
+
when Rdkafka::Bindings::RD_KAFKA_EVENT_LISTCONSUMERGROUPS_RESULT then ListConsumerGroupsHandler
|
|
228
230
|
end
|
|
229
231
|
|
|
230
232
|
handler&.call(event_ptr)
|
|
@@ -286,9 +288,9 @@ module Rdkafka
|
|
|
286
288
|
end
|
|
287
289
|
end
|
|
288
290
|
|
|
289
|
-
#
|
|
291
|
+
# @!visibility private
|
|
290
292
|
@@mutex = Mutex.new
|
|
291
|
-
#
|
|
293
|
+
# @!visibility private
|
|
292
294
|
@@current_pid = nil
|
|
293
295
|
|
|
294
296
|
class << self
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
# Tracks live public clients so they can be closed before Ruby begins forced finalization.
|
|
5
|
+
# @private
|
|
6
|
+
module Clients
|
|
7
|
+
# Ruby rewrote `ObjectSpace::WeakMap` in 3.3. On 3.2 and earlier its `[]` can hand back a
|
|
8
|
+
# foreign or already-freed object for a key, which raises `NoMethodError` or segfaults the VM
|
|
9
|
+
# the moment the value is touched. Track weakly only where that is safe and fall back to a
|
|
10
|
+
# strong registry - pruned as clients close - on older Rubies. The strong registry holds an
|
|
11
|
+
# open, un-closed client until shutdown, which is exactly when this cleanup needs it anyway.
|
|
12
|
+
WEAK_TRACKING = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.3")
|
|
13
|
+
|
|
14
|
+
@pid = Process.pid
|
|
15
|
+
@mutex = Mutex.new
|
|
16
|
+
|
|
17
|
+
if WEAK_TRACKING
|
|
18
|
+
@clients = ObjectSpace::WeakMap.new
|
|
19
|
+
@tokens = [].freeze
|
|
20
|
+
else
|
|
21
|
+
@clients = {}.freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Registers a successfully constructed client so it can be closed during process shutdown.
|
|
25
|
+
# @param client [Consumer, Producer, Admin] client to close during process shutdown
|
|
26
|
+
# @return [Consumer, Producer, Admin] the registered client
|
|
27
|
+
def self.register(client)
|
|
28
|
+
reset_after_fork
|
|
29
|
+
|
|
30
|
+
@mutex.synchronize do
|
|
31
|
+
if WEAK_TRACKING
|
|
32
|
+
live_clients
|
|
33
|
+
|
|
34
|
+
# The strongly-held token is the key and the client is the weak value, so registering a
|
|
35
|
+
# client never keeps it alive. Enumerating weak keys can expose reclaimed slots, so we
|
|
36
|
+
# look the value up by token instead.
|
|
37
|
+
token = Object.new
|
|
38
|
+
@clients[token] = client
|
|
39
|
+
@tokens = (@tokens + [token]).freeze
|
|
40
|
+
else
|
|
41
|
+
# Strong references, so drop clients that have already been closed to bound growth.
|
|
42
|
+
live = @clients.reject { |_id, existing| existing.closed? }
|
|
43
|
+
live[client.object_id] = client
|
|
44
|
+
@clients = live.freeze
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
client
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Closes each live client registered in this process.
|
|
52
|
+
# @return [nil]
|
|
53
|
+
# @raise [StandardError] the first close error, after attempting every client
|
|
54
|
+
def self.close_all
|
|
55
|
+
return unless @pid == Process.pid
|
|
56
|
+
|
|
57
|
+
clients = @mutex.synchronize { current_clients }
|
|
58
|
+
first_error = nil
|
|
59
|
+
|
|
60
|
+
clients.each do |client|
|
|
61
|
+
client.close unless client.closed?
|
|
62
|
+
rescue => error
|
|
63
|
+
first_error ||= error
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
raise first_error if first_error
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns the currently registered live clients.
|
|
70
|
+
# @return [Array<Consumer, Producer, Admin>] live clients
|
|
71
|
+
# @private
|
|
72
|
+
def self.current_clients
|
|
73
|
+
WEAK_TRACKING ? live_clients : @clients.values
|
|
74
|
+
end
|
|
75
|
+
private_class_method :current_clients
|
|
76
|
+
|
|
77
|
+
# Returns the live clients and releases tokens whose clients have been collected.
|
|
78
|
+
# Weak-tracking Rubies only.
|
|
79
|
+
# @return [Array<Consumer, Producer, Admin>] live clients
|
|
80
|
+
# @private
|
|
81
|
+
def self.live_clients
|
|
82
|
+
clients = []
|
|
83
|
+
tokens = []
|
|
84
|
+
|
|
85
|
+
@tokens.each do |token|
|
|
86
|
+
client = @clients[token]
|
|
87
|
+
clients << client if client
|
|
88
|
+
tokens << token if client
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
@tokens = tokens.freeze
|
|
92
|
+
clients
|
|
93
|
+
end
|
|
94
|
+
private_class_method :live_clients
|
|
95
|
+
|
|
96
|
+
# Replaces inherited state before registering a client in a forked child.
|
|
97
|
+
# @return [nil]
|
|
98
|
+
# @private
|
|
99
|
+
def self.reset_after_fork
|
|
100
|
+
return if @pid == Process.pid
|
|
101
|
+
|
|
102
|
+
if WEAK_TRACKING
|
|
103
|
+
@clients = ObjectSpace::WeakMap.new
|
|
104
|
+
@tokens = [].freeze
|
|
105
|
+
else
|
|
106
|
+
@clients = {}.freeze
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@pid = Process.pid
|
|
110
|
+
@mutex = Mutex.new
|
|
111
|
+
end
|
|
112
|
+
private_class_method :reset_after_fork
|
|
113
|
+
end
|
|
114
|
+
end
|
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
|
|
@@ -207,21 +209,16 @@ module Rdkafka
|
|
|
207
209
|
Rdkafka::Bindings.rd_kafka_conf_set_rebalance_cb(config, Rdkafka::Bindings::RebalanceCallback)
|
|
208
210
|
end
|
|
209
211
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
Rdkafka::
|
|
219
|
-
|
|
220
|
-
run_polling_thread: false,
|
|
221
|
-
opaque: opaque,
|
|
222
|
-
auto_start: native_kafka_auto_start
|
|
223
|
-
)
|
|
224
|
-
)
|
|
212
|
+
build_native_client(
|
|
213
|
+
config,
|
|
214
|
+
:rd_kafka_consumer,
|
|
215
|
+
opaque: opaque,
|
|
216
|
+
run_polling_thread: false,
|
|
217
|
+
auto_start: native_kafka_auto_start
|
|
218
|
+
) do |kafka, native_kafka|
|
|
219
|
+
Rdkafka::Bindings.rd_kafka_poll_set_consumer(kafka) if @consumer_poll_set
|
|
220
|
+
Rdkafka::Consumer.new(native_kafka)
|
|
221
|
+
end
|
|
225
222
|
end
|
|
226
223
|
|
|
227
224
|
# Create a producer with this configuration.
|
|
@@ -245,19 +242,17 @@ module Rdkafka
|
|
|
245
242
|
# Return producer with Kafka client
|
|
246
243
|
partitioner_name = self[:partitioner] || self["partitioner"]
|
|
247
244
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
).tap do |producer|
|
|
260
|
-
opaque.producer = producer
|
|
245
|
+
build_native_client(
|
|
246
|
+
config,
|
|
247
|
+
:rd_kafka_producer,
|
|
248
|
+
opaque: opaque,
|
|
249
|
+
run_polling_thread: run_polling_thread,
|
|
250
|
+
auto_start: native_kafka_auto_start,
|
|
251
|
+
timeout_ms: native_kafka_poll_timeout_ms
|
|
252
|
+
) do |_kafka, native_kafka|
|
|
253
|
+
Rdkafka::Producer.new(native_kafka, partitioner_name).tap do |producer|
|
|
254
|
+
opaque.producer = producer
|
|
255
|
+
end
|
|
261
256
|
end
|
|
262
257
|
end
|
|
263
258
|
|
|
@@ -277,17 +272,16 @@ module Rdkafka
|
|
|
277
272
|
config = native_config(opaque)
|
|
278
273
|
Rdkafka::Bindings.rd_kafka_conf_set_background_event_cb(config, Rdkafka::Callbacks::BackgroundEventCallbackFunction)
|
|
279
274
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
)
|
|
275
|
+
build_native_client(
|
|
276
|
+
config,
|
|
277
|
+
:rd_kafka_producer,
|
|
278
|
+
opaque: opaque,
|
|
279
|
+
run_polling_thread: run_polling_thread,
|
|
280
|
+
auto_start: native_kafka_auto_start,
|
|
281
|
+
timeout_ms: native_kafka_poll_timeout_ms
|
|
282
|
+
) do |_kafka, native_kafka|
|
|
283
|
+
Rdkafka::Admin.new(native_kafka)
|
|
284
|
+
end
|
|
291
285
|
end
|
|
292
286
|
|
|
293
287
|
# Returns all configuration properties and their current values for this config.
|
|
@@ -295,6 +289,9 @@ module Rdkafka
|
|
|
295
289
|
# Uses `rd_kafka_conf_dump` to retrieve every property (including defaults and
|
|
296
290
|
# internal properties like `client.software.name`) as a flat Hash.
|
|
297
291
|
#
|
|
292
|
+
# @return [Hash{Symbol => String}] property names mapped to their current values
|
|
293
|
+
#
|
|
294
|
+
# @raise [ConfigError] When the configuration contains invalid options
|
|
298
295
|
# @note The librdkafka C API does not distinguish between producer-only, consumer-only,
|
|
299
296
|
# and global properties at the configuration level. All properties are returned
|
|
300
297
|
# regardless of the intended client type.
|
|
@@ -302,10 +299,6 @@ module Rdkafka
|
|
|
302
299
|
# @note The returned Hash may include sensitive values such as authentication
|
|
303
300
|
# credentials and key passwords. Do not log or serialize the returned data
|
|
304
301
|
# unless you have explicitly redacted secret entries.
|
|
305
|
-
#
|
|
306
|
-
# @return [Hash{Symbol => String}] property names mapped to their current values
|
|
307
|
-
#
|
|
308
|
-
# @raise [ConfigError] When the configuration contains invalid options
|
|
309
302
|
def describe_properties
|
|
310
303
|
config = nil
|
|
311
304
|
dump_ptr = nil
|
|
@@ -330,7 +323,8 @@ module Rdkafka
|
|
|
330
323
|
Rdkafka::Bindings.rd_kafka_conf_destroy(config) if config
|
|
331
324
|
end
|
|
332
325
|
|
|
333
|
-
# Error that is returned by the underlying rdkafka error if an invalid configuration option is
|
|
326
|
+
# Error that is returned by the underlying rdkafka error if an invalid configuration option is
|
|
327
|
+
# present.
|
|
334
328
|
class ConfigError < RuntimeError; end
|
|
335
329
|
|
|
336
330
|
# Error that is returned by the underlying rdkafka library if the client cannot be created.
|
|
@@ -341,6 +335,56 @@ module Rdkafka
|
|
|
341
335
|
|
|
342
336
|
private
|
|
343
337
|
|
|
338
|
+
# Builds a public client around a native Kafka handle, destroying the current owner if setup
|
|
339
|
+
# fails before ownership is transferred to the caller.
|
|
340
|
+
# @param config [FFI::Pointer] native rdkafka configuration
|
|
341
|
+
# @param type [Symbol] native client type
|
|
342
|
+
# @param opaque [Object] callback context retained for the native client
|
|
343
|
+
# @param run_polling_thread [Boolean] whether to run a Ruby polling thread
|
|
344
|
+
# @param auto_start [Boolean] whether to start the polling thread after wrapping the client
|
|
345
|
+
# @param timeout_ms [Integer] polling timeout in milliseconds
|
|
346
|
+
# @yieldparam kafka [FFI::Pointer] native Kafka handle
|
|
347
|
+
# @yieldparam native_kafka [NativeKafka] Ruby owner of the native handle
|
|
348
|
+
# @return [Consumer, Producer, Admin] the constructed public client
|
|
349
|
+
# @private
|
|
350
|
+
def build_native_client(
|
|
351
|
+
config,
|
|
352
|
+
type,
|
|
353
|
+
opaque:,
|
|
354
|
+
run_polling_thread:,
|
|
355
|
+
auto_start:,
|
|
356
|
+
timeout_ms: Defaults::NATIVE_KAFKA_POLL_TIMEOUT_MS
|
|
357
|
+
)
|
|
358
|
+
kafka = native_kafka(config, type)
|
|
359
|
+
native_kafka = nil
|
|
360
|
+
client = nil
|
|
361
|
+
|
|
362
|
+
begin
|
|
363
|
+
native_kafka = Rdkafka::NativeKafka.new(
|
|
364
|
+
kafka,
|
|
365
|
+
run_polling_thread: run_polling_thread,
|
|
366
|
+
opaque: opaque,
|
|
367
|
+
auto_start: false,
|
|
368
|
+
timeout_ms: timeout_ms
|
|
369
|
+
)
|
|
370
|
+
client = yield(kafka, native_kafka)
|
|
371
|
+
native_kafka.start if auto_start
|
|
372
|
+
Rdkafka::Clients.register(client)
|
|
373
|
+
rescue Exception
|
|
374
|
+
owner = client || native_kafka
|
|
375
|
+
|
|
376
|
+
begin
|
|
377
|
+
owner ? owner.close : Rdkafka::Bindings.rd_kafka_destroy(kafka)
|
|
378
|
+
rescue Exception => err
|
|
379
|
+
# Never let a teardown failure replace the error that caused it: that error is what the
|
|
380
|
+
# caller needs to see, and it is re-raised below regardless.
|
|
381
|
+
Rdkafka::Config.logger.error("Unhandled exception: #{err.class} - #{err.message}")
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
raise
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
344
388
|
# This method is only intended to be used to create a client,
|
|
345
389
|
# using it in another way will leak memory.
|
|
346
390
|
#
|
|
@@ -427,6 +471,9 @@ module Rdkafka
|
|
|
427
471
|
|
|
428
472
|
# Return handle which should be closed using rd_kafka_destroy after usage.
|
|
429
473
|
handle
|
|
474
|
+
rescue Exception
|
|
475
|
+
Rdkafka::Bindings.rd_kafka_destroy(handle) if handle && !handle.null?
|
|
476
|
+
raise
|
|
430
477
|
end
|
|
431
478
|
end
|
|
432
479
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
class Consumer
|
|
5
|
+
# Creates FFI pointers from the process-global allocator so that memory handed to librdkafka
|
|
6
|
+
# can be released by it with the matching `free`.
|
|
7
|
+
module Allocator
|
|
8
|
+
extend FFI::Library
|
|
9
|
+
|
|
10
|
+
ffi_lib FFI::CURRENT_PROCESS # use process-global malloc not FFI::Library::LIBC
|
|
11
|
+
attach_function :malloc, [:size_t], :pointer
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
# Return an FFI pointer to a string allocated from the global allocator
|
|
15
|
+
# so that it can be released by another library.
|
|
16
|
+
#
|
|
17
|
+
# @private
|
|
18
|
+
#
|
|
19
|
+
# @param string [String] Ruby string that will be copied into the new
|
|
20
|
+
# string pointer.
|
|
21
|
+
#
|
|
22
|
+
# @return [FFI::Pointer]
|
|
23
|
+
def string_pointer(string)
|
|
24
|
+
bytes = string.to_s
|
|
25
|
+
pointer = malloc(bytes.bytesize + 1)
|
|
26
|
+
Kernel.raise(NoMemoryError, "malloc failed") if pointer.null?
|
|
27
|
+
|
|
28
|
+
pointer.put_bytes(0, bytes)
|
|
29
|
+
pointer.put_char(bytes.bytesize, 0) # NUL terminator
|
|
30
|
+
|
|
31
|
+
pointer.autorelease = false
|
|
32
|
+
|
|
33
|
+
pointer
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -6,7 +6,8 @@ module Rdkafka
|
|
|
6
6
|
class TopicPartitionList
|
|
7
7
|
# Create a topic partition list.
|
|
8
8
|
#
|
|
9
|
-
# @param data [Hash{String => nil,Partition}] The topic and partition data or nil to create an
|
|
9
|
+
# @param data [Hash{String => nil,Partition}] The topic and partition data or nil to create an
|
|
10
|
+
# empty list
|
|
10
11
|
#
|
|
11
12
|
# @return [TopicPartitionList]
|
|
12
13
|
def initialize(data = nil)
|
|
@@ -34,10 +35,12 @@ module Rdkafka
|
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
# Add a topic with optionally partitions to the list.
|
|
37
|
-
# Calling this method multiple times for the same topic will overwrite the previous
|
|
38
|
+
# Calling this method multiple times for the same topic will overwrite the previous
|
|
39
|
+
# configuraton.
|
|
38
40
|
#
|
|
39
41
|
# @param topic [String] The topic's name
|
|
40
|
-
# @param partitions [Array<Integer>, Range<Integer>, Integer] The topic's partitions or
|
|
42
|
+
# @param partitions [Array<Integer>, Range<Integer>, Integer] The topic's partitions or
|
|
43
|
+
# partition count
|
|
41
44
|
#
|
|
42
45
|
# @return [nil]
|
|
43
46
|
#
|
|
@@ -61,11 +64,12 @@ module Rdkafka
|
|
|
61
64
|
end
|
|
62
65
|
|
|
63
66
|
# Add a topic with partitions and offsets set to the list
|
|
64
|
-
# Calling this method multiple times for the same topic will overwrite the previous
|
|
67
|
+
# Calling this method multiple times for the same topic will overwrite the previous
|
|
68
|
+
# configuraton.
|
|
65
69
|
#
|
|
66
70
|
# @param topic [String] The topic's name
|
|
67
|
-
# @param partitions_with_offsets [Hash{Integer => Integer}, Array<Consumer::Partition>] The
|
|
68
|
-
# partitions and offsets (Hash) or partitions with offsets and metadata (Array)
|
|
71
|
+
# @param partitions_with_offsets [Hash{Integer => Integer}, Array<Consumer::Partition>] The
|
|
72
|
+
# topic's partitions and offsets (Hash) or partitions with offsets and metadata (Array)
|
|
69
73
|
# @return [nil]
|
|
70
74
|
def add_topic_and_partitions_with_offsets(topic, partitions_with_offsets)
|
|
71
75
|
@data[topic.to_s] = partitions_with_offsets.map do |p, o|
|
|
@@ -73,7 +77,8 @@ module Rdkafka
|
|
|
73
77
|
end
|
|
74
78
|
end
|
|
75
79
|
|
|
76
|
-
# Return a `Hash` with the topics as keys and and an array of partition information as the
|
|
80
|
+
# Return a `Hash` with the topics as keys and and an array of partition information as the
|
|
81
|
+
# value if present.
|
|
77
82
|
#
|
|
78
83
|
# @return [Hash{String => Array<Partition>,nil}]
|
|
79
84
|
def to_h
|
|
@@ -97,7 +102,8 @@ module Rdkafka
|
|
|
97
102
|
#
|
|
98
103
|
# @private
|
|
99
104
|
#
|
|
100
|
-
# @param pointer [FFI::Pointer] Optional pointer to an existing native list. Its contents will
|
|
105
|
+
# @param pointer [FFI::Pointer] Optional pointer to an existing native list. Its contents will
|
|
106
|
+
# be copied.
|
|
101
107
|
#
|
|
102
108
|
# @return [TopicPartitionList]
|
|
103
109
|
def self.from_native_tpl(pointer)
|
|
@@ -118,7 +124,6 @@ module Rdkafka
|
|
|
118
124
|
else
|
|
119
125
|
elem[:offset]
|
|
120
126
|
end
|
|
121
|
-
|
|
122
127
|
partition = Partition.new(
|
|
123
128
|
elem[:partition],
|
|
124
129
|
offset,
|
|
@@ -157,10 +162,9 @@ module Rdkafka
|
|
|
157
162
|
# Remove the respond to check after karafka 2.3.0 is released
|
|
158
163
|
if p.respond_to?(:metadata) && p.metadata
|
|
159
164
|
part = Rdkafka::Bindings::TopicPartition.new(ref)
|
|
160
|
-
|
|
161
|
-
# released here:
|
|
165
|
+
# The metadata string is owned by librdkafka once handed over and released here:
|
|
162
166
|
# https://github.com/confluentinc/librdkafka/blob/e03d3bb91ed92a38f38d9806b8d8deffe78a1de5/src/rdkafka_partition.c#L2682C18-L2682C18
|
|
163
|
-
str_ptr
|
|
167
|
+
str_ptr = Allocator.string_pointer(p.metadata)
|
|
164
168
|
part[:metadata] = str_ptr
|
|
165
169
|
part[:metadata_size] = p.metadata.bytesize
|
|
166
170
|
end
|