rdkafka 0.29.2-x86_64-linux-gnu → 0.31.0-x86_64-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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -15
  3. data/README.md +2 -0
  4. data/docker-compose-ssl.yml +1 -0
  5. data/docker-compose.yml +1 -0
  6. data/ext/librdkafka.so +0 -0
  7. data/lib/rdkafka/admin/alter_consumer_group_offsets_handle.rb +31 -0
  8. data/lib/rdkafka/admin/alter_consumer_group_offsets_report.rb +50 -0
  9. data/lib/rdkafka/admin/delete_consumer_group_offsets_handle.rb +31 -0
  10. data/lib/rdkafka/admin/delete_consumer_group_offsets_report.rb +50 -0
  11. data/lib/rdkafka/admin/describe_cluster_handle.rb +31 -0
  12. data/lib/rdkafka/admin/describe_cluster_report.rb +48 -0
  13. data/lib/rdkafka/admin/describe_consumer_groups_handle.rb +31 -0
  14. data/lib/rdkafka/admin/describe_consumer_groups_report.rb +151 -0
  15. data/lib/rdkafka/admin/describe_result_parsing.rb +47 -0
  16. data/lib/rdkafka/admin/describe_topics_handle.rb +31 -0
  17. data/lib/rdkafka/admin/describe_topics_report.rb +118 -0
  18. data/lib/rdkafka/admin.rb +365 -0
  19. data/lib/rdkafka/bindings/admin/acls.rb +85 -0
  20. data/lib/rdkafka/bindings/admin/configs.rb +37 -0
  21. data/lib/rdkafka/bindings/admin/list_offsets.rb +26 -0
  22. data/lib/rdkafka/bindings/admin.rb +197 -0
  23. data/lib/rdkafka/bindings/callbacks.rb +100 -0
  24. data/lib/rdkafka/bindings/configuration.rb +50 -0
  25. data/lib/rdkafka/bindings/consumer.rb +83 -0
  26. data/lib/rdkafka/bindings/errors.rb +15 -0
  27. data/lib/rdkafka/bindings/handle.rb +16 -0
  28. data/lib/rdkafka/bindings/messages.rb +28 -0
  29. data/lib/rdkafka/bindings/metadata.rb +14 -0
  30. data/lib/rdkafka/bindings/polling.rb +17 -0
  31. data/lib/rdkafka/bindings/producer.rb +77 -0
  32. data/lib/rdkafka/bindings/structs.rb +62 -0
  33. data/lib/rdkafka/bindings.rb +12 -612
  34. data/lib/rdkafka/callbacks/alter_consumer_group_offsets_handler.rb +65 -0
  35. data/lib/rdkafka/callbacks/delete_consumer_group_offsets_handler.rb +65 -0
  36. data/lib/rdkafka/callbacks/describe_cluster_handler.rb +36 -0
  37. data/lib/rdkafka/callbacks/describe_consumer_groups_handler.rb +36 -0
  38. data/lib/rdkafka/callbacks/describe_topics_handler.rb +36 -0
  39. data/lib/rdkafka/callbacks.rb +24 -17
  40. data/lib/rdkafka/clients.rb +13 -46
  41. data/lib/rdkafka/config.rb +34 -0
  42. data/lib/rdkafka/consumer/message.rb +6 -9
  43. data/lib/rdkafka/producer/delivery_handle.rb +8 -2
  44. data/lib/rdkafka/producer/delivery_report.rb +51 -1
  45. data/lib/rdkafka/version.rb +3 -3
  46. data/lib/rdkafka.rb +16 -0
  47. data/package-lock.json +9 -9
  48. data/rdkafka.gemspec +1 -1
  49. data/renovate.json +3 -0
  50. metadata +33 -3
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Callbacks
5
+ # Handles `RD_KAFKA_EVENT_ALTERCONSUMERGROUPOFFSETS_RESULT` events
6
+ # @private
7
+ class AlterConsumerGroupOffsetsHandler < BaseHandler
8
+ class << self
9
+ # Resolves the alter consumer group 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
+ result = Rdkafka::Bindings.rd_kafka_event_AlterConsumerGroupOffsets_result(event_ptr)
14
+
15
+ pointer_to_size_t = FFI::MemoryPointer.new(:size_t)
16
+ group_result_array = Rdkafka::Bindings.rd_kafka_AlterConsumerGroupOffsets_result_groups(result, pointer_to_size_t)
17
+ group_results = GroupResult.create_group_results_from_array(pointer_to_size_t.read_int, group_result_array)
18
+ handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
19
+
20
+ if (handle = Rdkafka::Admin::AlterConsumerGroupOffsetsHandle.remove(handle_ptr.address))
21
+ unless resolve_operation_error(event_ptr, handle)
22
+ group_result = group_results[0]
23
+
24
+ # librdkafka only supports one group per invocation, so a missing group result
25
+ # means the broker returned nothing to act on rather than a partial success.
26
+ if group_result.nil?
27
+ handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
28
+ handle.result = Rdkafka::Admin::AlterConsumerGroupOffsetsReport.new(
29
+ FFI::Pointer::NULL,
30
+ FFI::Pointer::NULL
31
+ )
32
+ else
33
+ report = Rdkafka::Admin::AlterConsumerGroupOffsetsReport.new(
34
+ group_result.error_string,
35
+ group_result.result_name,
36
+ Rdkafka::Bindings.rd_kafka_group_result_partitions(group_result_array.read_pointer)
37
+ )
38
+ handle.result = report
39
+
40
+ # Kafka reports a rejected offset change per partition, leaving the group level
41
+ # error unset - a group with live members comes back with the group error NULL
42
+ # and `unknown_member_id` on each partition. Resolving on the group error alone
43
+ # would hand back a report that looks like success, so any failed partition has
44
+ # to fail the handle too.
45
+ failed = report.partitions.find { |partition| partition[:error] }
46
+
47
+ if !group_result.result_error.zero?
48
+ handle[:response] = group_result.result_error
49
+ handle.broker_message = report.error_string
50
+ elsif failed
51
+ handle[:response] = failed[:error].rdkafka_response
52
+ handle.broker_message = "Failed for partition #{failed[:partition]} of '#{failed[:topic]}'"
53
+ else
54
+ handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
55
+ end
56
+ end
57
+
58
+ handle.unlock
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Callbacks
5
+ # Handles `RD_KAFKA_EVENT_DELETECONSUMERGROUPOFFSETS_RESULT` events
6
+ # @private
7
+ class DeleteConsumerGroupOffsetsHandler < BaseHandler
8
+ class << self
9
+ # Resolves the delete consumer group 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
+ result = Rdkafka::Bindings.rd_kafka_event_DeleteConsumerGroupOffsets_result(event_ptr)
14
+
15
+ pointer_to_size_t = FFI::MemoryPointer.new(:size_t)
16
+ group_result_array = Rdkafka::Bindings.rd_kafka_DeleteConsumerGroupOffsets_result_groups(result, pointer_to_size_t)
17
+ group_results = GroupResult.create_group_results_from_array(pointer_to_size_t.read_int, group_result_array)
18
+ handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
19
+
20
+ if (handle = Rdkafka::Admin::DeleteConsumerGroupOffsetsHandle.remove(handle_ptr.address))
21
+ unless resolve_operation_error(event_ptr, handle)
22
+ group_result = group_results[0]
23
+
24
+ # librdkafka only supports one group per invocation, so a missing group result
25
+ # means the broker returned nothing to act on rather than a partial success.
26
+ if group_result.nil?
27
+ handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
28
+ handle.result = Rdkafka::Admin::DeleteConsumerGroupOffsetsReport.new(
29
+ FFI::Pointer::NULL,
30
+ FFI::Pointer::NULL
31
+ )
32
+ else
33
+ report = Rdkafka::Admin::DeleteConsumerGroupOffsetsReport.new(
34
+ group_result.error_string,
35
+ group_result.result_name,
36
+ Rdkafka::Bindings.rd_kafka_group_result_partitions(group_result_array.read_pointer)
37
+ )
38
+ handle.result = report
39
+
40
+ # Kafka reports a rejected offset change per partition, leaving the group level
41
+ # error unset - a group with live members comes back with the group error NULL
42
+ # and `unknown_member_id` on each partition. Resolving on the group error alone
43
+ # would hand back a report that looks like success, so any failed partition has
44
+ # to fail the handle too.
45
+ failed = report.partitions.find { |partition| partition[:error] }
46
+
47
+ if !group_result.result_error.zero?
48
+ handle[:response] = group_result.result_error
49
+ handle.broker_message = report.error_string
50
+ elsif failed
51
+ handle[:response] = failed[:error].rdkafka_response
52
+ handle.broker_message = "Failed for partition #{failed[:partition]} of '#{failed[:topic]}'"
53
+ else
54
+ handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
55
+ end
56
+ end
57
+
58
+ handle.unlock
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Callbacks
5
+ # Handles `RD_KAFKA_EVENT_DESCRIBECLUSTER_RESULT` events
6
+ # @private
7
+ class DescribeClusterHandler < BaseHandler
8
+ class << self
9
+ # Resolves the describe cluster 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_DescribeCluster_result(event_ptr)
14
+ handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
15
+
16
+ return unless (handle = Rdkafka::Admin::DescribeClusterHandle.remove(handle_ptr.address))
17
+
18
+ return if resolve_operation_error(event_ptr, handle)
19
+
20
+ handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
21
+
22
+ # Parsing must copy everything out of event-owned memory before the event is destroyed.
23
+ # An exception here is captured and re-raised on the waiting thread, since it cannot
24
+ # unwind through librdkafka native frames.
25
+ handle.result = begin
26
+ Rdkafka::Admin::DescribeClusterReport.new(result_ptr)
27
+ rescue => e
28
+ e
29
+ end
30
+
31
+ handle.unlock
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Callbacks
5
+ # Handles `RD_KAFKA_EVENT_DESCRIBECONSUMERGROUPS_RESULT` events
6
+ # @private
7
+ class DescribeConsumerGroupsHandler < BaseHandler
8
+ class << self
9
+ # Resolves the describe-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_DescribeConsumerGroups_result(event_ptr)
14
+ handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
15
+
16
+ return unless (handle = Rdkafka::Admin::DescribeConsumerGroupsHandle.remove(handle_ptr.address))
17
+
18
+ return if resolve_operation_error(event_ptr, handle)
19
+
20
+ handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
21
+
22
+ # Parsing must copy everything out of event-owned memory before the event is destroyed.
23
+ # An exception here is captured and re-raised on the waiting thread, since it cannot
24
+ # unwind through librdkafka native frames.
25
+ handle.result = begin
26
+ Rdkafka::Admin::DescribeConsumerGroupsReport.new(result_ptr)
27
+ rescue => e
28
+ e
29
+ end
30
+
31
+ handle.unlock
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Callbacks
5
+ # Handles `RD_KAFKA_EVENT_DESCRIBETOPICS_RESULT` events
6
+ # @private
7
+ class DescribeTopicsHandler < BaseHandler
8
+ class << self
9
+ # Resolves the describe topics 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_DescribeTopics_result(event_ptr)
14
+ handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
15
+
16
+ return unless (handle = Rdkafka::Admin::DescribeTopicsHandle.remove(handle_ptr.address))
17
+
18
+ return if resolve_operation_error(event_ptr, handle)
19
+
20
+ handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
21
+
22
+ # Parsing must copy everything out of event-owned memory before the event is destroyed.
23
+ # An exception here is captured and re-raised on the waiting thread, since it cannot
24
+ # unwind through librdkafka native frames.
25
+ handle.result = begin
26
+ Rdkafka::Admin::DescribeTopicsReport.new(result_ptr)
27
+ rescue => e
28
+ e
29
+ end
30
+
31
+ handle.unlock
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -21,10 +21,9 @@ module Rdkafka
21
21
  # @param array_pointer [FFI::Pointer] pointer to the results array
22
22
  # @return [Array<TopicResult>] array of topic results
23
23
  def self.create_topic_results_from_array(count, array_pointer)
24
- (1..count).map do |index|
25
- result_pointer = (array_pointer + (index - 1)).read_pointer
26
- new(result_pointer)
27
- end
24
+ return [] if count.zero?
25
+
26
+ array_pointer.read_array_of_pointer(count).map { |result_pointer| new(result_pointer) }
28
27
  end
29
28
  end
30
29
 
@@ -53,10 +52,9 @@ module Rdkafka
53
52
  # @param array_pointer [FFI::Pointer] pointer to the results array
54
53
  # @return [Array<GroupResult>] array of group results
55
54
  def self.create_group_results_from_array(count, array_pointer)
56
- (1..count).map do |index|
57
- result_pointer = (array_pointer + (index - 1)).read_pointer
58
- new(result_pointer)
59
- end
55
+ return [] if count.zero?
56
+
57
+ array_pointer.read_array_of_pointer(count).map { |result_pointer| new(result_pointer) }
60
58
  end
61
59
  end
62
60
 
@@ -77,10 +75,9 @@ module Rdkafka
77
75
  # @param array_pointer [FFI::Pointer] pointer to the results array
78
76
  # @return [Array<CreateAclResult>] array of ACL results
79
77
  def self.create_acl_results_from_array(count, array_pointer)
80
- (1..count).map do |index|
81
- result_pointer = (array_pointer + (index - 1)).read_pointer
82
- new(result_pointer)
83
- end
78
+ return [] if count.zero?
79
+
80
+ array_pointer.read_array_of_pointer(count).map { |result_pointer| new(result_pointer) }
84
81
  end
85
82
  end
86
83
 
@@ -108,10 +105,9 @@ module Rdkafka
108
105
  # @param array_pointer [FFI::Pointer] pointer to the results array
109
106
  # @return [Array<DeleteAclResult>] array of delete ACL results
110
107
  def self.delete_acl_results_from_array(count, array_pointer)
111
- (1..count).map do |index|
112
- result_pointer = (array_pointer + (index - 1)).read_pointer
113
- new(result_pointer)
114
- end
108
+ return [] if count.zero?
109
+
110
+ array_pointer.read_array_of_pointer(count).map { |result_pointer| new(result_pointer) }
115
111
  end
116
112
  end
117
113
 
@@ -213,6 +209,8 @@ module Rdkafka
213
209
  when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETETOPICS_RESULT then DeleteTopicHandler
214
210
  when Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_CREATEPARTITIONS_RESULT then CreatePartitionsHandler
215
211
  when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEGROUPS_RESULT then DeleteGroupsHandler
212
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_ALTERCONSUMERGROUPOFFSETS_RESULT then AlterConsumerGroupOffsetsHandler
213
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETECONSUMERGROUPOFFSETS_RESULT then DeleteConsumerGroupOffsetsHandler
216
214
  when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETERECORDS_RESULT then DeleteRecordsHandler
217
215
  when Rdkafka::Bindings::RD_KAFKA_EVENT_CREATEACLS_RESULT then CreateAclHandler
218
216
  when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEACLS_RESULT then DeleteAclHandler
@@ -221,6 +219,9 @@ module Rdkafka
221
219
  when Rdkafka::Bindings::RD_KAFKA_EVENT_INCREMENTALALTERCONFIGS_RESULT then IncrementalAlterConfigsHandler
222
220
  when Rdkafka::Bindings::RD_KAFKA_EVENT_LISTOFFSETS_RESULT then ListOffsetsHandler
223
221
  when Rdkafka::Bindings::RD_KAFKA_EVENT_LISTCONSUMERGROUPS_RESULT then ListConsumerGroupsHandler
222
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBECONSUMERGROUPS_RESULT then DescribeConsumerGroupsHandler
223
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBECLUSTER_RESULT then DescribeClusterHandler
224
+ when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBETOPICS_RESULT then DescribeTopicsHandler
224
225
  end
225
226
 
226
227
  handler&.call(event_ptr)
@@ -249,6 +250,9 @@ module Rdkafka
249
250
  delivery_handle[:response] = message[:err]
250
251
  delivery_handle[:partition] = message[:partition]
251
252
  delivery_handle[:offset] = message[:offset]
253
+ delivery_handle[:status] = Rdkafka::Bindings.rd_kafka_message_status(message_ptr)
254
+ delivery_handle[:latency] = Rdkafka::Bindings.rd_kafka_message_latency(message_ptr)
255
+ delivery_handle[:broker_id] = Rdkafka::Bindings.rd_kafka_message_broker_id(message_ptr)
252
256
  # The topic is not stored on the handle here: it is already set as the handle's
253
257
  # `topic` Ruby attribute during produce, which spares a native string copy that
254
258
  # would otherwise be allocated and retained for every message.
@@ -262,7 +266,10 @@ module Rdkafka
262
266
  message[:offset],
263
267
  topic_name,
264
268
  message[:err],
265
- delivery_handle.label
269
+ delivery_handle.label,
270
+ status: delivery_handle[:status],
271
+ latency: delivery_handle[:latency],
272
+ broker_id: delivery_handle[:broker_id]
266
273
  ),
267
274
  delivery_handle
268
275
  )
@@ -4,22 +4,10 @@ module Rdkafka
4
4
  # Tracks live public clients so they can be closed before Ruby begins forced finalization.
5
5
  # @private
6
6
  module Clients
7
- # Ruby rewrote `ObjectSpace::WeakMap` in 3.3. On 3.2 and earlier its `[]` can hand back a
8
- # foreign or already-freed object for a key, which raises `NoMethodError` or segfaults the VM
9
- # the moment the value is touched. Track weakly only where that is safe and fall back to a
10
- # strong registry - pruned as clients close - on older Rubies. The strong registry holds an
11
- # open, un-closed client until shutdown, which is exactly when this cleanup needs it anyway.
12
- WEAK_TRACKING = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.3")
13
-
14
7
  @pid = Process.pid
15
8
  @mutex = Mutex.new
16
-
17
- if WEAK_TRACKING
18
- @clients = ObjectSpace::WeakMap.new
19
- @tokens = [].freeze
20
- else
21
- @clients = {}.freeze
22
- end
9
+ @clients = ObjectSpace::WeakMap.new
10
+ @tokens = [].freeze
23
11
 
24
12
  # Registers a successfully constructed client so it can be closed during process shutdown.
25
13
  # @param client [Consumer, Producer, Admin] client to close during process shutdown
@@ -28,21 +16,14 @@ module Rdkafka
28
16
  reset_after_fork
29
17
 
30
18
  @mutex.synchronize do
31
- if WEAK_TRACKING
32
- live_clients
33
-
34
- # The strongly-held token is the key and the client is the weak value, so registering a
35
- # client never keeps it alive. Enumerating weak keys can expose reclaimed slots, so we
36
- # look the value up by token instead.
37
- token = Object.new
38
- @clients[token] = client
39
- @tokens = (@tokens + [token]).freeze
40
- else
41
- # Strong references, so drop clients that have already been closed to bound growth.
42
- live = @clients.reject { |_id, existing| existing.closed? }
43
- live[client.object_id] = client
44
- @clients = live.freeze
45
- end
19
+ live_clients
20
+
21
+ # The strongly-held token is the key and the client is the weak value, so registering a
22
+ # client never keeps it alive. Enumerating weak keys can expose reclaimed slots, so we
23
+ # look the value up by token instead.
24
+ token = Object.new
25
+ @clients[token] = client
26
+ @tokens = (@tokens + [token]).freeze
46
27
  end
47
28
 
48
29
  client
@@ -54,7 +35,7 @@ module Rdkafka
54
35
  def self.close_all
55
36
  return unless @pid == Process.pid
56
37
 
57
- clients = @mutex.synchronize { current_clients }
38
+ clients = @mutex.synchronize { live_clients }
58
39
  first_error = nil
59
40
 
60
41
  clients.each do |client|
@@ -66,16 +47,7 @@ module Rdkafka
66
47
  raise first_error if first_error
67
48
  end
68
49
 
69
- # Returns the currently registered live clients.
70
- # @return [Array<Consumer, Producer, Admin>] live clients
71
- # @private
72
- def self.current_clients
73
- WEAK_TRACKING ? live_clients : @clients.values
74
- end
75
- private_class_method :current_clients
76
-
77
50
  # Returns the live clients and releases tokens whose clients have been collected.
78
- # Weak-tracking Rubies only.
79
51
  # @return [Array<Consumer, Producer, Admin>] live clients
80
52
  # @private
81
53
  def self.live_clients
@@ -99,13 +71,8 @@ module Rdkafka
99
71
  def self.reset_after_fork
100
72
  return if @pid == Process.pid
101
73
 
102
- if WEAK_TRACKING
103
- @clients = ObjectSpace::WeakMap.new
104
- @tokens = [].freeze
105
- else
106
- @clients = {}.freeze
107
- end
108
-
74
+ @clients = ObjectSpace::WeakMap.new
75
+ @tokens = [].freeze
109
76
  @pid = Process.pid
110
77
  @mutex = Mutex.new
111
78
  end
@@ -22,6 +22,12 @@ module Rdkafka
22
22
  @@log_mutex = Mutex.new
23
23
  # @!visibility private
24
24
  @@oauthbearer_token_refresh_callback = nil
25
+ # @!visibility private
26
+ # Whether the partition key length passed to the partitioner is the byte length (`bytesize`)
27
+ # rather than the character length (`size`). `false` (character length) is the legacy default
28
+ # to avoid changing partition assignment for existing multibyte/binary keys; set to `true` to
29
+ # opt into the correct, other-client-consistent byte-length hashing.
30
+ @@partitioner_key_uses_bytesize = false
25
31
 
26
32
  # Returns the current logger, by default this is a logger to stdout.
27
33
  #
@@ -122,6 +128,34 @@ module Rdkafka
122
128
  @@oauthbearer_token_refresh_callback
123
129
  end
124
130
 
131
+ # Whether the partitioner hashes the partition key by its byte length instead of its character
132
+ # length. Defaults to `false` (character length) to preserve existing partition assignment for
133
+ # multibyte/binary keys. Passing the byte length is the correct, other-client-consistent
134
+ # behavior; enable it (and be aware it can move such keys to a different partition) with
135
+ # `Rdkafka::Config.partitioner_key_uses_bytesize = true`. This default is expected to change in
136
+ # a future major version.
137
+ #
138
+ # @return [Boolean]
139
+ def self.partitioner_key_uses_bytesize
140
+ @@partitioner_key_uses_bytesize
141
+ end
142
+
143
+ # Sets whether the partitioner hashes the partition key by its byte length rather than its
144
+ # character length. The key length method is resolved here once rather than on every produced
145
+ # message, and each call re-resolves it, so changing the setting at runtime takes effect
146
+ # immediately.
147
+ #
148
+ # @param value [Boolean] true to hash by byte length, false (default) for character length
149
+ # @return [void]
150
+ def self.partitioner_key_uses_bytesize=(value)
151
+ @@partitioner_key_uses_bytesize = value
152
+
153
+ Rdkafka::Bindings.singleton_class.alias_method(
154
+ :partition_key_length,
155
+ value ? :partition_key_bytesize : :partition_key_size
156
+ )
157
+ end
158
+
125
159
  # @private
126
160
  def self.opaques
127
161
  @@opaques
@@ -35,19 +35,16 @@ module Rdkafka
35
35
  # @param native_message [Rdkafka::Bindings::Message] native message struct from librdkafka
36
36
  def initialize(native_message)
37
37
  # Set topic
38
- unless native_message[:rkt].null?
39
- @topic = Rdkafka::Bindings.rd_kafka_topic_name(native_message[:rkt])
40
- end
38
+ rkt = native_message[:rkt]
39
+ @topic = Rdkafka::Bindings.rd_kafka_topic_name(rkt) unless rkt.null?
41
40
  # Set partition
42
41
  @partition = native_message[:partition]
43
42
  # Set payload
44
- unless native_message[:payload].null?
45
- @payload = native_message[:payload].read_string(native_message[:len])
46
- end
43
+ payload = native_message[:payload]
44
+ @payload = payload.read_string(native_message[:len]) unless payload.null?
47
45
  # Set key
48
- unless native_message[:key].null?
49
- @key = native_message[:key].read_string(native_message[:key_len])
50
- end
46
+ key = native_message[:key]
47
+ @key = key.read_string(native_message[:key_len]) unless key.null?
51
48
  # Set offset
52
49
  @offset = native_message[:offset]
53
50
  # Set timestamp
@@ -7,7 +7,10 @@ module Rdkafka
7
7
  layout :pending, :bool,
8
8
  :response, :int,
9
9
  :partition, :int,
10
- :offset, :int64
10
+ :offset, :int64,
11
+ :status, :int,
12
+ :latency, :int64,
13
+ :broker_id, :int32
11
14
 
12
15
  # @return [Object, nil] label set during message production or nil by default
13
16
  attr_accessor :label
@@ -31,7 +34,10 @@ module Rdkafka
31
34
  # we should not return it
32
35
  topic,
33
36
  (self[:response] != 0) ? RdkafkaError.new(self[:response]) : nil,
34
- label
37
+ label,
38
+ status: self[:status],
39
+ latency: self[:latency],
40
+ broker_id: self[:broker_id]
35
41
  )
36
42
  end
37
43
  end
@@ -25,6 +25,26 @@ module Rdkafka
25
25
  # @return [Object, nil] label set during message production or nil by default
26
26
  attr_reader :label
27
27
 
28
+ # Persistence status of the message, one of `Bindings::RD_KAFKA_MSG_STATUS_*`:
29
+ # - `RD_KAFKA_MSG_STATUS_NOT_PERSISTED` - the message was not written to the broker. A retry
30
+ # risks reordering, but not a duplicate.
31
+ # - `RD_KAFKA_MSG_STATUS_POSSIBLY_PERSISTED` - the message may have been written (e.g. the
32
+ # request timed out before an ack). A retry risks reordering and a duplicate.
33
+ # - `RD_KAFKA_MSG_STATUS_PERSISTED` - the broker acknowledged the message. Trust this only
34
+ # with `acks=all`.
35
+ # `nil` when the report was not built from a delivery callback.
36
+ # @return [Integer, nil]
37
+ attr_reader :status
38
+
39
+ # Time from the `produce` call until the delivery report, in microseconds, or `nil` when
40
+ # not available
41
+ # @return [Integer, nil]
42
+ attr_reader :latency
43
+
44
+ # Id of the broker the message was produced to, or `nil` when not known
45
+ # @return [Integer, nil]
46
+ attr_reader :broker_id
47
+
28
48
  # We alias the `#topic_name` under `#topic` to make this consistent with `Consumer::Message`
29
49
  # where the topic name is under `#topic` method. That way we have a consistent name that
30
50
  # is present in both places
@@ -38,12 +58,42 @@ module Rdkafka
38
58
  # @param topic_name [String, nil] topic name
39
59
  # @param error [Integer, nil] error code if any
40
60
  # @param label [Object, nil] user-defined label
41
- def initialize(partition, offset, topic_name = nil, error = nil, label = nil)
61
+ # @param status [Integer, nil] `Bindings::RD_KAFKA_MSG_STATUS_*` persistence status
62
+ # @param latency [Integer, nil] produce latency in microseconds, negative when not available
63
+ # @param broker_id [Integer, nil] broker id, negative when not known
64
+ def initialize(
65
+ partition,
66
+ offset,
67
+ topic_name = nil,
68
+ error = nil,
69
+ label = nil,
70
+ status: nil,
71
+ latency: nil,
72
+ broker_id: nil
73
+ )
42
74
  @partition = partition
43
75
  @offset = offset
44
76
  @topic_name = topic_name
45
77
  @error = error
46
78
  @label = label
79
+ @status = status
80
+ @latency = (latency.nil? || latency.negative?) ? nil : latency
81
+ @broker_id = (broker_id.nil? || broker_id.negative?) ? nil : broker_id
82
+ end
83
+
84
+ # @return [Boolean] true when the broker acknowledged the message
85
+ def persisted?
86
+ status == Bindings::RD_KAFKA_MSG_STATUS_PERSISTED
87
+ end
88
+
89
+ # @return [Boolean] true when the message may have been written, so a retry may duplicate it
90
+ def possibly_persisted?
91
+ status == Bindings::RD_KAFKA_MSG_STATUS_POSSIBLY_PERSISTED
92
+ end
93
+
94
+ # @return [Boolean] true when the message was not written, so a retry does not duplicate it
95
+ def not_persisted?
96
+ status == Bindings::RD_KAFKA_MSG_STATUS_NOT_PERSISTED
47
97
  end
48
98
  end
49
99
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Rdkafka
4
4
  # Current rdkafka-ruby gem version
5
- VERSION = "0.29.2"
5
+ VERSION = "0.31.0"
6
6
  # Target librdkafka version to be used
7
- LIBRDKAFKA_VERSION = "2.14.2"
7
+ LIBRDKAFKA_VERSION = "2.15.1"
8
8
  # SHA256 hash of the librdkafka source tarball for verification
9
- LIBRDKAFKA_SOURCE_SHA256 = "d7eec9c31c817fa44402f679c252dfbf97e4c338a849a25c3579a31fd127beb8"
9
+ LIBRDKAFKA_SOURCE_SHA256 = "23c8575c7d1ced07246cb9cf200c11325b72201fd4134a02414ca869fbdd8ed3"
10
10
  end
data/lib/rdkafka.rb CHANGED
@@ -13,6 +13,10 @@ require "rdkafka/helpers/list_offsets"
13
13
  require "rdkafka/defaults"
14
14
  require "rdkafka/abstract_handle"
15
15
  require "rdkafka/admin"
16
+ require "rdkafka/admin/alter_consumer_group_offsets_handle"
17
+ require "rdkafka/admin/alter_consumer_group_offsets_report"
18
+ require "rdkafka/admin/delete_consumer_group_offsets_handle"
19
+ require "rdkafka/admin/delete_consumer_group_offsets_report"
16
20
  require "rdkafka/admin/create_topic_handle"
17
21
  require "rdkafka/admin/create_topic_report"
18
22
  require "rdkafka/admin/delete_groups_handle"
@@ -37,6 +41,13 @@ require "rdkafka/admin/list_offsets_handle"
37
41
  require "rdkafka/admin/list_offsets_report"
38
42
  require "rdkafka/admin/list_consumer_groups_handle"
39
43
  require "rdkafka/admin/list_consumer_groups_report"
44
+ require "rdkafka/admin/describe_result_parsing"
45
+ require "rdkafka/admin/describe_cluster_handle"
46
+ require "rdkafka/admin/describe_cluster_report"
47
+ require "rdkafka/admin/describe_topics_handle"
48
+ require "rdkafka/admin/describe_topics_report"
49
+ require "rdkafka/admin/describe_consumer_groups_handle"
50
+ require "rdkafka/admin/describe_consumer_groups_report"
40
51
  require "rdkafka/admin/acl_binding_result"
41
52
  require "rdkafka/admin/config_binding_result"
42
53
  require "rdkafka/admin/config_resource_binding_result"
@@ -46,6 +57,8 @@ require "rdkafka/callbacks/base_handler"
46
57
  require "rdkafka/callbacks/create_topic_handler"
47
58
  require "rdkafka/callbacks/delete_topic_handler"
48
59
  require "rdkafka/callbacks/create_partitions_handler"
60
+ require "rdkafka/callbacks/alter_consumer_group_offsets_handler"
61
+ require "rdkafka/callbacks/delete_consumer_group_offsets_handler"
49
62
  require "rdkafka/callbacks/delete_groups_handler"
50
63
  require "rdkafka/callbacks/delete_records_handler"
51
64
  require "rdkafka/callbacks/create_acl_handler"
@@ -55,6 +68,9 @@ require "rdkafka/callbacks/describe_configs_handler"
55
68
  require "rdkafka/callbacks/incremental_alter_configs_handler"
56
69
  require "rdkafka/callbacks/list_offsets_handler"
57
70
  require "rdkafka/callbacks/list_consumer_groups_handler"
71
+ require "rdkafka/callbacks/describe_cluster_handler"
72
+ require "rdkafka/callbacks/describe_topics_handler"
73
+ require "rdkafka/callbacks/describe_consumer_groups_handler"
58
74
  require "rdkafka/config"
59
75
  require "rdkafka/consumer"
60
76
  require "rdkafka/consumer/headers"