karafka-rdkafka 0.27.2-aarch64-linux-gnu → 0.28.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +1 -0
  4. data/docker-compose-ssl.yml +1 -1
  5. data/docker-compose.yml +1 -1
  6. data/ext/librdkafka.so +0 -0
  7. data/karafka-rdkafka.gemspec +1 -0
  8. data/lib/rdkafka/abstract_handle.rb +37 -4
  9. data/lib/rdkafka/admin/config_binding_result.rb +1 -1
  10. data/lib/rdkafka/admin/create_acl_handle.rb +2 -8
  11. data/lib/rdkafka/admin/create_partitions_handle.rb +1 -20
  12. data/lib/rdkafka/admin/create_topic_handle.rb +1 -20
  13. data/lib/rdkafka/admin/delete_acl_handle.rb +2 -10
  14. data/lib/rdkafka/admin/delete_groups_handle.rb +3 -11
  15. data/lib/rdkafka/admin/delete_topic_handle.rb +1 -20
  16. data/lib/rdkafka/admin/describe_acl_handle.rb +2 -10
  17. data/lib/rdkafka/admin/describe_configs_handle.rb +2 -13
  18. data/lib/rdkafka/admin/describe_configs_report.rb +5 -6
  19. data/lib/rdkafka/admin/incremental_alter_configs_handle.rb +2 -13
  20. data/lib/rdkafka/admin/incremental_alter_configs_report.rb +5 -6
  21. data/lib/rdkafka/admin/list_offsets_handle.rb +2 -13
  22. data/lib/rdkafka/admin/list_offsets_report.rb +5 -2
  23. data/lib/rdkafka/admin.rb +56 -137
  24. data/lib/rdkafka/bindings.rb +12 -3
  25. data/lib/rdkafka/callbacks/base_handler.rb +62 -0
  26. data/lib/rdkafka/callbacks/create_acl_handler.rb +37 -0
  27. data/lib/rdkafka/callbacks/create_partitions_handler.rb +37 -0
  28. data/lib/rdkafka/callbacks/create_topic_handler.rb +37 -0
  29. data/lib/rdkafka/callbacks/delete_acl_handler.rb +42 -0
  30. data/lib/rdkafka/callbacks/delete_groups_handler.rb +37 -0
  31. data/lib/rdkafka/callbacks/delete_topic_handler.rb +37 -0
  32. data/lib/rdkafka/callbacks/describe_acl_handler.rb +35 -0
  33. data/lib/rdkafka/callbacks/describe_configs_handler.rb +42 -0
  34. data/lib/rdkafka/callbacks/incremental_alter_configs_handler.rb +42 -0
  35. data/lib/rdkafka/callbacks/list_offsets_handler.rb +42 -0
  36. data/lib/rdkafka/callbacks.rb +56 -244
  37. data/lib/rdkafka/config.rb +50 -33
  38. data/lib/rdkafka/consumer/headers.rb +19 -5
  39. data/lib/rdkafka/consumer/topic_partition_list.rb +43 -33
  40. data/lib/rdkafka/consumer.rb +164 -23
  41. data/lib/rdkafka/defaults.rb +21 -1
  42. data/lib/rdkafka/error.rb +12 -4
  43. data/lib/rdkafka/helpers/list_offsets.rb +127 -0
  44. data/lib/rdkafka/helpers/metadata.rb +29 -0
  45. data/lib/rdkafka/metadata.rb +84 -19
  46. data/lib/rdkafka/producer/delivery_handle.rb +3 -3
  47. data/lib/rdkafka/producer/partitions_count_cache.rb +24 -38
  48. data/lib/rdkafka/producer.rb +72 -51
  49. data/lib/rdkafka/version.rb +3 -3
  50. data/lib/rdkafka.rb +13 -0
  51. data/package-lock.json +6 -6
  52. data/renovate.json +11 -1
  53. metadata +14 -4
  54. data/Gemfile +0 -13
  55. data/Gemfile.lint +0 -14
  56. data/Gemfile.lint.lock +0 -123
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Callbacks
5
+ # Handles `RD_KAFKA_EVENT_DESCRIBECONFIGS_RESULT` events
6
+ # @private
7
+ class DescribeConfigsHandler < BaseHandler
8
+ class << self
9
+ # Resolves the describe-configs handle from its result event
10
+ # @param event_ptr [FFI::Pointer] pointer to the event
11
+ # @return [void]
12
+ def call(event_ptr)
13
+ describe_configs = DescribeConfigsResult.new(event_ptr)
14
+ describe_configs_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
15
+
16
+ if describe_configs_handle = Rdkafka::Admin::DescribeConfigsHandle.remove(describe_configs_handle_ptr.address)
17
+ describe_configs_handle[:response] = describe_configs.result_error
18
+ describe_configs_handle.broker_message = read_event_string(describe_configs.error_string)
19
+
20
+ # Parsing can raise on per-resource errors. The exception is captured and re-raised on
21
+ # the waiting thread, since an exception leaving this callback would unwind through
22
+ # librdkafka native frames
23
+ describe_configs_handle.result = begin
24
+ if describe_configs.result_error == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
25
+ Rdkafka::Admin::DescribeConfigsReport.new(
26
+ config_entries: describe_configs.results,
27
+ entry_count: describe_configs.results_count
28
+ )
29
+ else
30
+ Rdkafka::Admin::DescribeConfigsReport.new(config_entries: FFI::Pointer::NULL, entry_count: 0)
31
+ end
32
+ rescue => e
33
+ e
34
+ end
35
+
36
+ describe_configs_handle.unlock
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Callbacks
5
+ # Handles `RD_KAFKA_EVENT_INCREMENTALALTERCONFIGS_RESULT` events
6
+ # @private
7
+ class IncrementalAlterConfigsHandler < BaseHandler
8
+ class << self
9
+ # Resolves the incremental-alter-configs handle from its result event
10
+ # @param event_ptr [FFI::Pointer] pointer to the event
11
+ # @return [void]
12
+ def call(event_ptr)
13
+ incremental_alter = IncrementalAlterConfigsResult.new(event_ptr)
14
+ incremental_alter_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
15
+
16
+ if incremental_alter_handle = Rdkafka::Admin::IncrementalAlterConfigsHandle.remove(incremental_alter_handle_ptr.address)
17
+ incremental_alter_handle[:response] = incremental_alter.result_error
18
+ incremental_alter_handle.broker_message = read_event_string(incremental_alter.error_string)
19
+
20
+ # Parsing can raise on per-resource errors. The exception is captured and re-raised on
21
+ # the waiting thread, since an exception leaving this callback would unwind through
22
+ # librdkafka native frames
23
+ incremental_alter_handle.result = begin
24
+ if incremental_alter.result_error == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
25
+ Rdkafka::Admin::IncrementalAlterConfigsReport.new(
26
+ config_entries: incremental_alter.results,
27
+ entry_count: incremental_alter.results_count
28
+ )
29
+ else
30
+ Rdkafka::Admin::IncrementalAlterConfigsReport.new(config_entries: FFI::Pointer::NULL, entry_count: 0)
31
+ end
32
+ rescue => e
33
+ e
34
+ end
35
+
36
+ incremental_alter_handle.unlock
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Callbacks
5
+ # Handles `RD_KAFKA_EVENT_LISTOFFSETS_RESULT` events
6
+ # @private
7
+ class ListOffsetsHandler < BaseHandler
8
+ class << self
9
+ # Resolves the list-offsets handle from its result event
10
+ # @param event_ptr [FFI::Pointer] pointer to the event
11
+ # @return [void]
12
+ def call(event_ptr)
13
+ list_offsets = ListOffsetsResult.new(event_ptr)
14
+ list_offsets_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
15
+
16
+ if list_offsets_handle = Rdkafka::Admin::ListOffsetsHandle.remove(list_offsets_handle_ptr.address)
17
+ list_offsets_handle[:response] = list_offsets.result_error
18
+ list_offsets_handle.broker_message = read_event_string(list_offsets.error_string)
19
+
20
+ # Parsing can raise on per-partition errors. The exception is captured and re-raised
21
+ # on the waiting thread, since an exception leaving this callback would unwind through
22
+ # librdkafka native frames
23
+ list_offsets_handle.result = begin
24
+ if list_offsets.result_error == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
25
+ Rdkafka::Admin::ListOffsetsReport.new(
26
+ result_infos: list_offsets.result_infos,
27
+ result_count: list_offsets.result_count
28
+ )
29
+ else
30
+ Rdkafka::Admin::ListOffsetsReport.new(result_infos: FFI::Pointer::NULL, result_count: 0)
31
+ end
32
+ rescue => e
33
+ e
34
+ end
35
+
36
+ list_offsets_handle.unlock
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -98,7 +98,7 @@ module Rdkafka
98
98
  @error_string = Rdkafka::Bindings.rd_kafka_error_string(rd_kafka_error_pointer)
99
99
  if @result_error == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
100
100
  # Get the number of matching acls
101
- pointer_to_size_t = FFI::MemoryPointer.new(:int32)
101
+ pointer_to_size_t = FFI::MemoryPointer.new(:size_t)
102
102
  @matching_acls = Rdkafka::Bindings.rd_kafka_DeleteAcls_result_response_matching_acls(acl_result_pointer, pointer_to_size_t)
103
103
  @matching_acls_count = pointer_to_size_t.read_int
104
104
  end
@@ -129,7 +129,7 @@ module Rdkafka
129
129
  if @result_error == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
130
130
  acl_describe_result = Rdkafka::Bindings.rd_kafka_event_DescribeAcls_result(event_ptr)
131
131
  # Get the number of matching acls
132
- pointer_to_size_t = FFI::MemoryPointer.new(:int32)
132
+ pointer_to_size_t = FFI::MemoryPointer.new(:size_t)
133
133
  @matching_acls = Rdkafka::Bindings.rd_kafka_DescribeAcls_result_acls(acl_describe_result, pointer_to_size_t)
134
134
  @matching_acls_count = pointer_to_size_t.read_int
135
135
  end
@@ -151,7 +151,7 @@ module Rdkafka
151
151
  if @result_error == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
152
152
  configs_describe_result = Rdkafka::Bindings.rd_kafka_event_DescribeConfigs_result(event_ptr)
153
153
  # Get the number of matching acls
154
- pointer_to_size_t = FFI::MemoryPointer.new(:int32)
154
+ pointer_to_size_t = FFI::MemoryPointer.new(:size_t)
155
155
  @results = Rdkafka::Bindings.rd_kafka_DescribeConfigs_result_resources(configs_describe_result, pointer_to_size_t)
156
156
  @results_count = pointer_to_size_t.read_int
157
157
  end
@@ -173,7 +173,7 @@ module Rdkafka
173
173
  if @result_error == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
174
174
  incremental_alter_result = Rdkafka::Bindings.rd_kafka_event_IncrementalAlterConfigs_result(event_ptr)
175
175
  # Get the number of matching acls
176
- pointer_to_size_t = FFI::MemoryPointer.new(:int32)
176
+ pointer_to_size_t = FFI::MemoryPointer.new(:size_t)
177
177
  @results = Rdkafka::Bindings.rd_kafka_IncrementalAlterConfigs_result_resources(incremental_alter_result, pointer_to_size_t)
178
178
  @results_count = pointer_to_size_t.read_int
179
179
  end
@@ -201,6 +201,12 @@ module Rdkafka
201
201
  end
202
202
  end
203
203
 
204
+ # Dispatches librdkafka admin background events to the matching handler.
205
+ #
206
+ # The actual per-operation handling lives in the `Rdkafka::Callbacks::*Handler` classes
207
+ # (see `lib/rdkafka/callbacks/`); this class only maps the event type to its handler and
208
+ # guarantees the event is destroyed afterwards.
209
+ #
204
210
  # @private
205
211
  class BackgroundEventCallback
206
212
  # Handles background events from librdkafka
@@ -208,232 +214,26 @@ module Rdkafka
208
214
  # @param event_ptr [FFI::Pointer] pointer to the event
209
215
  # @param _opaque_ptr [FFI::Pointer] unused opaque pointer
210
216
  def self.call(_client_ptr, event_ptr, _opaque_ptr)
211
- case Rdkafka::Bindings.rd_kafka_event_type(event_ptr)
212
- when Rdkafka::Bindings::RD_KAFKA_EVENT_CREATETOPICS_RESULT
213
- process_create_topic(event_ptr)
214
- when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBECONFIGS_RESULT
215
- process_describe_configs(event_ptr)
216
- when Rdkafka::Bindings::RD_KAFKA_EVENT_INCREMENTALALTERCONFIGS_RESULT
217
- process_incremental_alter_configs(event_ptr)
218
- when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETETOPICS_RESULT
219
- process_delete_topic(event_ptr)
220
- when Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_CREATEPARTITIONS_RESULT
221
- process_create_partitions(event_ptr)
222
- when Rdkafka::Bindings::RD_KAFKA_EVENT_CREATEACLS_RESULT
223
- process_create_acl(event_ptr)
224
- when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEACLS_RESULT
225
- process_delete_acl(event_ptr)
226
- when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBEACLS_RESULT
227
- process_describe_acl(event_ptr)
228
- when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEGROUPS_RESULT
229
- process_delete_groups(event_ptr)
230
- when Rdkafka::Bindings::RD_KAFKA_EVENT_LISTOFFSETS_RESULT
231
- process_list_offsets(event_ptr)
232
- end
233
- end
234
-
235
- private
236
-
237
- # Processes create topic result event
238
- # @param event_ptr [FFI::Pointer] pointer to the event
239
- def self.process_create_topic(event_ptr)
240
- create_topics_result = Rdkafka::Bindings.rd_kafka_event_CreateTopics_result(event_ptr)
241
-
242
- # Get the number of create topic results
243
- pointer_to_size_t = FFI::MemoryPointer.new(:int32)
244
- create_topic_result_array = Rdkafka::Bindings.rd_kafka_CreateTopics_result_topics(create_topics_result, pointer_to_size_t)
245
- create_topic_results = TopicResult.create_topic_results_from_array(pointer_to_size_t.read_int, create_topic_result_array)
246
- create_topic_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
247
-
248
- if create_topic_handle = Rdkafka::Admin::CreateTopicHandle.remove(create_topic_handle_ptr.address)
249
- create_topic_handle[:response] = create_topic_results[0].result_error
250
- create_topic_handle[:error_string] = create_topic_results[0].error_string
251
- create_topic_handle[:result_name] = create_topic_results[0].result_name
252
-
253
- create_topic_handle.unlock
254
- end
255
- end
256
-
257
- # Processes describe configs result event
258
- # @param event_ptr [FFI::Pointer] pointer to the event
259
- def self.process_describe_configs(event_ptr)
260
- describe_configs = DescribeConfigsResult.new(event_ptr)
261
- describe_configs_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
262
-
263
- if describe_configs_handle = Rdkafka::Admin::DescribeConfigsHandle.remove(describe_configs_handle_ptr.address)
264
- describe_configs_handle[:response] = describe_configs.result_error
265
- describe_configs_handle[:response_string] = describe_configs.error_string
266
- describe_configs_handle[:pending] = false
267
-
268
- if describe_configs.result_error == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
269
- describe_configs_handle[:config_entries] = describe_configs.results
270
- describe_configs_handle[:entry_count] = describe_configs.results_count
271
- end
272
-
273
- describe_configs_handle.unlock
274
- end
275
- end
276
-
277
- # Processes incremental alter configs result event
278
- # @param event_ptr [FFI::Pointer] pointer to the event
279
- def self.process_incremental_alter_configs(event_ptr)
280
- incremental_alter = IncrementalAlterConfigsResult.new(event_ptr)
281
- incremental_alter_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
282
-
283
- if incremental_alter_handle = Rdkafka::Admin::IncrementalAlterConfigsHandle.remove(incremental_alter_handle_ptr.address)
284
- incremental_alter_handle[:response] = incremental_alter.result_error
285
- incremental_alter_handle[:response_string] = incremental_alter.error_string
286
- incremental_alter_handle[:pending] = false
287
-
288
- if incremental_alter.result_error == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
289
- incremental_alter_handle[:config_entries] = incremental_alter.results
290
- incremental_alter_handle[:entry_count] = incremental_alter.results_count
291
- end
292
-
293
- incremental_alter_handle.unlock
294
- end
295
- end
296
-
297
- # Processes delete groups result event
298
- # @param event_ptr [FFI::Pointer] pointer to the event
299
- def self.process_delete_groups(event_ptr)
300
- delete_groups_result = Rdkafka::Bindings.rd_kafka_event_DeleteGroups_result(event_ptr)
301
-
302
- # Get the number of delete group results
303
- pointer_to_size_t = FFI::MemoryPointer.new(:size_t)
304
- delete_group_result_array = Rdkafka::Bindings.rd_kafka_DeleteGroups_result_groups(delete_groups_result, pointer_to_size_t)
305
- delete_group_results = GroupResult.create_group_results_from_array(pointer_to_size_t.read_int, delete_group_result_array) # TODO fix this
306
- delete_group_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
307
-
308
- if (delete_group_handle = Rdkafka::Admin::DeleteGroupsHandle.remove(delete_group_handle_ptr.address))
309
- delete_group_handle[:response] = delete_group_results[0].result_error
310
- delete_group_handle[:error_string] = delete_group_results[0].error_string
311
- delete_group_handle[:result_name] = delete_group_results[0].result_name
312
-
313
- delete_group_handle.unlock
314
- end
315
- end
316
-
317
- # Processes delete topic result event
318
- # @param event_ptr [FFI::Pointer] pointer to the event
319
- def self.process_delete_topic(event_ptr)
320
- delete_topics_result = Rdkafka::Bindings.rd_kafka_event_DeleteTopics_result(event_ptr)
321
-
322
- # Get the number of topic results
323
- pointer_to_size_t = FFI::MemoryPointer.new(:int32)
324
- delete_topic_result_array = Rdkafka::Bindings.rd_kafka_DeleteTopics_result_topics(delete_topics_result, pointer_to_size_t)
325
- delete_topic_results = TopicResult.create_topic_results_from_array(pointer_to_size_t.read_int, delete_topic_result_array)
326
- delete_topic_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
327
-
328
- if delete_topic_handle = Rdkafka::Admin::DeleteTopicHandle.remove(delete_topic_handle_ptr.address)
329
- delete_topic_handle[:response] = delete_topic_results[0].result_error
330
- delete_topic_handle[:error_string] = delete_topic_results[0].error_string
331
- delete_topic_handle[:result_name] = delete_topic_results[0].result_name
332
-
333
- delete_topic_handle.unlock
217
+ handler = case Rdkafka::Bindings.rd_kafka_event_type(event_ptr)
218
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_CREATETOPICS_RESULT then CreateTopicHandler
219
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETETOPICS_RESULT then DeleteTopicHandler
220
+ when Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_CREATEPARTITIONS_RESULT then CreatePartitionsHandler
221
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEGROUPS_RESULT then DeleteGroupsHandler
222
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_CREATEACLS_RESULT then CreateAclHandler
223
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEACLS_RESULT then DeleteAclHandler
224
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBEACLS_RESULT then DescribeAclHandler
225
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBECONFIGS_RESULT then DescribeConfigsHandler
226
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_INCREMENTALALTERCONFIGS_RESULT then IncrementalAlterConfigsHandler
227
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_LISTOFFSETS_RESULT then ListOffsetsHandler
334
228
  end
335
- end
336
-
337
- # Processes create partitions result event
338
- # @param event_ptr [FFI::Pointer] pointer to the event
339
- def self.process_create_partitions(event_ptr)
340
- create_partitionss_result = Rdkafka::Bindings.rd_kafka_event_CreatePartitions_result(event_ptr)
341
-
342
- # Get the number of create topic results
343
- pointer_to_size_t = FFI::MemoryPointer.new(:int32)
344
- create_partitions_result_array = Rdkafka::Bindings.rd_kafka_CreatePartitions_result_topics(create_partitionss_result, pointer_to_size_t)
345
- create_partitions_results = TopicResult.create_topic_results_from_array(pointer_to_size_t.read_int, create_partitions_result_array)
346
- create_partitions_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
347
-
348
- if create_partitions_handle = Rdkafka::Admin::CreatePartitionsHandle.remove(create_partitions_handle_ptr.address)
349
- create_partitions_handle[:response] = create_partitions_results[0].result_error
350
- create_partitions_handle[:error_string] = create_partitions_results[0].error_string
351
- create_partitions_handle[:result_name] = create_partitions_results[0].result_name
352
229
 
353
- create_partitions_handle.unlock
354
- end
355
- end
356
-
357
- # Processes create ACL result event
358
- # @param event_ptr [FFI::Pointer] pointer to the event
359
- def self.process_create_acl(event_ptr)
360
- create_acls_result = Rdkafka::Bindings.rd_kafka_event_CreateAcls_result(event_ptr)
361
-
362
- # Get the number of acl results
363
- pointer_to_size_t = FFI::MemoryPointer.new(:int32)
364
- create_acl_result_array = Rdkafka::Bindings.rd_kafka_CreateAcls_result_acls(create_acls_result, pointer_to_size_t)
365
- create_acl_results = CreateAclResult.create_acl_results_from_array(pointer_to_size_t.read_int, create_acl_result_array)
366
- create_acl_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
367
-
368
- if create_acl_handle = Rdkafka::Admin::CreateAclHandle.remove(create_acl_handle_ptr.address)
369
- create_acl_handle[:response] = create_acl_results[0].result_error
370
- create_acl_handle[:response_string] = create_acl_results[0].error_string
371
-
372
- create_acl_handle.unlock
373
- end
374
- end
375
-
376
- # Processes delete ACL result event
377
- # @param event_ptr [FFI::Pointer] pointer to the event
378
- def self.process_delete_acl(event_ptr)
379
- delete_acls_result = Rdkafka::Bindings.rd_kafka_event_DeleteAcls_result(event_ptr)
380
-
381
- # Get the number of acl results
382
- pointer_to_size_t = FFI::MemoryPointer.new(:int32)
383
- delete_acl_result_responses = Rdkafka::Bindings.rd_kafka_DeleteAcls_result_responses(delete_acls_result, pointer_to_size_t)
384
- delete_acl_results = DeleteAclResult.delete_acl_results_from_array(pointer_to_size_t.read_int, delete_acl_result_responses)
385
- delete_acl_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
386
-
387
- if delete_acl_handle = Rdkafka::Admin::DeleteAclHandle.remove(delete_acl_handle_ptr.address)
388
- delete_acl_handle[:response] = delete_acl_results[0].result_error
389
- delete_acl_handle[:response_string] = delete_acl_results[0].error_string
390
-
391
- if delete_acl_results[0].result_error == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
392
- delete_acl_handle[:matching_acls] = delete_acl_results[0].matching_acls
393
- delete_acl_handle[:matching_acls_count] = delete_acl_results[0].matching_acls_count
394
- end
395
-
396
- delete_acl_handle.unlock
397
- end
398
- end
399
-
400
- # Processes describe ACL result event
401
- # @param event_ptr [FFI::Pointer] pointer to the event
402
- def self.process_describe_acl(event_ptr)
403
- describe_acl = DescribeAclResult.new(event_ptr)
404
- describe_acl_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
405
-
406
- if describe_acl_handle = Rdkafka::Admin::DescribeAclHandle.remove(describe_acl_handle_ptr.address)
407
- describe_acl_handle[:response] = describe_acl.result_error
408
- describe_acl_handle[:response_string] = describe_acl.error_string
409
-
410
- if describe_acl.result_error == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
411
- describe_acl_handle[:acls] = describe_acl.matching_acls
412
- describe_acl_handle[:acls_count] = describe_acl.matching_acls_count
413
- end
414
-
415
- describe_acl_handle.unlock
416
- end
417
- end
418
-
419
- # Processes list offsets result event
420
- # @param event_ptr [FFI::Pointer] pointer to the event
421
- def self.process_list_offsets(event_ptr)
422
- list_offsets = ListOffsetsResult.new(event_ptr)
423
- list_offsets_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
424
-
425
- if list_offsets_handle = Rdkafka::Admin::ListOffsetsHandle.remove(list_offsets_handle_ptr.address)
426
- list_offsets_handle[:response] = list_offsets.result_error
427
- list_offsets_handle[:response_string] = list_offsets.error_string
428
- list_offsets_handle[:pending] = false
429
-
430
- if list_offsets.result_error == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
431
- list_offsets_handle[:result_infos] = list_offsets.result_infos
432
- list_offsets_handle[:result_count] = list_offsets.result_count
433
- end
434
-
435
- list_offsets_handle.unlock
436
- end
230
+ handler&.call(event_ptr)
231
+ ensure
232
+ # Background queue events are owned by the application and must be destroyed once
233
+ # served (see rd_kafka_conf_set_background_event_cb). All event-owned memory (result
234
+ # arrays, strings) has to be copied into Ruby objects by the handlers above before this
235
+ # runs - nothing may retain pointers into the event past this point.
236
+ Rdkafka::Bindings.rd_kafka_event_destroy(event_ptr)
437
237
  end
438
238
  end
439
239
 
@@ -453,23 +253,35 @@ module Rdkafka
453
253
  delivery_handle[:response] = message[:err]
454
254
  delivery_handle[:partition] = message[:partition]
455
255
  delivery_handle[:offset] = message[:offset]
456
- delivery_handle[:topic_name] = FFI::MemoryPointer.from_string(topic_name)
457
-
458
- # Call delivery callback on opaque
459
- if opaque = Rdkafka::Config.opaques[opaque_ptr.to_i]
460
- opaque.call_delivery_callback(
461
- Rdkafka::Producer::DeliveryReport.new(
462
- message[:partition],
463
- message[:offset],
464
- topic_name,
465
- message[:err],
466
- delivery_handle.label
467
- ),
468
- delivery_handle
469
- )
256
+ # The topic is not stored on the handle here: it is already set as the handle's
257
+ # `topic` Ruby attribute during produce, which spares a native string copy that
258
+ # would otherwise be allocated and retained for every message.
259
+
260
+ begin
261
+ # Call delivery callback on opaque
262
+ if opaque = Rdkafka::Config.opaques[opaque_ptr.to_i]
263
+ opaque.call_delivery_callback(
264
+ Rdkafka::Producer::DeliveryReport.new(
265
+ message[:partition],
266
+ message[:offset],
267
+ topic_name,
268
+ message[:err],
269
+ delivery_handle.label
270
+ ),
271
+ delivery_handle
272
+ )
273
+ end
274
+ rescue Exception => err
275
+ # A user delivery callback that raises must not escape this FFI callback: it runs on
276
+ # librdkafka's polling thread (abort_on_exception = true), so an escaping exception
277
+ # would crash the whole process. We log and swallow it, matching the rebalance
278
+ # callback. The `ensure` below still unlocks the handle, so `wait` returns the
279
+ # delivery report instead of blocking until its timeout and raising
280
+ # WaitTimeoutError for a message that was in fact delivered.
281
+ Rdkafka::Config.logger.error("Unhandled exception: #{err.class} - #{err.message}")
282
+ ensure
283
+ delivery_handle.unlock
470
284
  end
471
-
472
- delivery_handle.unlock
473
285
  end
474
286
  end
475
287
  end
@@ -197,6 +197,11 @@ module Rdkafka
197
197
  opaque = Opaque.new
198
198
  config = native_config(opaque)
199
199
 
200
+ # Set callback to receive background events, so admin-style operations issued on the
201
+ # consumer handle (e.g. Consumer#list_offsets) get their results dispatched back to their
202
+ # handles. librdkafka spawns its internally-managed background thread because of this.
203
+ Rdkafka::Bindings.rd_kafka_conf_set_background_event_cb(config, Rdkafka::Callbacks::BackgroundEventCallbackFunction)
204
+
200
205
  if @consumer_rebalance_listener
201
206
  opaque.consumer_rebalance_listener = @consumer_rebalance_listener
202
207
  Rdkafka::Bindings.rd_kafka_conf_set_rebalance_cb(config, Rdkafka::Bindings::RebalanceCallback)
@@ -342,47 +347,55 @@ module Rdkafka
342
347
  # @param opaque [Object, nil] optional opaque pointer for callbacks
343
348
  # @return [FFI::Pointer] native rdkafka configuration pointer
344
349
  def native_config(opaque = nil)
345
- Rdkafka::Bindings.rd_kafka_conf_new.tap do |config|
346
- # Create config
347
- @config_hash.merge(REQUIRED_CONFIG).each do |key, value|
348
- error_buffer = FFI::MemoryPointer.from_string(" " * 256)
349
- result = Rdkafka::Bindings.rd_kafka_conf_set(
350
- config,
351
- key.to_s,
352
- value.to_s,
353
- error_buffer,
354
- 256
355
- )
356
- unless result == :config_ok
357
- raise ConfigError.new(error_buffer.read_string)
358
- end
350
+ config = Rdkafka::Bindings.rd_kafka_conf_new
351
+
352
+ # Create config
353
+ @config_hash.merge(REQUIRED_CONFIG).each do |key, value|
354
+ error_buffer = FFI::MemoryPointer.from_string(" " * 256)
355
+ result = Rdkafka::Bindings.rd_kafka_conf_set(
356
+ config,
357
+ key.to_s,
358
+ value.to_s,
359
+ error_buffer,
360
+ 256
361
+ )
362
+ unless result == :config_ok
363
+ raise ConfigError.new(error_buffer.read_string)
359
364
  end
365
+ end
360
366
 
361
- # Set opaque pointer that's used as a proxy for callbacks
362
- if opaque
363
- pointer = ::FFI::Pointer.new(:pointer, opaque.object_id)
364
- Rdkafka::Bindings.rd_kafka_conf_set_opaque(config, pointer)
367
+ # Set opaque pointer that's used as a proxy for callbacks
368
+ if opaque
369
+ pointer = ::FFI::Pointer.new(:pointer, opaque.object_id)
370
+ Rdkafka::Bindings.rd_kafka_conf_set_opaque(config, pointer)
365
371
 
366
- # Store opaque with the pointer as key. We use this approach instead
367
- # of trying to convert the pointer to a Ruby object because there is
368
- # no risk of a segfault this way.
369
- Rdkafka::Config.opaques[pointer.to_i] = opaque
370
- end
372
+ # Store opaque with the pointer as key. We use this approach instead
373
+ # of trying to convert the pointer to a Ruby object because there is
374
+ # no risk of a segfault this way.
375
+ Rdkafka::Config.opaques[pointer.to_i] = opaque
376
+ end
371
377
 
372
- # Set log callback
373
- Rdkafka::Bindings.rd_kafka_conf_set_log_cb(config, Rdkafka::Bindings::LogCallback)
378
+ # Set log callback
379
+ Rdkafka::Bindings.rd_kafka_conf_set_log_cb(config, Rdkafka::Bindings::LogCallback)
374
380
 
375
- # Set stats callback
376
- Rdkafka::Bindings.rd_kafka_conf_set_stats_cb(config, Rdkafka::Bindings::StatsCallback)
381
+ # Set stats callback
382
+ Rdkafka::Bindings.rd_kafka_conf_set_stats_cb(config, Rdkafka::Bindings::StatsCallback)
377
383
 
378
- # Set error callback
379
- Rdkafka::Bindings.rd_kafka_conf_set_error_cb(config, Rdkafka::Bindings::ErrorCallback)
384
+ # Set error callback
385
+ Rdkafka::Bindings.rd_kafka_conf_set_error_cb(config, Rdkafka::Bindings::ErrorCallback)
380
386
 
381
- # Set oauth callback
382
- if Rdkafka::Config.oauthbearer_token_refresh_callback
383
- Rdkafka::Bindings.rd_kafka_conf_set_oauthbearer_token_refresh_cb(config, Rdkafka::Bindings::OAuthbearerTokenRefreshCallback)
384
- end
387
+ # Set oauth callback
388
+ if Rdkafka::Config.oauthbearer_token_refresh_callback
389
+ Rdkafka::Bindings.rd_kafka_conf_set_oauthbearer_token_refresh_cb(config, Rdkafka::Bindings::OAuthbearerTokenRefreshCallback)
385
390
  end
391
+
392
+ config
393
+ rescue Exception
394
+ # rd_kafka_conf_new allocates a conf the caller owns until it is handed to rd_kafka_new. If
395
+ # we raise before returning it (e.g. an invalid option in the loop above), destroy it here
396
+ # so the native config is not leaked.
397
+ Rdkafka::Bindings.rd_kafka_conf_destroy(config) if config
398
+ raise
386
399
  end
387
400
 
388
401
  # Creates a native Kafka handle
@@ -400,6 +413,10 @@ module Rdkafka
400
413
  )
401
414
 
402
415
  if handle.null?
416
+ # On success rd_kafka_new takes ownership of the conf and frees it itself; on failure
417
+ # librdkafka keeps application ownership (see rdkafka.c), so we must destroy it here to
418
+ # avoid leaking the conf on every failed client creation.
419
+ Rdkafka::Bindings.rd_kafka_conf_destroy(config)
403
420
  raise ClientCreationError.new(error_buffer.read_string)
404
421
  end
405
422
 
@@ -7,6 +7,11 @@ module Rdkafka
7
7
  # Empty frozen hash used when there are no headers
8
8
  EMPTY_HEADERS = {}.freeze
9
9
 
10
+ # Key under which reusable scratch pointers are stored on the current thread/fiber
11
+ SCRATCH_KEY = :rdkafka_headers_scratch
12
+
13
+ private_constant :SCRATCH_KEY
14
+
10
15
  # Reads a librdkafka native message's headers and returns them as a Ruby Hash
11
16
  # where each key maps to either a String (single value) or Array<String> (multiple values)
12
17
  # to support duplicate headers per KIP-82
@@ -17,7 +22,20 @@ module Rdkafka
17
22
  # @return [Hash{String => String, Array<String>}] headers Hash for the native_message
18
23
  # @raise [Rdkafka::RdkafkaError] when fail to read headers
19
24
  def self.from_native(native_message)
20
- headers_ptrptr = FFI::MemoryPointer.new(:pointer)
25
+ # Scratch output pointers for the header FFI calls, reused across messages. They are
26
+ # stored in fiber-local storage (`Thread.current[]` is fiber-local by design), so each
27
+ # thread and each fiber gets its own set and reuse is safe with fiber schedulers. They
28
+ # never escape this method and all data is read out of them before returning.
29
+ # Allocating them per message would cost several native allocations for every consumed
30
+ # message.
31
+ headers_ptrptr, name_ptrptr, value_ptrptr, size_ptr =
32
+ Thread.current[SCRATCH_KEY] ||= [
33
+ FFI::MemoryPointer.new(:pointer),
34
+ FFI::MemoryPointer.new(:pointer),
35
+ FFI::MemoryPointer.new(:pointer),
36
+ Rdkafka::Bindings::SizePtr.new
37
+ ].freeze
38
+
21
39
  err = Rdkafka::Bindings.rd_kafka_message_headers(native_message, headers_ptrptr)
22
40
 
23
41
  if err == Rdkafka::Bindings::RD_KAFKA_RESP_ERR__NOENT
@@ -28,10 +46,6 @@ module Rdkafka
28
46
 
29
47
  headers_ptr = headers_ptrptr.read_pointer
30
48
 
31
- name_ptrptr = FFI::MemoryPointer.new(:pointer)
32
- value_ptrptr = FFI::MemoryPointer.new(:pointer)
33
- size_ptr = Rdkafka::Bindings::SizePtr.new
34
-
35
49
  headers = {}
36
50
 
37
51
  idx = 0