karafka-rdkafka 0.30.1-aarch64-linux-gnu → 0.31.0-aarch64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -27
  3. data/README.md +1 -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/karafka-rdkafka.gemspec +1 -1
  8. data/lib/rdkafka/admin/describe_cluster_handle.rb +31 -0
  9. data/lib/rdkafka/admin/describe_cluster_report.rb +48 -0
  10. data/lib/rdkafka/admin/describe_consumer_groups_handle.rb +31 -0
  11. data/lib/rdkafka/admin/describe_consumer_groups_report.rb +151 -0
  12. data/lib/rdkafka/admin/describe_result_parsing.rb +47 -0
  13. data/lib/rdkafka/admin/describe_topics_handle.rb +31 -0
  14. data/lib/rdkafka/admin/describe_topics_report.rb +118 -0
  15. data/lib/rdkafka/admin.rb +245 -0
  16. data/lib/rdkafka/bindings/admin/acls.rb +85 -0
  17. data/lib/rdkafka/bindings/admin/configs.rb +37 -0
  18. data/lib/rdkafka/bindings/admin/list_offsets.rb +26 -0
  19. data/lib/rdkafka/bindings/admin.rb +197 -0
  20. data/lib/rdkafka/bindings/callbacks.rb +148 -0
  21. data/lib/rdkafka/bindings/configuration.rb +50 -0
  22. data/lib/rdkafka/bindings/consumer.rb +83 -0
  23. data/lib/rdkafka/bindings/errors.rb +17 -0
  24. data/lib/rdkafka/bindings/handle.rb +16 -0
  25. data/lib/rdkafka/bindings/messages.rb +28 -0
  26. data/lib/rdkafka/bindings/metadata.rb +17 -0
  27. data/lib/rdkafka/bindings/polling.rb +17 -0
  28. data/lib/rdkafka/bindings/producer.rb +82 -0
  29. data/lib/rdkafka/bindings/share_consumer.rb +67 -0
  30. data/lib/rdkafka/bindings/structs.rb +62 -0
  31. data/lib/rdkafka/bindings.rb +13 -775
  32. data/lib/rdkafka/callbacks/describe_cluster_handler.rb +36 -0
  33. data/lib/rdkafka/callbacks/describe_consumer_groups_handler.rb +36 -0
  34. data/lib/rdkafka/callbacks/describe_topics_handler.rb +36 -0
  35. data/lib/rdkafka/callbacks.rb +10 -1
  36. data/lib/rdkafka/clients.rb +13 -46
  37. data/lib/rdkafka/consumer/message.rb +6 -9
  38. data/lib/rdkafka/defaults.rb +4 -0
  39. data/lib/rdkafka/producer/delivery_handle.rb +18 -16
  40. data/lib/rdkafka/producer/delivery_report.rb +51 -1
  41. data/lib/rdkafka/share_consumer.rb +54 -3
  42. data/lib/rdkafka/version.rb +3 -3
  43. data/lib/rdkafka.rb +10 -0
  44. data/package-lock.json +9 -9
  45. data/renovate.json +3 -0
  46. metadata +27 -2
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Bindings
5
+ LogCallback = FFI::Function.new(
6
+ :void, [:pointer, :int, :string, :string]
7
+ ) do |_client_ptr, level, _level_string, line|
8
+ severity = case level
9
+ when 0, 1, 2
10
+ Logger::FATAL
11
+ when 3
12
+ Logger::ERROR
13
+ when 4
14
+ Logger::WARN
15
+ when 5, 6
16
+ Logger::INFO
17
+ when 7
18
+ Logger::DEBUG
19
+ else
20
+ Logger::UNKNOWN
21
+ end
22
+
23
+ Rdkafka::Config.ensure_log_thread
24
+ Rdkafka::Config.log_queue << [severity, "rdkafka: #{line}"]
25
+ end
26
+
27
+ StatsCallback = FFI::Function.new(
28
+ :int, [:pointer, :string, :int, :pointer]
29
+ ) do |_client_ptr, json, _json_len, _opaque|
30
+ if Rdkafka::Config.statistics_callback
31
+ stats = JSON.parse(json)
32
+
33
+ # If user requested statistics callbacks, we can use the statistics data to get the
34
+ # partitions count for each topic when this data is published. That way we do not have
35
+ # to query this information when user is using `partition_key`. This takes around 0.02ms
36
+ # every statistics interval period (most likely every 5 seconds) and saves us from making
37
+ # any queries to the cluster for the partition count.
38
+ #
39
+ # One edge case is if user would set the `statistics.interval.ms` much higher than the
40
+ # default current partition count refresh (30 seconds). This is taken care of as the lack
41
+ # of reporting to the partitions cache will cause cache expire and blocking refresh.
42
+ #
43
+ # If user sets `topic.metadata.refresh.interval.ms` too high this is on the user.
44
+ #
45
+ # Since this cache is shared, having few consumers and/or producers in one process will
46
+ # automatically improve the querying times even with low refresh times.
47
+ (stats["topics"] || EMPTY_HASH).each do |topic_name, details|
48
+ partitions_count = details["partitions"].keys.count { |k| !(k == RD_KAFKA_PARTITION_UA_STR) }
49
+
50
+ next unless partitions_count.positive?
51
+
52
+ Rdkafka::Producer.partitions_count_cache.set(topic_name, partitions_count)
53
+ end
54
+
55
+ Rdkafka::Config.statistics_callback.call(stats)
56
+ end
57
+
58
+ # Return 0 so librdkafka frees the json string
59
+ RD_KAFKA_RESP_ERR_NO_ERROR
60
+ end
61
+
62
+ # Retrieves fatal error details from a kafka client handle.
63
+ # This is a helper method to extract fatal error information consistently
64
+ # across different parts of the codebase (callbacks, testing utilities, etc.).
65
+ #
66
+ # @param client_ptr [FFI::Pointer] Native kafka client pointer
67
+ # @return [Hash, nil] Hash with :error_code and :error_string if fatal error occurred,
68
+ # nil otherwise
69
+ #
70
+ # @example
71
+ # details = Rdkafka::Bindings.extract_fatal_error(client_ptr)
72
+ # if details
73
+ # puts "Fatal error #{details[:error_code]}: #{details[:error_string]}"
74
+ # end
75
+ def self.extract_fatal_error(client_ptr)
76
+ error_buffer = FFI::MemoryPointer.new(:char, FATAL_ERROR_BUFFER_SIZE)
77
+
78
+ error_code = rd_kafka_fatal_error(client_ptr, error_buffer, FATAL_ERROR_BUFFER_SIZE)
79
+
80
+ return nil if error_code == RD_KAFKA_RESP_ERR_NO_ERROR
81
+
82
+ {
83
+ error_code: error_code,
84
+ error_string: error_buffer.read_string
85
+ }
86
+ end
87
+
88
+ ErrorCallback = FFI::Function.new(
89
+ :void, [:pointer, :int, :string, :pointer]
90
+ ) do |client_ptr, err_code, reason, _opaque|
91
+ if Rdkafka::Config.error_callback
92
+ instance_name = client_ptr.null? ? nil : Rdkafka::Bindings.rd_kafka_name(client_ptr)
93
+
94
+ # Handle fatal errors according to librdkafka documentation:
95
+ # When ERR__FATAL is received, we must call rd_kafka_fatal_error()
96
+ # to get the actual underlying fatal error code and description.
97
+ error = if err_code == RD_KAFKA_RESP_ERR__FATAL
98
+ Rdkafka::RdkafkaError.build_fatal(
99
+ client_ptr,
100
+ fallback_error_code: err_code,
101
+ fallback_message: reason,
102
+ instance_name: instance_name
103
+ )
104
+ else
105
+ Rdkafka::RdkafkaError.build(err_code, broker_message: reason, instance_name: instance_name)
106
+ end
107
+
108
+ error.set_backtrace(caller)
109
+ Rdkafka::Config.error_callback.call(error)
110
+ end
111
+ end
112
+
113
+ # The OAuth callback is currently global and contextless. This means that the callback will be
114
+ # called for all instances, and the callback must be able to determine to which instance it is
115
+ # associated. The instance name will be provided in the callback, allowing the callback to
116
+ # reference the correct instance.
117
+ #
118
+ # An example of how to use the instance name in the callback is given below.
119
+ # The `refresh_token` is configured as the `oauthbearer_token_refresh_callback`.
120
+ # `instances` is a map of client names to client instances, maintained by the user.
121
+ #
122
+ # ```
123
+ # def refresh_token(config, client_name)
124
+ # client = instances[client_name]
125
+ # client.oauthbearer_set_token(
126
+ # token: 'new-token-value',
127
+ # lifetime_ms: token-lifetime-ms,
128
+ # principal_name: 'principal-name'
129
+ # )
130
+ # end
131
+ # ```
132
+ OAuthbearerTokenRefreshCallback = FFI::Function.new(
133
+ :void, [:pointer, :string, :pointer]
134
+ ) do |client_ptr, config, opaque_ptr|
135
+ if Rdkafka::Config.oauthbearer_token_refresh_callback && !client_ptr.null?
136
+ client_name = Rdkafka::Bindings.rd_kafka_name(client_ptr)
137
+
138
+ # Share consumers have no way to query their librdkafka client name (the share handle
139
+ # exposes no name accessor), so hand it over here - where librdkafka provides it -
140
+ # letting applications correlate this callback's client name with a ShareConsumer via
141
+ # ShareConsumer#name.
142
+ Rdkafka::Config.opaques[opaque_ptr.to_i]&.capture_client_name(client_name)
143
+
144
+ Rdkafka::Config.oauthbearer_token_refresh_callback.call(config, client_name)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Bindings
5
+ # Configuration
6
+
7
+ enum :kafka_config_response, [
8
+ :config_unknown, -2,
9
+ :config_invalid, -1,
10
+ :config_ok, 0
11
+ ]
12
+
13
+ attach_function :rd_kafka_conf_new, [], :pointer
14
+ attach_function :rd_kafka_conf_set, [:pointer, :string, :string, :pointer, :int], :kafka_config_response
15
+ attach_function :rd_kafka_conf_get, [:pointer, :string, :pointer, :pointer], :kafka_config_response
16
+ attach_function :rd_kafka_conf, [:pointer], :pointer
17
+ attach_function :rd_kafka_conf_dump, [:pointer, :pointer], :pointer
18
+ attach_function :rd_kafka_conf_dump_free, [:pointer, :size_t], :void
19
+ attach_function :rd_kafka_conf_destroy, [:pointer], :void
20
+ callback :log_cb, [:pointer, :int, :string, :string], :void
21
+ attach_function :rd_kafka_conf_set_log_cb, [:pointer, :log_cb], :void
22
+ attach_function :rd_kafka_conf_set_opaque, [:pointer, :pointer], :void
23
+ callback :stats_cb, [:pointer, :string, :int, :pointer], :int
24
+ attach_function :rd_kafka_conf_set_stats_cb, [:pointer, :stats_cb], :void
25
+ callback :error_cb, [:pointer, :int, :string, :pointer], :void
26
+ attach_function :rd_kafka_conf_set_error_cb, [:pointer, :error_cb], :void
27
+ attach_function :rd_kafka_rebalance_protocol, [:pointer], :string
28
+ callback :oauthbearer_token_refresh_cb, [:pointer, :string, :pointer], :void
29
+ attach_function :rd_kafka_conf_set_oauthbearer_token_refresh_cb, [:pointer, :oauthbearer_token_refresh_cb], :void
30
+ attach_function :rd_kafka_oauthbearer_set_token, [:pointer, :string, :int64, :pointer, :pointer, :int, :pointer, :int], :int
31
+ attach_function :rd_kafka_oauthbearer_set_token_failure, [:pointer, :string], :int
32
+ # Log queue
33
+ attach_function :rd_kafka_set_log_queue, [:pointer, :pointer], :void
34
+ attach_function :rd_kafka_queue_get_main, [:pointer], :pointer
35
+ attach_function :rd_kafka_queue_get_background, [:pointer], :pointer
36
+
37
+ # Queue IO Event Support - for fiber scheduler integration
38
+ # Enables notifications to a custom FD when queue transitions from empty to non-empty
39
+ # Arguments:
40
+ # - queue (rd_kafka_queue_t*) - the queue to monitor
41
+ # - fd (int) - file descriptor to write to (provide your own pipe/eventfd)
42
+ # - payload (const void*) - data to write to fd
43
+ # - size (size_t) - size of payload
44
+ attach_function :rd_kafka_queue_io_event_enable, [:pointer, :int, :pointer, :size_t], :void
45
+ # Per topic configs
46
+ attach_function :rd_kafka_topic_conf_new, [], :pointer
47
+ attach_function :rd_kafka_topic_conf_destroy, [:pointer], :void
48
+ attach_function :rd_kafka_topic_conf_set, [:pointer, :string, :string, :pointer, :int], :kafka_config_response
49
+ end
50
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Bindings
5
+ # Consumer
6
+
7
+ attach_function :rd_kafka_subscribe, [:pointer, :pointer], :int, blocking: true
8
+ attach_function :rd_kafka_unsubscribe, [:pointer], :int, blocking: true
9
+ attach_function :rd_kafka_subscription, [:pointer, :pointer], :int, blocking: true
10
+ attach_function :rd_kafka_assign, [:pointer, :pointer], :int, blocking: true
11
+ attach_function :rd_kafka_incremental_assign, [:pointer, :pointer], :int, blocking: true
12
+ attach_function :rd_kafka_incremental_unassign, [:pointer, :pointer], :int, blocking: true
13
+ attach_function :rd_kafka_assignment, [:pointer, :pointer], :int, blocking: true
14
+ attach_function :rd_kafka_assignment_lost, [:pointer], :int, blocking: true
15
+ attach_function :rd_kafka_committed, [:pointer, :pointer, :int], :int, blocking: true
16
+ attach_function :rd_kafka_commit, [:pointer, :pointer, :bool], :int, blocking: true
17
+ attach_function :rd_kafka_poll_set_consumer, [:pointer], :void, blocking: true
18
+ attach_function :rd_kafka_consumer_poll, [:pointer, :int], :pointer, blocking: true
19
+ # Non-blocking consumer poll variant (does not release GVL)
20
+ # More efficient for poll(0) calls in fiber schedulers.
21
+ attach_function :rd_kafka_consumer_poll_nb, :rd_kafka_consumer_poll, [:pointer, :int], :pointer, blocking: false
22
+ attach_function :rd_kafka_consumer_close, [:pointer], :void, blocking: true
23
+ attach_function :rd_kafka_queue_get_consumer, [:pointer], :pointer
24
+ attach_function :rd_kafka_consume_batch_queue, [:pointer, :int, :pointer, :size_t], :ssize_t, blocking: true
25
+ attach_function :rd_kafka_consume_batch_queue_nb, :rd_kafka_consume_batch_queue, [:pointer, :int, :pointer, :size_t], :ssize_t, blocking: false
26
+ attach_function :rd_kafka_offsets_store, [:pointer, :pointer], :int, blocking: true
27
+ attach_function :rd_kafka_pause_partitions, [:pointer, :pointer], :int, blocking: true
28
+ attach_function :rd_kafka_resume_partitions, [:pointer, :pointer], :int, blocking: true
29
+ attach_function :rd_kafka_seek, [:pointer, :int32, :int64, :int], :int, blocking: true
30
+ attach_function :rd_kafka_offsets_for_times, [:pointer, :pointer, :int], :int, blocking: true
31
+ attach_function :rd_kafka_position, [:pointer, :pointer], :int, blocking: true
32
+ # those two are used for eos support
33
+ attach_function :rd_kafka_consumer_group_metadata, [:pointer], :pointer, blocking: true
34
+ attach_function :rd_kafka_consumer_group_metadata_destroy, [:pointer], :void, blocking: true
35
+
36
+ # Headers
37
+ attach_function :rd_kafka_header_get_all, [:pointer, :size_t, :pointer, :pointer, SizePtr], :int
38
+ attach_function :rd_kafka_message_headers, [:pointer, :pointer], :int
39
+
40
+ # Rebalance
41
+
42
+ callback :rebalance_cb_function, [:pointer, :int, :pointer, :pointer], :void
43
+ attach_function :rd_kafka_conf_set_rebalance_cb, [:pointer, :rebalance_cb_function], :void, blocking: true
44
+
45
+ RebalanceCallback = FFI::Function.new(
46
+ :void, [:pointer, :int, :pointer, :pointer]
47
+ ) do |client_ptr, code, partitions_ptr, opaque_ptr|
48
+ case code
49
+ when RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS
50
+ if Rdkafka::Bindings.rd_kafka_rebalance_protocol(client_ptr) == "COOPERATIVE"
51
+ Rdkafka::Bindings.rd_kafka_incremental_assign(client_ptr, partitions_ptr)
52
+ else
53
+ Rdkafka::Bindings.rd_kafka_assign(client_ptr, partitions_ptr)
54
+ end
55
+ else # RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS or errors
56
+ if Rdkafka::Bindings.rd_kafka_rebalance_protocol(client_ptr) == "COOPERATIVE"
57
+ Rdkafka::Bindings.rd_kafka_incremental_unassign(client_ptr, partitions_ptr)
58
+ else
59
+ Rdkafka::Bindings.rd_kafka_assign(client_ptr, FFI::Pointer::NULL)
60
+ end
61
+ end
62
+
63
+ opaque = Rdkafka::Config.opaques[opaque_ptr.to_i]
64
+ return unless opaque
65
+
66
+ tpl = Rdkafka::Consumer::TopicPartitionList.from_native_tpl(partitions_ptr).freeze
67
+ begin
68
+ case code
69
+ when RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS
70
+ opaque.call_on_partitions_assigned(tpl)
71
+ when RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS
72
+ opaque.call_on_partitions_revoked(tpl)
73
+ end
74
+ rescue Exception => err
75
+ Rdkafka::Config.logger.error("Unhandled exception: #{err.class} - #{err.message}")
76
+ end
77
+ end
78
+
79
+ # Watermark offsets
80
+
81
+ attach_function :rd_kafka_query_watermark_offsets, [:pointer, :string, :int, :pointer, :pointer, :int], :int, blocking: true
82
+ end
83
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Bindings
5
+ attach_function :rd_kafka_err2name, [:int], :string
6
+ attach_function :rd_kafka_err2str, [:int], :string
7
+ attach_function :rd_kafka_error_is_fatal, [:pointer], :int
8
+ attach_function :rd_kafka_error_is_retriable, [:pointer], :int
9
+ attach_function :rd_kafka_error_txn_requires_abort, [:pointer], :int
10
+ attach_function :rd_kafka_error_destroy, [:pointer], :void
11
+ attach_function :rd_kafka_error_code, [:pointer], :int32
12
+ attach_function :rd_kafka_error_string, [:pointer], :pointer
13
+ attach_function :rd_kafka_get_err_descs, [:pointer, :pointer], :void
14
+ attach_function :rd_kafka_fatal_error, [:pointer, :pointer, :int], :int
15
+ attach_function :rd_kafka_test_fatal_error, [:pointer, :int, :string], :int
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Bindings
5
+ # Handle
6
+
7
+ enum :kafka_type, [
8
+ :rd_kafka_producer,
9
+ :rd_kafka_consumer
10
+ ]
11
+
12
+ attach_function :rd_kafka_new, [:kafka_type, :pointer, :pointer, :int], :pointer
13
+
14
+ attach_function :rd_kafka_destroy, [:pointer], :void
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Bindings
5
+ # Messages, topics and topic partition lists
6
+
7
+ attach_function :rd_kafka_message_destroy, [:pointer], :void
8
+ attach_function :rd_kafka_message_timestamp, [:pointer, :pointer], :int64
9
+ attach_function :rd_kafka_message_latency, [:pointer], :int64
10
+ attach_function :rd_kafka_message_broker_id, [:pointer], :int32
11
+ attach_function :rd_kafka_message_status, [:pointer], :int
12
+
13
+ # Message persistence status (rd_kafka_msg_status_t)
14
+ RD_KAFKA_MSG_STATUS_NOT_PERSISTED = 0
15
+ RD_KAFKA_MSG_STATUS_POSSIBLY_PERSISTED = 1
16
+ RD_KAFKA_MSG_STATUS_PERSISTED = 2
17
+
18
+ attach_function :rd_kafka_topic_new, [:pointer, :string, :pointer], :pointer
19
+ attach_function :rd_kafka_topic_destroy, [:pointer], :pointer
20
+ attach_function :rd_kafka_topic_name, [:pointer], :string
21
+
22
+ attach_function :rd_kafka_topic_partition_list_new, [:int32], :pointer
23
+ attach_function :rd_kafka_topic_partition_list_add, [:pointer, :string, :int32], :pointer
24
+ attach_function :rd_kafka_topic_partition_list_set_offset, [:pointer, :string, :int32, :int64], :void
25
+ attach_function :rd_kafka_topic_partition_list_destroy, [:pointer], :void
26
+ attach_function :rd_kafka_topic_partition_list_copy, [:pointer], :pointer
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Bindings
5
+ # Metadata
6
+
7
+ attach_function :rd_kafka_name, [:pointer], :string
8
+ # Both return a newly allocated string the caller must release with rd_kafka_mem_free; they
9
+ # are attached as :pointer (not :string) so we can free them instead of leaking. clusterid
10
+ # also takes a timeout_ms (the previous arity was wrong, leaving that argument as garbage).
11
+ attach_function :rd_kafka_memberid, [:pointer], :pointer, blocking: true
12
+ attach_function :rd_kafka_clusterid, [:pointer, :int], :pointer, blocking: true
13
+ attach_function :rd_kafka_mem_free, [:pointer, :pointer], :void
14
+ attach_function :rd_kafka_metadata, [:pointer, :int, :pointer, :pointer, :int], :int, blocking: true
15
+ attach_function :rd_kafka_metadata_destroy, [:pointer], :void, blocking: true
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Bindings
5
+ # Polling
6
+
7
+ attach_function :rd_kafka_flush, [:pointer, :int], :int, blocking: true
8
+ attach_function :rd_kafka_poll, [:pointer, :int], :int, blocking: true
9
+ attach_function :rd_kafka_outq_len, [:pointer], :int, blocking: true
10
+
11
+ # Non-blocking poll variants (do not release GVL)
12
+ # These are more efficient for poll(0) calls in fiber schedulers where GVL
13
+ # release/reacquire overhead is wasteful since we don't expect to wait.
14
+ # Uses the same underlying C function but with blocking: false to skip GVL release.
15
+ attach_function :rd_kafka_poll_nb, :rd_kafka_poll, [:pointer, :int], :int, blocking: false
16
+ end
17
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Bindings
5
+ # Producer
6
+ RD_KAFKA_VTYPE_END = 0
7
+ RD_KAFKA_VTYPE_TOPIC = 1
8
+ RD_KAFKA_VTYPE_RKT = 2
9
+ RD_KAFKA_VTYPE_PARTITION = 3
10
+ RD_KAFKA_VTYPE_VALUE = 4
11
+ RD_KAFKA_VTYPE_KEY = 5
12
+ RD_KAFKA_VTYPE_OPAQUE = 6
13
+ RD_KAFKA_VTYPE_MSGFLAGS = 7
14
+ RD_KAFKA_VTYPE_TIMESTAMP = 8
15
+ RD_KAFKA_VTYPE_HEADER = 9
16
+ RD_KAFKA_VTYPE_HEADERS = 10
17
+ RD_KAFKA_PURGE_F_QUEUE = 1
18
+ RD_KAFKA_PURGE_F_INFLIGHT = 2
19
+
20
+ RD_KAFKA_MSG_F_COPY = 0x2
21
+
22
+ attach_function :rd_kafka_producev, [:pointer, :varargs], :int, blocking: true
23
+ attach_function :rd_kafka_purge, [:pointer, :int], :int, blocking: true
24
+ callback :delivery_cb, [:pointer, :pointer, :pointer], :void
25
+ attach_function :rd_kafka_conf_set_dr_msg_cb, [:pointer, :delivery_cb], :void
26
+ attach_function :rd_kafka_init_transactions, [:pointer, :int], :pointer, blocking: true
27
+ attach_function :rd_kafka_send_offsets_to_transaction, [:pointer, :pointer, :pointer, :int], :pointer, blocking: true
28
+ attach_function :rd_kafka_begin_transaction, [:pointer], :pointer, blocking: true
29
+ attach_function :rd_kafka_abort_transaction, [:pointer, :int], :pointer, blocking: true
30
+ attach_function :rd_kafka_commit_transaction, [:pointer, :int], :pointer, blocking: true
31
+
32
+ # Hash mapping partitioner names to their FFI function symbols
33
+ # @return [Hash{String => Symbol}]
34
+ PARTITIONERS = %w[random consistent consistent_random murmur2 murmur2_random fnv1a fnv1a_random].each_with_object({}) do |name, hsh|
35
+ method_name = :"rd_kafka_msg_partitioner_#{name}"
36
+ attach_function method_name, [:pointer, :pointer, :size_t, :int32, :pointer, :pointer], :int32
37
+ hsh[name] = method_name
38
+ end
39
+
40
+ # Calculates the partition for a message based on the partitioner
41
+ #
42
+ # @param topic_ptr [FFI::Pointer] pointer to the topic handle
43
+ # @param str [String] the partition key string
44
+ # @param partition_count [Integer, nil] number of partitions
45
+ # @param partitioner [String] name of the partitioner to use
46
+ # @return [Integer] partition number or RD_KAFKA_PARTITION_UA if unassigned
47
+ # @raise [Rdkafka::Config::ConfigError] when an unknown partitioner is specified
48
+ def self.partitioner(topic_ptr, str, partition_count, partitioner = "consistent_random")
49
+ # Return RD_KAFKA_PARTITION_UA(unassigned partition) when partition count is nil/zero.
50
+ return RD_KAFKA_PARTITION_UA unless partition_count&.nonzero?
51
+
52
+ str_ptr = str.empty? ? FFI::MemoryPointer::NULL : FFI::MemoryPointer.from_string(str)
53
+ method_name = PARTITIONERS.fetch(partitioner) do
54
+ raise Rdkafka::Config::ConfigError.new("Unknown partitioner: #{partitioner}")
55
+ end
56
+
57
+ public_send(method_name, topic_ptr, str_ptr, partition_key_length(str), partition_count, nil, nil)
58
+ end
59
+
60
+ # Partition key length as the character count (legacy default).
61
+ #
62
+ # @param str [String] the partition key string
63
+ # @return [Integer]
64
+ def self.partition_key_size(str)
65
+ str.size
66
+ end
67
+
68
+ # Partition key length as the byte count, which is what librdkafka hashes (UTF-8 bytes copied
69
+ # into the key pointer) and what other Kafka clients use.
70
+ #
71
+ # @param str [String] the partition key string
72
+ # @return [Integer]
73
+ def self.partition_key_bytesize(str)
74
+ str.bytesize
75
+ end
76
+
77
+ # Length of the partition key passed to librdkafka. Aliased to one of the methods above by
78
+ # `Rdkafka::Config.partitioner_key_uses_bytesize=`, so the per-message path does not check the
79
+ # setting on every call.
80
+ singleton_class.alias_method :partition_key_length, :partition_key_size
81
+ end
82
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Bindings
5
+ # Share consumer (KIP-932, preview)
6
+ #
7
+ # The share consumer is a distinct librdkafka handle type (rd_kafka_share_t) created with
8
+ # rd_kafka_share_consumer_new instead of rd_kafka_new. It is single-threaded by design and
9
+ # librdkafka itself rejects concurrent access with RD_KAFKA_RESP_ERR__CONFLICT.
10
+
11
+ # Minimal mirror of the head of librdkafka's `struct rd_kafka_share_s`, whose first member is
12
+ # the wrapped `rd_kafka_t` (`rkshare_rk`). `rd_kafka_share_t` has no public accessors of its
13
+ # own - not even for the client name - but the handle it wraps is a normal `rd_kafka_t` and is
14
+ # the very handle librdkafka passes to the statistics, error and OAuthBearer callbacks. Reading
15
+ # it lets a share consumer report a name that matches its statistics `name` field, which
16
+ # `rd_kafka_name` cannot do when handed the `rd_kafka_share_t` directly (it would read an
17
+ # unrelated field and return garbage). This mirrors how the gem already maps other librdkafka
18
+ # structs by layout for the pinned librdkafka version.
19
+ class NativeShareConsumer < FFI::Struct
20
+ layout :rkshare_rk, :pointer
21
+ end
22
+
23
+ # Acknowledge types for share consumer records
24
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT = 1
25
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_RELEASE = 2
26
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_REJECT = 3
27
+
28
+ attach_function :rd_kafka_share_consumer_new, [:pointer, :pointer, :size_t], :pointer
29
+ attach_function :rd_kafka_share_consumer_close, [:pointer], :pointer, blocking: true
30
+ attach_function :rd_kafka_share_consumer_closed, [:pointer], :int
31
+ attach_function :rd_kafka_share_destroy, [:pointer], :pointer, blocking: true
32
+ attach_function :rd_kafka_share_set_log_queue, [:pointer, :pointer], :pointer
33
+
34
+ attach_function :rd_kafka_share_subscribe, [:pointer, :pointer], :int, blocking: true
35
+ attach_function :rd_kafka_share_unsubscribe, [:pointer], :int, blocking: true
36
+ attach_function :rd_kafka_share_subscription, [:pointer, :pointer], :int, blocking: true
37
+
38
+ attach_function :rd_kafka_share_poll, [:pointer, :int, :pointer], :pointer, blocking: true
39
+ attach_function :rd_kafka_messages_count, [:pointer], :size_t
40
+ attach_function :rd_kafka_messages_get, [:pointer, :size_t], :pointer
41
+ attach_function :rd_kafka_messages_destroy, [:pointer], :void
42
+ attach_function :rd_kafka_message_delivery_count, [:pointer], :int16
43
+
44
+ # The by-coordinates acknowledge variant is the only one we bind: Ruby messages are copies
45
+ # that do not retain the native rd_kafka_message_t pointer, so the message-pointer variants
46
+ # (rd_kafka_share_acknowledge / rd_kafka_share_acknowledge_type) cannot be used safely here.
47
+ attach_function :rd_kafka_share_acknowledge_offset, [:pointer, :string, :int32, :int64, :int], :int
48
+
49
+ attach_function :rd_kafka_share_commit_sync, [:pointer, :int, :pointer], :pointer, blocking: true
50
+ attach_function :rd_kafka_share_commit_async, [:pointer], :pointer
51
+
52
+ callback :share_acknowledgement_commit_cb, [:pointer, :pointer, :int, :pointer], :void
53
+ attach_function :rd_kafka_share_set_acknowledgement_commit_cb, [:pointer, :share_acknowledgement_commit_cb, :pointer], :pointer
54
+
55
+ # Accessors for the rd_kafka_share_partition_offsets_list_t handed to the acknowledgement
56
+ # commit callback. The list is owned by librdkafka for the duration of the callback: read it
57
+ # inside the callback only, never destroy or retain it.
58
+ attach_function :rd_kafka_share_partition_offsets_list_count, [:pointer], :size_t
59
+ attach_function :rd_kafka_share_partition_offsets_list_get, [:pointer, :size_t], :pointer
60
+ attach_function :rd_kafka_share_partition_offsets_partition, [:pointer], :pointer
61
+ attach_function :rd_kafka_share_partition_offsets_offsets, [:pointer], :pointer
62
+ attach_function :rd_kafka_share_partition_offsets_offsets_cnt, [:pointer], :size_t
63
+
64
+ attach_function :rd_kafka_share_oauthbearer_set_token, [:pointer, :string, :int64, :string, :pointer, :size_t, :pointer, :size_t], :int
65
+ attach_function :rd_kafka_share_oauthbearer_set_token_failure, [:pointer, :string], :int
66
+ end
67
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Bindings
5
+ # FFI struct representing a Kafka message (rd_kafka_message_t)
6
+ class Message < FFI::Struct
7
+ layout :err, :int,
8
+ :rkt, :pointer,
9
+ :partition, :int32,
10
+ :payload, :pointer,
11
+ :len, :size_t,
12
+ :key, :pointer,
13
+ :key_len, :size_t,
14
+ :offset, :int64,
15
+ :_private, :pointer
16
+ end
17
+
18
+ # FFI struct representing a topic partition (rd_kafka_topic_partition_t)
19
+ class TopicPartition < FFI::Struct
20
+ layout :topic, :string,
21
+ :partition, :int32,
22
+ :offset, :int64,
23
+ :metadata, :pointer,
24
+ :metadata_size, :size_t,
25
+ :opaque, :pointer,
26
+ :err, :int,
27
+ :_private, :pointer
28
+ end
29
+
30
+ # FFI struct representing a topic partition list (rd_kafka_topic_partition_list_t)
31
+ class TopicPartitionList < FFI::Struct
32
+ layout :cnt, :int,
33
+ :size, :int,
34
+ :elems, :pointer
35
+ end
36
+
37
+ # FFI struct representing a config resource (rd_kafka_ConfigResource_t)
38
+ # Structs for management of configurations. Each configuration is attached to a resource
39
+ # and one resource can have many configuration details. Each resource will also have
40
+ # separate errors results if obtaining configuration was not possible for any reason
41
+ class ConfigResource < FFI::Struct
42
+ layout :type, :int,
43
+ :name, :string
44
+ end
45
+
46
+ # FFI struct for error description (rd_kafka_err_desc)
47
+ class NativeErrorDesc < FFI::Struct
48
+ layout :code, :int,
49
+ :name, :pointer,
50
+ :desc, :pointer
51
+ end
52
+
53
+ # FFI struct for native error (rd_kafka_error_t)
54
+ class NativeError < FFI::Struct
55
+ layout :code, :int32,
56
+ :errstr, :pointer,
57
+ :fatal, :u_int8_t,
58
+ :retriable, :u_int8_t,
59
+ :txn_requires_abort, :u_int8_t
60
+ end
61
+ end
62
+ end