rdkafka 0.28.0 → 0.29.0

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +1 -0
  4. data/dist/{librdkafka-2.14.1.tar.gz → librdkafka-2.14.2.tar.gz} +0 -0
  5. data/docker-compose-ssl.yml +1 -1
  6. data/docker-compose.yml +1 -1
  7. data/lib/rdkafka/abstract_handle.rb +31 -2
  8. data/lib/rdkafka/admin/config_binding_result.rb +2 -2
  9. data/lib/rdkafka/admin/create_acl_handle.rb +6 -5
  10. data/lib/rdkafka/admin/create_partitions_handle.rb +4 -6
  11. data/lib/rdkafka/admin/create_topic_handle.rb +4 -6
  12. data/lib/rdkafka/admin/delete_acl_handle.rb +5 -7
  13. data/lib/rdkafka/admin/delete_groups_handle.rb +4 -6
  14. data/lib/rdkafka/admin/delete_topic_handle.rb +4 -6
  15. data/lib/rdkafka/admin/describe_acl_handle.rb +5 -7
  16. data/lib/rdkafka/admin/describe_configs_handle.rb +4 -10
  17. data/lib/rdkafka/admin/describe_configs_report.rb +1 -5
  18. data/lib/rdkafka/admin/incremental_alter_configs_handle.rb +4 -10
  19. data/lib/rdkafka/admin/incremental_alter_configs_report.rb +1 -5
  20. data/lib/rdkafka/admin/list_offsets_handle.rb +5 -10
  21. data/lib/rdkafka/admin.rb +75 -43
  22. data/lib/rdkafka/bindings.rb +9 -4
  23. data/lib/rdkafka/callbacks/base_handler.rb +62 -0
  24. data/lib/rdkafka/callbacks/create_acl_handler.rb +37 -0
  25. data/lib/rdkafka/callbacks/create_partitions_handler.rb +37 -0
  26. data/lib/rdkafka/callbacks/create_topic_handler.rb +37 -0
  27. data/lib/rdkafka/callbacks/delete_acl_handler.rb +42 -0
  28. data/lib/rdkafka/callbacks/delete_groups_handler.rb +37 -0
  29. data/lib/rdkafka/callbacks/delete_topic_handler.rb +37 -0
  30. data/lib/rdkafka/callbacks/describe_acl_handler.rb +35 -0
  31. data/lib/rdkafka/callbacks/describe_configs_handler.rb +42 -0
  32. data/lib/rdkafka/callbacks/incremental_alter_configs_handler.rb +42 -0
  33. data/lib/rdkafka/callbacks/list_offsets_handler.rb +42 -0
  34. data/lib/rdkafka/callbacks.rb +50 -244
  35. data/lib/rdkafka/config.rb +45 -33
  36. data/lib/rdkafka/consumer/headers.rb +19 -5
  37. data/lib/rdkafka/consumer/partition.rb +8 -1
  38. data/lib/rdkafka/consumer/topic_partition_list.rb +44 -22
  39. data/lib/rdkafka/consumer.rb +111 -21
  40. data/lib/rdkafka/defaults.rb +19 -0
  41. data/lib/rdkafka/helpers/metadata.rb +29 -0
  42. data/lib/rdkafka/metadata.rb +86 -19
  43. data/lib/rdkafka/native_kafka.rb +2 -4
  44. data/lib/rdkafka/producer/delivery_handle.rb +3 -3
  45. data/lib/rdkafka/producer/partitions_count_cache.rb +24 -38
  46. data/lib/rdkafka/producer.rb +56 -44
  47. data/lib/rdkafka/version.rb +3 -3
  48. data/lib/rdkafka.rb +12 -0
  49. data/package-lock.json +3 -3
  50. data/rdkafka.gemspec +1 -0
  51. metadata +14 -5
  52. data/Gemfile +0 -13
  53. data/Gemfile.lint +0 -14
  54. 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_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
@@ -208,232 +208,26 @@ module Rdkafka
208
208
  # @param event_ptr [FFI::Pointer] pointer to the event
209
209
  # @param _opaque_ptr [FFI::Pointer] unused opaque pointer
210
210
  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
334
- 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
-
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
211
+ handler = case Rdkafka::Bindings.rd_kafka_event_type(event_ptr)
212
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_CREATETOPICS_RESULT then CreateTopicHandler
213
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETETOPICS_RESULT then DeleteTopicHandler
214
+ when Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_CREATEPARTITIONS_RESULT then CreatePartitionsHandler
215
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEGROUPS_RESULT then DeleteGroupsHandler
216
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_CREATEACLS_RESULT then CreateAclHandler
217
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEACLS_RESULT then DeleteAclHandler
218
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBEACLS_RESULT then DescribeAclHandler
219
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBECONFIGS_RESULT then DescribeConfigsHandler
220
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_INCREMENTALALTERCONFIGS_RESULT then IncrementalAlterConfigsHandler
221
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_LISTOFFSETS_RESULT then ListOffsetsHandler
416
222
  end
417
- end
418
223
 
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
224
+ handler&.call(event_ptr)
225
+ ensure
226
+ # Background queue events are owned by the application and must be destroyed once
227
+ # served (see rd_kafka_conf_set_background_event_cb). All event-owned memory (result
228
+ # arrays, strings) has to be copied into Ruby objects by the handlers above before this
229
+ # runs - nothing may retain pointers into the event past this point.
230
+ Rdkafka::Bindings.rd_kafka_event_destroy(event_ptr)
437
231
  end
438
232
  end
439
233
 
@@ -453,23 +247,35 @@ module Rdkafka
453
247
  delivery_handle[:response] = message[:err]
454
248
  delivery_handle[:partition] = message[:partition]
455
249
  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
- )
250
+ # The topic is not stored on the handle here: it is already set as the handle's
251
+ # `topic` Ruby attribute during produce, which spares a native string copy that
252
+ # would otherwise be allocated and retained for every message.
253
+
254
+ begin
255
+ # Call delivery callback on opaque
256
+ if opaque = Rdkafka::Config.opaques[opaque_ptr.to_i]
257
+ opaque.call_delivery_callback(
258
+ Rdkafka::Producer::DeliveryReport.new(
259
+ message[:partition],
260
+ message[:offset],
261
+ topic_name,
262
+ message[:err],
263
+ delivery_handle.label
264
+ ),
265
+ delivery_handle
266
+ )
267
+ end
268
+ rescue Exception => err
269
+ # A user delivery callback that raises must not escape this FFI callback: it runs on
270
+ # librdkafka's polling thread (abort_on_exception = true), so an escaping exception
271
+ # would crash the whole process. We log and swallow it, matching the rebalance
272
+ # callback. The `ensure` below still unlocks the handle, so `wait` returns the
273
+ # delivery report instead of blocking until its timeout and raising
274
+ # WaitTimeoutError for a message that was in fact delivered.
275
+ Rdkafka::Config.logger.error("Unhandled exception: #{err.class} - #{err.message}")
276
+ ensure
277
+ delivery_handle.unlock
470
278
  end
471
-
472
- delivery_handle.unlock
473
279
  end
474
280
  end
475
281
  end
@@ -342,47 +342,55 @@ module Rdkafka
342
342
  # @param opaque [Object, nil] optional opaque pointer for callbacks
343
343
  # @return [FFI::Pointer] native rdkafka configuration pointer
344
344
  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
345
+ config = Rdkafka::Bindings.rd_kafka_conf_new
346
+
347
+ # Create config
348
+ @config_hash.merge(REQUIRED_CONFIG).each do |key, value|
349
+ error_buffer = FFI::MemoryPointer.from_string(" " * 256)
350
+ result = Rdkafka::Bindings.rd_kafka_conf_set(
351
+ config,
352
+ key.to_s,
353
+ value.to_s,
354
+ error_buffer,
355
+ 256
356
+ )
357
+ unless result == :config_ok
358
+ raise ConfigError.new(error_buffer.read_string)
359
359
  end
360
+ end
360
361
 
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)
362
+ # Set opaque pointer that's used as a proxy for callbacks
363
+ if opaque
364
+ pointer = ::FFI::Pointer.new(:pointer, opaque.object_id)
365
+ Rdkafka::Bindings.rd_kafka_conf_set_opaque(config, pointer)
365
366
 
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
367
+ # Store opaque with the pointer as key. We use this approach instead
368
+ # of trying to convert the pointer to a Ruby object because there is
369
+ # no risk of a segfault this way.
370
+ Rdkafka::Config.opaques[pointer.to_i] = opaque
371
+ end
371
372
 
372
- # Set log callback
373
- Rdkafka::Bindings.rd_kafka_conf_set_log_cb(config, Rdkafka::Bindings::LogCallback)
373
+ # Set log callback
374
+ Rdkafka::Bindings.rd_kafka_conf_set_log_cb(config, Rdkafka::Bindings::LogCallback)
374
375
 
375
- # Set stats callback
376
- Rdkafka::Bindings.rd_kafka_conf_set_stats_cb(config, Rdkafka::Bindings::StatsCallback)
376
+ # Set stats callback
377
+ Rdkafka::Bindings.rd_kafka_conf_set_stats_cb(config, Rdkafka::Bindings::StatsCallback)
377
378
 
378
- # Set error callback
379
- Rdkafka::Bindings.rd_kafka_conf_set_error_cb(config, Rdkafka::Bindings::ErrorCallback)
379
+ # Set error callback
380
+ Rdkafka::Bindings.rd_kafka_conf_set_error_cb(config, Rdkafka::Bindings::ErrorCallback)
380
381
 
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
382
+ # Set oauth callback
383
+ if Rdkafka::Config.oauthbearer_token_refresh_callback
384
+ Rdkafka::Bindings.rd_kafka_conf_set_oauthbearer_token_refresh_cb(config, Rdkafka::Bindings::OAuthbearerTokenRefreshCallback)
385
385
  end
386
+
387
+ config
388
+ rescue Exception
389
+ # rd_kafka_conf_new allocates a conf the caller owns until it is handed to rd_kafka_new. If
390
+ # we raise before returning it (e.g. an invalid option in the loop above), destroy it here
391
+ # so the native config is not leaked.
392
+ Rdkafka::Bindings.rd_kafka_conf_destroy(config) if config
393
+ raise
386
394
  end
387
395
 
388
396
  # Creates a native Kafka handle
@@ -400,6 +408,10 @@ module Rdkafka
400
408
  )
401
409
 
402
410
  if handle.null?
411
+ # On success rd_kafka_new takes ownership of the conf and frees it itself; on failure
412
+ # librdkafka keeps application ownership (see rdkafka.c), so we must destroy it here to
413
+ # avoid leaking the conf on every failed client creation.
414
+ Rdkafka::Bindings.rd_kafka_conf_destroy(config)
403
415
  raise ClientCreationError.new(error_buffer.read_string)
404
416
  end
405
417
 
@@ -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
@@ -16,14 +16,20 @@ module Rdkafka
16
16
  # @return [Integer]
17
17
  attr_reader :err
18
18
 
19
+ # Partition metadata in the context of a consumer
20
+ # @return [String, nil]
21
+ attr_reader :metadata
22
+
19
23
  # @private
20
24
  # @param partition [Integer] partition number
21
25
  # @param offset [Integer, nil] partition offset
22
26
  # @param err [Integer] error code from librdkafka
23
- def initialize(partition, offset, err = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR)
27
+ # @param metadata [String, nil] partition metadata
28
+ def initialize(partition, offset, err = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR, metadata = nil)
24
29
  @partition = partition
25
30
  @offset = offset
26
31
  @err = err
32
+ @metadata = metadata
27
33
  end
28
34
 
29
35
  # Human readable representation of this partition.
@@ -32,6 +38,7 @@ module Rdkafka
32
38
  message = "<Partition #{partition}"
33
39
  message += " offset=#{offset}" if offset
34
40
  message += " err=#{err}" if err != Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
41
+ message += " metadata=#{metadata}" if !metadata.nil?
35
42
  message += ">"
36
43
  message
37
44
  end