karafka-rdkafka 0.27.2-aarch64-linux-gnu → 0.28.1-aarch64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -1
  3. data/README.md +4 -3
  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_acl_report.rb +2 -1
  12. data/lib/rdkafka/admin/create_partitions_handle.rb +1 -20
  13. data/lib/rdkafka/admin/create_topic_handle.rb +1 -20
  14. data/lib/rdkafka/admin/delete_acl_handle.rb +2 -10
  15. data/lib/rdkafka/admin/delete_groups_handle.rb +3 -11
  16. data/lib/rdkafka/admin/delete_records_handle.rb +31 -0
  17. data/lib/rdkafka/admin/delete_records_report.rb +25 -0
  18. data/lib/rdkafka/admin/delete_topic_handle.rb +1 -20
  19. data/lib/rdkafka/admin/describe_acl_handle.rb +2 -10
  20. data/lib/rdkafka/admin/describe_acl_report.rb +2 -1
  21. data/lib/rdkafka/admin/describe_configs_handle.rb +2 -13
  22. data/lib/rdkafka/admin/describe_configs_report.rb +5 -6
  23. data/lib/rdkafka/admin/incremental_alter_configs_handle.rb +2 -13
  24. data/lib/rdkafka/admin/incremental_alter_configs_report.rb +5 -6
  25. data/lib/rdkafka/admin/list_consumer_groups_handle.rb +31 -0
  26. data/lib/rdkafka/admin/list_consumer_groups_report.rb +83 -0
  27. data/lib/rdkafka/admin/list_offsets_handle.rb +2 -13
  28. data/lib/rdkafka/admin/list_offsets_report.rb +5 -2
  29. data/lib/rdkafka/admin.rb +233 -145
  30. data/lib/rdkafka/bindings.rb +63 -16
  31. data/lib/rdkafka/callbacks/base_handler.rb +62 -0
  32. data/lib/rdkafka/callbacks/create_acl_handler.rb +37 -0
  33. data/lib/rdkafka/callbacks/create_partitions_handler.rb +37 -0
  34. data/lib/rdkafka/callbacks/create_topic_handler.rb +37 -0
  35. data/lib/rdkafka/callbacks/delete_acl_handler.rb +42 -0
  36. data/lib/rdkafka/callbacks/delete_groups_handler.rb +37 -0
  37. data/lib/rdkafka/callbacks/delete_records_handler.rb +38 -0
  38. data/lib/rdkafka/callbacks/delete_topic_handler.rb +37 -0
  39. data/lib/rdkafka/callbacks/describe_acl_handler.rb +35 -0
  40. data/lib/rdkafka/callbacks/describe_configs_handler.rb +42 -0
  41. data/lib/rdkafka/callbacks/incremental_alter_configs_handler.rb +42 -0
  42. data/lib/rdkafka/callbacks/list_consumer_groups_handler.rb +39 -0
  43. data/lib/rdkafka/callbacks/list_offsets_handler.rb +42 -0
  44. data/lib/rdkafka/callbacks.rb +60 -246
  45. data/lib/rdkafka/config.rb +69 -50
  46. data/lib/rdkafka/consumer/headers.rb +19 -5
  47. data/lib/rdkafka/consumer/topic_partition_list.rb +57 -42
  48. data/lib/rdkafka/consumer.rb +190 -48
  49. data/lib/rdkafka/defaults.rb +32 -3
  50. data/lib/rdkafka/error.rb +14 -5
  51. data/lib/rdkafka/helpers/list_offsets.rb +127 -0
  52. data/lib/rdkafka/helpers/metadata.rb +29 -0
  53. data/lib/rdkafka/helpers/oauth.rb +10 -5
  54. data/lib/rdkafka/metadata.rb +84 -19
  55. data/lib/rdkafka/native_kafka.rb +20 -7
  56. data/lib/rdkafka/producer/delivery_handle.rb +4 -5
  57. data/lib/rdkafka/producer/partitions_count_cache.rb +24 -38
  58. data/lib/rdkafka/producer/testing.rb +2 -1
  59. data/lib/rdkafka/producer.rb +100 -71
  60. data/lib/rdkafka/version.rb +3 -3
  61. data/lib/rdkafka.rb +19 -0
  62. data/package-lock.json +6 -6
  63. data/renovate.json +11 -1
  64. metadata +21 -5
  65. data/Gemfile +0 -13
  66. data/Gemfile.lint +0 -14
  67. data/Gemfile.lint.lock +0 -123
@@ -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
@@ -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,28 @@ 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
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
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_DELETERECORDS_RESULT then DeleteRecordsHandler
223
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_CREATEACLS_RESULT then CreateAclHandler
224
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEACLS_RESULT then DeleteAclHandler
225
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBEACLS_RESULT then DescribeAclHandler
226
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBECONFIGS_RESULT then DescribeConfigsHandler
227
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_INCREMENTALALTERCONFIGS_RESULT then IncrementalAlterConfigsHandler
228
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_LISTOFFSETS_RESULT then ListOffsetsHandler
229
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_LISTCONSUMERGROUPS_RESULT then ListConsumerGroupsHandler
397
230
  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
231
 
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
232
+ handler&.call(event_ptr)
233
+ ensure
234
+ # Background queue events are owned by the application and must be destroyed once
235
+ # served (see rd_kafka_conf_set_background_event_cb). All event-owned memory (result
236
+ # arrays, strings) has to be copied into Ruby objects by the handlers above before this
237
+ # runs - nothing may retain pointers into the event past this point.
238
+ Rdkafka::Bindings.rd_kafka_event_destroy(event_ptr)
437
239
  end
438
240
  end
439
241
 
@@ -453,30 +255,42 @@ module Rdkafka
453
255
  delivery_handle[:response] = message[:err]
454
256
  delivery_handle[:partition] = message[:partition]
455
257
  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
- )
258
+ # The topic is not stored on the handle here: it is already set as the handle's
259
+ # `topic` Ruby attribute during produce, which spares a native string copy that
260
+ # would otherwise be allocated and retained for every message.
261
+
262
+ begin
263
+ # Call delivery callback on opaque
264
+ if opaque = Rdkafka::Config.opaques[opaque_ptr.to_i]
265
+ opaque.call_delivery_callback(
266
+ Rdkafka::Producer::DeliveryReport.new(
267
+ message[:partition],
268
+ message[:offset],
269
+ topic_name,
270
+ message[:err],
271
+ delivery_handle.label
272
+ ),
273
+ delivery_handle
274
+ )
275
+ end
276
+ rescue Exception => err
277
+ # A user delivery callback that raises must not escape this FFI callback: it runs on
278
+ # librdkafka's polling thread (abort_on_exception = true), so an escaping exception
279
+ # would crash the whole process. We log and swallow it, matching the rebalance
280
+ # callback. The `ensure` below still unlocks the handle, so `wait` returns the
281
+ # delivery report instead of blocking until its timeout and raising
282
+ # WaitTimeoutError for a message that was in fact delivered.
283
+ Rdkafka::Config.logger.error("Unhandled exception: #{err.class} - #{err.message}")
284
+ ensure
285
+ delivery_handle.unlock
470
286
  end
471
-
472
- delivery_handle.unlock
473
287
  end
474
288
  end
475
289
  end
476
290
 
477
- # @private
291
+ # @!visibility private
478
292
  @@mutex = Mutex.new
479
- # @private
293
+ # @!visibility private
480
294
  @@current_pid = nil
481
295
 
482
296
  class << self
@@ -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
@@ -197,6 +199,11 @@ module Rdkafka
197
199
  opaque = Opaque.new
198
200
  config = native_config(opaque)
199
201
 
202
+ # Set callback to receive background events, so admin-style operations issued on the
203
+ # consumer handle (e.g. Consumer#list_offsets) get their results dispatched back to their
204
+ # handles. librdkafka spawns its internally-managed background thread because of this.
205
+ Rdkafka::Bindings.rd_kafka_conf_set_background_event_cb(config, Rdkafka::Callbacks::BackgroundEventCallbackFunction)
206
+
200
207
  if @consumer_rebalance_listener
201
208
  opaque.consumer_rebalance_listener = @consumer_rebalance_listener
202
209
  Rdkafka::Bindings.rd_kafka_conf_set_rebalance_cb(config, Rdkafka::Bindings::RebalanceCallback)
@@ -290,6 +297,9 @@ module Rdkafka
290
297
  # Uses `rd_kafka_conf_dump` to retrieve every property (including defaults and
291
298
  # internal properties like `client.software.name`) as a flat Hash.
292
299
  #
300
+ # @return [Hash{Symbol => String}] property names mapped to their current values
301
+ #
302
+ # @raise [ConfigError] When the configuration contains invalid options
293
303
  # @note The librdkafka C API does not distinguish between producer-only, consumer-only,
294
304
  # and global properties at the configuration level. All properties are returned
295
305
  # regardless of the intended client type.
@@ -297,10 +307,6 @@ module Rdkafka
297
307
  # @note The returned Hash may include sensitive values such as authentication
298
308
  # credentials and key passwords. Do not log or serialize the returned data
299
309
  # unless you have explicitly redacted secret entries.
300
- #
301
- # @return [Hash{Symbol => String}] property names mapped to their current values
302
- #
303
- # @raise [ConfigError] When the configuration contains invalid options
304
310
  def describe_properties
305
311
  config = nil
306
312
  dump_ptr = nil
@@ -325,7 +331,8 @@ module Rdkafka
325
331
  Rdkafka::Bindings.rd_kafka_conf_destroy(config) if config
326
332
  end
327
333
 
328
- # Error that is returned by the underlying rdkafka error if an invalid configuration option is present.
334
+ # Error that is returned by the underlying rdkafka error if an invalid configuration option is
335
+ # present.
329
336
  class ConfigError < RuntimeError; end
330
337
 
331
338
  # Error that is returned by the underlying rdkafka library if the client cannot be created.
@@ -342,47 +349,55 @@ module Rdkafka
342
349
  # @param opaque [Object, nil] optional opaque pointer for callbacks
343
350
  # @return [FFI::Pointer] native rdkafka configuration pointer
344
351
  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
352
+ config = Rdkafka::Bindings.rd_kafka_conf_new
353
+
354
+ # Create config
355
+ @config_hash.merge(REQUIRED_CONFIG).each do |key, value|
356
+ error_buffer = FFI::MemoryPointer.from_string(" " * 256)
357
+ result = Rdkafka::Bindings.rd_kafka_conf_set(
358
+ config,
359
+ key.to_s,
360
+ value.to_s,
361
+ error_buffer,
362
+ 256
363
+ )
364
+ unless result == :config_ok
365
+ raise ConfigError.new(error_buffer.read_string)
359
366
  end
367
+ end
360
368
 
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)
369
+ # Set opaque pointer that's used as a proxy for callbacks
370
+ if opaque
371
+ pointer = ::FFI::Pointer.new(:pointer, opaque.object_id)
372
+ Rdkafka::Bindings.rd_kafka_conf_set_opaque(config, pointer)
365
373
 
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
374
+ # Store opaque with the pointer as key. We use this approach instead
375
+ # of trying to convert the pointer to a Ruby object because there is
376
+ # no risk of a segfault this way.
377
+ Rdkafka::Config.opaques[pointer.to_i] = opaque
378
+ end
371
379
 
372
- # Set log callback
373
- Rdkafka::Bindings.rd_kafka_conf_set_log_cb(config, Rdkafka::Bindings::LogCallback)
380
+ # Set log callback
381
+ Rdkafka::Bindings.rd_kafka_conf_set_log_cb(config, Rdkafka::Bindings::LogCallback)
374
382
 
375
- # Set stats callback
376
- Rdkafka::Bindings.rd_kafka_conf_set_stats_cb(config, Rdkafka::Bindings::StatsCallback)
383
+ # Set stats callback
384
+ Rdkafka::Bindings.rd_kafka_conf_set_stats_cb(config, Rdkafka::Bindings::StatsCallback)
377
385
 
378
- # Set error callback
379
- Rdkafka::Bindings.rd_kafka_conf_set_error_cb(config, Rdkafka::Bindings::ErrorCallback)
386
+ # Set error callback
387
+ Rdkafka::Bindings.rd_kafka_conf_set_error_cb(config, Rdkafka::Bindings::ErrorCallback)
380
388
 
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
389
+ # Set oauth callback
390
+ if Rdkafka::Config.oauthbearer_token_refresh_callback
391
+ Rdkafka::Bindings.rd_kafka_conf_set_oauthbearer_token_refresh_cb(config, Rdkafka::Bindings::OAuthbearerTokenRefreshCallback)
385
392
  end
393
+
394
+ config
395
+ rescue Exception
396
+ # rd_kafka_conf_new allocates a conf the caller owns until it is handed to rd_kafka_new. If
397
+ # we raise before returning it (e.g. an invalid option in the loop above), destroy it here
398
+ # so the native config is not leaked.
399
+ Rdkafka::Bindings.rd_kafka_conf_destroy(config) if config
400
+ raise
386
401
  end
387
402
 
388
403
  # Creates a native Kafka handle
@@ -400,6 +415,10 @@ module Rdkafka
400
415
  )
401
416
 
402
417
  if handle.null?
418
+ # On success rd_kafka_new takes ownership of the conf and frees it itself; on failure
419
+ # librdkafka keeps application ownership (see rdkafka.c), so we must destroy it here to
420
+ # avoid leaking the conf on every failed client creation.
421
+ Rdkafka::Bindings.rd_kafka_conf_destroy(config)
403
422
  raise ClientCreationError.new(error_buffer.read_string)
404
423
  end
405
424