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.
@@ -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
@@ -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
- # @private
291
+ # @!visibility private
290
292
  @@mutex = Mutex.new
291
- # @private
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
@@ -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 https://github.com/confluentinc/librdkafka/blob/master/CONFIGURATION.md.
6
+ # configuration options is available on
7
+ # https://github.com/confluentinc/librdkafka/blob/master/CONFIGURATION.md.
7
8
  class Config
8
- # @private
9
+ # @!visibility private
9
10
  @@logger = Logger.new($stdout)
10
- # @private
11
+ # @!visibility private
11
12
  @@statistics_callback = nil
12
- # @private
13
+ # @!visibility private
13
14
  @@error_callback = nil
14
- # @private
15
+ # @!visibility private
15
16
  @@opaques = ObjectSpace::WeakMap.new
16
- # @private
17
+ # @!visibility private
17
18
  @@log_queue = Queue.new
18
- # We memoize thread on the first log flush
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
- # @private
21
+ # @!visibility private
22
22
  @@log_mutex = Mutex.new
23
- # @private
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: https://github.com/confluentinc/librdkafka/blob/master/STATISTICS.md
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 sent to the logger, as defined by librdkafka.
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
- # Create native client
211
- kafka = native_kafka(config, :rd_kafka_consumer)
212
-
213
- # Redirect the main queue to the consumer queue
214
- Rdkafka::Bindings.rd_kafka_poll_set_consumer(kafka) if @consumer_poll_set
215
-
216
- # Return consumer with Kafka client
217
- Rdkafka::Consumer.new(
218
- Rdkafka::NativeKafka.new(
219
- kafka,
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
- kafka = native_kafka(config, :rd_kafka_producer)
249
-
250
- Rdkafka::Producer.new(
251
- Rdkafka::NativeKafka.new(
252
- kafka,
253
- run_polling_thread: run_polling_thread,
254
- opaque: opaque,
255
- auto_start: native_kafka_auto_start,
256
- timeout_ms: native_kafka_poll_timeout_ms
257
- ),
258
- partitioner_name
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
- kafka = native_kafka(config, :rd_kafka_producer)
281
-
282
- Rdkafka::Admin.new(
283
- Rdkafka::NativeKafka.new(
284
- kafka,
285
- run_polling_thread: run_polling_thread,
286
- opaque: opaque,
287
- auto_start: native_kafka_auto_start,
288
- timeout_ms: native_kafka_poll_timeout_ms
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 present.
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 empty list
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 configuraton.
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 partition count
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 configuraton.
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 topic's
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 value if present.
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 be copied.
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
- str_ptr = FFI::MemoryPointer.from_string(p.metadata)
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.autorelease = false
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