rdkafka 0.22.2 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +101 -3
- data/README.md +21 -14
- data/Rakefile +21 -21
- data/bin/verify_kafka_warnings +39 -0
- data/dist/{librdkafka-2.8.0.tar.gz → librdkafka-2.14.2.tar.gz} +0 -0
- data/docker-compose-ssl.yml +35 -0
- data/docker-compose.yml +2 -2
- data/ext/Rakefile +27 -27
- data/lib/rdkafka/abstract_handle.rb +54 -7
- data/lib/rdkafka/admin/acl_binding_result.rb +5 -5
- data/lib/rdkafka/admin/config_binding_result.rb +2 -2
- data/lib/rdkafka/admin/config_resource_binding_result.rb +1 -0
- data/lib/rdkafka/admin/create_acl_handle.rb +10 -6
- data/lib/rdkafka/admin/create_acl_report.rb +3 -2
- data/lib/rdkafka/admin/create_partitions_handle.rb +8 -7
- data/lib/rdkafka/admin/create_partitions_report.rb +1 -0
- data/lib/rdkafka/admin/create_topic_handle.rb +8 -7
- data/lib/rdkafka/admin/create_topic_report.rb +3 -0
- data/lib/rdkafka/admin/delete_acl_handle.rb +9 -8
- data/lib/rdkafka/admin/delete_acl_report.rb +5 -3
- data/lib/rdkafka/admin/delete_groups_handle.rb +10 -7
- data/lib/rdkafka/admin/delete_groups_report.rb +3 -0
- data/lib/rdkafka/admin/delete_topic_handle.rb +8 -7
- data/lib/rdkafka/admin/delete_topic_report.rb +3 -0
- data/lib/rdkafka/admin/describe_acl_handle.rb +9 -8
- data/lib/rdkafka/admin/describe_acl_report.rb +5 -3
- data/lib/rdkafka/admin/describe_configs_handle.rb +7 -10
- data/lib/rdkafka/admin/describe_configs_report.rb +8 -6
- data/lib/rdkafka/admin/incremental_alter_configs_handle.rb +7 -10
- data/lib/rdkafka/admin/incremental_alter_configs_report.rb +8 -6
- data/lib/rdkafka/admin/list_offsets_handle.rb +31 -0
- data/lib/rdkafka/admin/list_offsets_report.rb +51 -0
- data/lib/rdkafka/admin.rb +357 -159
- data/lib/rdkafka/bindings.rb +205 -111
- data/lib/rdkafka/callbacks/base_handler.rb +62 -0
- data/lib/rdkafka/callbacks/create_acl_handler.rb +37 -0
- data/lib/rdkafka/callbacks/create_partitions_handler.rb +37 -0
- data/lib/rdkafka/callbacks/create_topic_handler.rb +37 -0
- data/lib/rdkafka/callbacks/delete_acl_handler.rb +42 -0
- data/lib/rdkafka/callbacks/delete_groups_handler.rb +37 -0
- data/lib/rdkafka/callbacks/delete_topic_handler.rb +37 -0
- data/lib/rdkafka/callbacks/describe_acl_handler.rb +35 -0
- data/lib/rdkafka/callbacks/describe_configs_handler.rb +42 -0
- data/lib/rdkafka/callbacks/incremental_alter_configs_handler.rb +42 -0
- data/lib/rdkafka/callbacks/list_offsets_handler.rb +42 -0
- data/lib/rdkafka/callbacks.rb +125 -216
- data/lib/rdkafka/config.rb +126 -66
- data/lib/rdkafka/consumer/headers.rb +22 -7
- data/lib/rdkafka/consumer/message.rb +12 -11
- data/lib/rdkafka/consumer/partition.rb +15 -4
- data/lib/rdkafka/consumer/topic_partition_list.rb +64 -38
- data/lib/rdkafka/consumer.rb +513 -59
- data/lib/rdkafka/defaults.rb +125 -0
- data/lib/rdkafka/error.rb +40 -14
- data/lib/rdkafka/helpers/metadata.rb +29 -0
- data/lib/rdkafka/helpers/oauth.rb +45 -13
- data/lib/rdkafka/helpers/time.rb +5 -0
- data/lib/rdkafka/metadata.rb +128 -37
- data/lib/rdkafka/native_kafka.rb +91 -8
- data/lib/rdkafka/producer/delivery_handle.rb +6 -6
- data/lib/rdkafka/producer/delivery_report.rb +10 -6
- data/lib/rdkafka/producer/partitions_count_cache.rb +51 -55
- data/lib/rdkafka/producer.rb +193 -95
- data/lib/rdkafka/version.rb +6 -3
- data/lib/rdkafka.rb +15 -0
- data/package-lock.json +331 -0
- data/package.json +9 -0
- data/rdkafka.gemspec +58 -36
- data/renovate.json +39 -23
- metadata +39 -125
- data/.github/CODEOWNERS +0 -3
- data/.github/FUNDING.yml +0 -1
- data/.github/workflows/ci_linux_x86_64_gnu.yml +0 -271
- data/.github/workflows/ci_linux_x86_64_musl.yml +0 -194
- data/.github/workflows/ci_macos_arm64.yml +0 -284
- data/.github/workflows/push_linux_x86_64_gnu.yml +0 -65
- data/.github/workflows/push_linux_x86_64_musl.yml +0 -79
- data/.github/workflows/push_macos_arm64.yml +0 -54
- data/.github/workflows/push_ruby.yml +0 -37
- data/.github/workflows/verify-action-pins.yml +0 -16
- data/.gitignore +0 -14
- data/.rspec +0 -2
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.yardopts +0 -2
- data/Gemfile +0 -5
- data/ext/README.md +0 -19
- data/ext/build_common.sh +0 -361
- data/ext/build_linux_x86_64_gnu.sh +0 -306
- data/ext/build_linux_x86_64_musl.sh +0 -763
- data/ext/build_macos_arm64.sh +0 -550
- data/spec/rdkafka/abstract_handle_spec.rb +0 -117
- data/spec/rdkafka/admin/create_acl_handle_spec.rb +0 -56
- data/spec/rdkafka/admin/create_acl_report_spec.rb +0 -18
- data/spec/rdkafka/admin/create_topic_handle_spec.rb +0 -52
- data/spec/rdkafka/admin/create_topic_report_spec.rb +0 -16
- data/spec/rdkafka/admin/delete_acl_handle_spec.rb +0 -85
- data/spec/rdkafka/admin/delete_acl_report_spec.rb +0 -72
- data/spec/rdkafka/admin/delete_topic_handle_spec.rb +0 -52
- data/spec/rdkafka/admin/delete_topic_report_spec.rb +0 -16
- data/spec/rdkafka/admin/describe_acl_handle_spec.rb +0 -85
- data/spec/rdkafka/admin/describe_acl_report_spec.rb +0 -73
- data/spec/rdkafka/admin_spec.rb +0 -971
- data/spec/rdkafka/bindings_spec.rb +0 -199
- data/spec/rdkafka/callbacks_spec.rb +0 -20
- data/spec/rdkafka/config_spec.rb +0 -258
- data/spec/rdkafka/consumer/headers_spec.rb +0 -73
- data/spec/rdkafka/consumer/message_spec.rb +0 -139
- data/spec/rdkafka/consumer/partition_spec.rb +0 -57
- data/spec/rdkafka/consumer/topic_partition_list_spec.rb +0 -248
- data/spec/rdkafka/consumer_spec.rb +0 -1274
- data/spec/rdkafka/error_spec.rb +0 -89
- data/spec/rdkafka/metadata_spec.rb +0 -79
- data/spec/rdkafka/native_kafka_spec.rb +0 -130
- data/spec/rdkafka/producer/delivery_handle_spec.rb +0 -45
- data/spec/rdkafka/producer/delivery_report_spec.rb +0 -25
- data/spec/rdkafka/producer/partitions_count_cache_spec.rb +0 -359
- data/spec/rdkafka/producer_spec.rb +0 -1345
- data/spec/spec_helper.rb +0 -195
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
# Default timeout and timing values used throughout rdkafka-ruby.
|
|
5
|
+
#
|
|
6
|
+
# All timeout values can be overridden per-call via method parameters.
|
|
7
|
+
# These constants provide a central place to understand and reference
|
|
8
|
+
# the default values used across the library.
|
|
9
|
+
#
|
|
10
|
+
# @note These are rdkafka-ruby defaults, not librdkafka configuration options.
|
|
11
|
+
# For librdkafka options, see:
|
|
12
|
+
# https://github.com/confluentinc/librdkafka/blob/master/CONFIGURATION.md
|
|
13
|
+
#
|
|
14
|
+
# @example Overriding a timeout per-call
|
|
15
|
+
# consumer.committed(timeout_ms: 5_000) # Use 5 seconds instead of default 2 seconds
|
|
16
|
+
#
|
|
17
|
+
# @example Checking the default value
|
|
18
|
+
# Rdkafka::Defaults::CONSUMER_COMMITTED_TIMEOUT_MS # => 2000
|
|
19
|
+
module Defaults
|
|
20
|
+
# Consumer timeouts (in milliseconds)
|
|
21
|
+
|
|
22
|
+
# Default timeout for fetching committed offsets
|
|
23
|
+
# @see Consumer#committed
|
|
24
|
+
CONSUMER_COMMITTED_TIMEOUT_MS = 2_000
|
|
25
|
+
|
|
26
|
+
# Default timeout for fetching the cluster id
|
|
27
|
+
# @see Consumer#cluster_id
|
|
28
|
+
CONSUMER_CLUSTER_ID_TIMEOUT_MS = 1_000
|
|
29
|
+
|
|
30
|
+
# Default timeout for querying watermark offsets
|
|
31
|
+
# @see Consumer#query_watermark_offsets
|
|
32
|
+
CONSUMER_QUERY_WATERMARK_TIMEOUT_MS = 1_000
|
|
33
|
+
|
|
34
|
+
# Default timeout for lag calculation watermark queries
|
|
35
|
+
# @see Consumer#lag
|
|
36
|
+
CONSUMER_LAG_TIMEOUT_MS = 1_000
|
|
37
|
+
|
|
38
|
+
# Default timeout for offsets_for_times operation
|
|
39
|
+
# @see Consumer#offsets_for_times
|
|
40
|
+
CONSUMER_OFFSETS_FOR_TIMES_TIMEOUT_MS = 1_000
|
|
41
|
+
|
|
42
|
+
# Default poll timeout for Consumer#each iterator
|
|
43
|
+
# @see Consumer#each
|
|
44
|
+
CONSUMER_POLL_TIMEOUT_MS = 250
|
|
45
|
+
|
|
46
|
+
# Seek operation timeout (0 = non-blocking)
|
|
47
|
+
# @see Consumer#seek_by
|
|
48
|
+
CONSUMER_SEEK_TIMEOUT_MS = 0
|
|
49
|
+
|
|
50
|
+
# Events poll timeout (0 = non-blocking/async)
|
|
51
|
+
# @see Consumer#events_poll
|
|
52
|
+
CONSUMER_EVENTS_POLL_TIMEOUT_MS = 0
|
|
53
|
+
|
|
54
|
+
# Producer timeouts (in milliseconds)
|
|
55
|
+
|
|
56
|
+
# Default timeout for producer flush operation
|
|
57
|
+
# @see Producer#flush
|
|
58
|
+
PRODUCER_FLUSH_TIMEOUT_MS = 5_000
|
|
59
|
+
|
|
60
|
+
# Default flush timeout during purge operation
|
|
61
|
+
# @see Producer#purge
|
|
62
|
+
PRODUCER_PURGE_FLUSH_TIMEOUT_MS = 100
|
|
63
|
+
|
|
64
|
+
# Metadata timeouts (in milliseconds)
|
|
65
|
+
|
|
66
|
+
# Default timeout for metadata requests
|
|
67
|
+
# @see Admin#metadata
|
|
68
|
+
# @see Metadata#initialize
|
|
69
|
+
METADATA_TIMEOUT_MS = 2_000
|
|
70
|
+
|
|
71
|
+
# Handle wait timeouts (in milliseconds)
|
|
72
|
+
|
|
73
|
+
# Default maximum wait timeout for async handles (delivery, admin operations)
|
|
74
|
+
# @see AbstractHandle#wait
|
|
75
|
+
HANDLE_WAIT_TIMEOUT_MS = 60_000
|
|
76
|
+
|
|
77
|
+
# Native Kafka polling (in milliseconds)
|
|
78
|
+
|
|
79
|
+
# Default poll timeout for producer/admin native polling thread
|
|
80
|
+
# @see Config#producer
|
|
81
|
+
# @see Config#admin
|
|
82
|
+
NATIVE_KAFKA_POLL_TIMEOUT_MS = 100
|
|
83
|
+
|
|
84
|
+
# Internal timing (in milliseconds)
|
|
85
|
+
|
|
86
|
+
# Sleep interval during purge wait loop
|
|
87
|
+
# @see Producer#purge
|
|
88
|
+
PRODUCER_PURGE_SLEEP_INTERVAL_MS = 1
|
|
89
|
+
|
|
90
|
+
# Sleep interval while waiting for operations to complete in NativeKafka#synchronize
|
|
91
|
+
# @see NativeKafka#synchronize
|
|
92
|
+
NATIVE_KAFKA_SYNCHRONIZE_SLEEP_INTERVAL_MS = 10
|
|
93
|
+
|
|
94
|
+
# Base backoff factor for metadata retry in milliseconds (multiplied by 2^attempt)
|
|
95
|
+
# @see Metadata#initialize
|
|
96
|
+
METADATA_RETRY_BACKOFF_BASE_MS = 100
|
|
97
|
+
|
|
98
|
+
# Maximum backoff time between metadata retries. Caps the exponential backoff so a long retry
|
|
99
|
+
# sequence against an unhealthy cluster cannot block the calling thread for minutes.
|
|
100
|
+
# @see Metadata#initialize
|
|
101
|
+
METADATA_RETRY_BACKOFF_MAX_MS = 1_000
|
|
102
|
+
|
|
103
|
+
# Soft wall-clock budget for the whole metadata retry loop; past it (and past
|
|
104
|
+
# METADATA_MIN_ATTEMPTS) the loop stops so a synchronous fetch cannot block the caller for long
|
|
105
|
+
# @see Metadata#initialize
|
|
106
|
+
METADATA_RETRY_BUDGET_MS = 5_000
|
|
107
|
+
|
|
108
|
+
# Cache settings (in milliseconds)
|
|
109
|
+
|
|
110
|
+
# Default time-to-live for cached partition counts
|
|
111
|
+
# @see Producer::PartitionsCountCache
|
|
112
|
+
PARTITIONS_COUNT_CACHE_TTL_MS = 30_000
|
|
113
|
+
|
|
114
|
+
# Configuration values (not time-based)
|
|
115
|
+
|
|
116
|
+
# Maximum number of metadata fetch retry attempts
|
|
117
|
+
# @see Metadata#initialize
|
|
118
|
+
METADATA_MAX_RETRIES = 10
|
|
119
|
+
|
|
120
|
+
# Minimum metadata fetch attempts before the retry budget may end the loop, so a slow broker
|
|
121
|
+
# (whose requests each consume the full timeout) still gets a few tries
|
|
122
|
+
# @see Metadata#initialize
|
|
123
|
+
METADATA_MIN_ATTEMPTS = 3
|
|
124
|
+
end
|
|
125
|
+
end
|
data/lib/rdkafka/error.rb
CHANGED
|
@@ -18,12 +18,21 @@ module Rdkafka
|
|
|
18
18
|
# @return [String]
|
|
19
19
|
attr_reader :broker_message
|
|
20
20
|
|
|
21
|
+
# The name of the rdkafka instance that generated this error
|
|
22
|
+
# @return [String, nil]
|
|
23
|
+
attr_reader :instance_name
|
|
24
|
+
|
|
21
25
|
# @private
|
|
22
|
-
|
|
26
|
+
# @param response [Integer] the raw error response code from librdkafka
|
|
27
|
+
# @param message_prefix [String, nil] optional prefix for error messages
|
|
28
|
+
# @param broker_message [String, nil] optional error message from the broker
|
|
29
|
+
# @param instance_name [String, nil] optional name of the rdkafka instance
|
|
30
|
+
def initialize(response, message_prefix = nil, broker_message: nil, instance_name: nil)
|
|
23
31
|
raise TypeError.new("Response has to be an integer") unless response.is_a? Integer
|
|
24
32
|
@rdkafka_response = response
|
|
25
33
|
@message_prefix = message_prefix
|
|
26
34
|
@broker_message = broker_message
|
|
35
|
+
@instance_name = instance_name
|
|
27
36
|
end
|
|
28
37
|
|
|
29
38
|
# This error's code, for example `:partition_eof`, `:msg_size_too_large`.
|
|
@@ -31,7 +40,7 @@ module Rdkafka
|
|
|
31
40
|
def code
|
|
32
41
|
code = Rdkafka::Bindings.rd_kafka_err2name(@rdkafka_response).downcase
|
|
33
42
|
if code[0] == "_"
|
|
34
|
-
code[1
|
|
43
|
+
code[1..].to_sym
|
|
35
44
|
else
|
|
36
45
|
code.to_sym
|
|
37
46
|
end
|
|
@@ -41,11 +50,16 @@ module Rdkafka
|
|
|
41
50
|
# @return [String]
|
|
42
51
|
def to_s
|
|
43
52
|
message_prefix_part = if message_prefix
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
"#{message_prefix} - "
|
|
54
|
+
else
|
|
55
|
+
""
|
|
56
|
+
end
|
|
57
|
+
instance_name_part = if instance_name
|
|
58
|
+
" [#{instance_name}]"
|
|
59
|
+
else
|
|
60
|
+
""
|
|
61
|
+
end
|
|
62
|
+
"#{message_prefix_part}#{Rdkafka::Bindings.rd_kafka_err2str(@rdkafka_response)} (#{code})#{instance_name_part}"
|
|
49
63
|
end
|
|
50
64
|
|
|
51
65
|
# Whether this error indicates the partition is EOF.
|
|
@@ -55,8 +69,10 @@ module Rdkafka
|
|
|
55
69
|
end
|
|
56
70
|
|
|
57
71
|
# Error comparison
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
# @param other [Object] object to compare with
|
|
73
|
+
# @return [Boolean]
|
|
74
|
+
def ==(other)
|
|
75
|
+
other.is_a?(self.class) && (to_s == other.to_s)
|
|
60
76
|
end
|
|
61
77
|
end
|
|
62
78
|
|
|
@@ -66,7 +82,10 @@ module Rdkafka
|
|
|
66
82
|
attr_reader :topic_partition_list
|
|
67
83
|
|
|
68
84
|
# @private
|
|
69
|
-
|
|
85
|
+
# @param response [Integer] the raw error response code from librdkafka
|
|
86
|
+
# @param topic_partition_list [TopicPartitionList] the topic partition list with error info
|
|
87
|
+
# @param message_prefix [String, nil] optional prefix for error messages
|
|
88
|
+
def initialize(response, topic_partition_list, message_prefix = nil)
|
|
70
89
|
super(response, message_prefix)
|
|
71
90
|
@topic_partition_list = topic_partition_list
|
|
72
91
|
end
|
|
@@ -74,28 +93,35 @@ module Rdkafka
|
|
|
74
93
|
|
|
75
94
|
# Error class for public consumer method calls on a closed consumer.
|
|
76
95
|
class ClosedConsumerError < BaseError
|
|
96
|
+
# @param method [Symbol] the method that was called
|
|
77
97
|
def initialize(method)
|
|
78
|
-
super("Illegal call to #{method
|
|
98
|
+
super("Illegal call to #{method} on a closed consumer")
|
|
79
99
|
end
|
|
80
100
|
end
|
|
81
101
|
|
|
82
102
|
# Error class for public producer method calls on a closed producer.
|
|
83
103
|
class ClosedProducerError < BaseError
|
|
104
|
+
# @param method [Symbol] the method that was called
|
|
84
105
|
def initialize(method)
|
|
85
|
-
super("Illegal call to #{method
|
|
106
|
+
super("Illegal call to #{method} on a closed producer")
|
|
86
107
|
end
|
|
87
108
|
end
|
|
88
109
|
|
|
89
|
-
# Error class for public
|
|
110
|
+
# Error class for public admin method calls on a closed admin.
|
|
90
111
|
class ClosedAdminError < BaseError
|
|
112
|
+
# @param method [Symbol] the method that was called
|
|
91
113
|
def initialize(method)
|
|
92
|
-
super("Illegal call to #{method
|
|
114
|
+
super("Illegal call to #{method} on a closed admin")
|
|
93
115
|
end
|
|
94
116
|
end
|
|
95
117
|
|
|
118
|
+
# Error class for calls on a closed inner librdkafka instance.
|
|
96
119
|
class ClosedInnerError < BaseError
|
|
97
120
|
def initialize
|
|
98
121
|
super("Illegal call to a closed inner librdkafka instance")
|
|
99
122
|
end
|
|
100
123
|
end
|
|
124
|
+
|
|
125
|
+
# Error class for librdkafka library loading failures (e.g., glibc compatibility issues).
|
|
126
|
+
class LibraryLoadError < BaseError; end
|
|
101
127
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
module Helpers
|
|
5
|
+
# Shared `#metadata` implementation for Admin, Consumer and Producer.
|
|
6
|
+
#
|
|
7
|
+
# `rd_kafka_metadata()` is handle-agnostic in librdkafka - it works on any `rd_kafka_t`
|
|
8
|
+
# (consumer, producer or admin) - so this Ruby-level implementation is identical across all
|
|
9
|
+
# three client types. Includers must provide a private `#closed_check(method)` that raises
|
|
10
|
+
# their own `Closed*Error`.
|
|
11
|
+
module Metadata
|
|
12
|
+
# Performs the metadata request using this client
|
|
13
|
+
#
|
|
14
|
+
# @param topic_name [String, nil] metadata about particular topic or all if nil
|
|
15
|
+
# @param timeout_ms [Integer] metadata request timeout
|
|
16
|
+
# @return [Rdkafka::Metadata] requested metadata
|
|
17
|
+
def metadata(topic_name = nil, timeout_ms = Defaults::METADATA_TIMEOUT_MS)
|
|
18
|
+
closed_check(__method__)
|
|
19
|
+
|
|
20
|
+
@native_kafka.with_inner do |inner|
|
|
21
|
+
# Must stay fully qualified: this module is itself named `Metadata`, so a bare
|
|
22
|
+
# `Metadata.new` here would resolve to `Rdkafka::Helpers::Metadata` (no `.new`) instead
|
|
23
|
+
# of this class.
|
|
24
|
+
Rdkafka::Metadata.new(inner, topic_name, timeout_ms)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
module Rdkafka
|
|
2
2
|
module Helpers
|
|
3
|
-
|
|
3
|
+
# OAuth helper methods for setting and refreshing SASL/OAUTHBEARER tokens
|
|
4
4
|
module OAuth
|
|
5
|
-
|
|
6
5
|
# Set the OAuthBearer token
|
|
7
6
|
#
|
|
8
7
|
# @param token [String] the mandatory token value to set, often (but not necessarily) a JWS compact serialization as per https://tools.ietf.org/html/rfc7515#section-3.1.
|
|
@@ -12,12 +11,18 @@ module Rdkafka
|
|
|
12
11
|
# @return [Integer] 0 on success
|
|
13
12
|
def oauthbearer_set_token(token:, lifetime_ms:, principal_name:, extensions: nil)
|
|
14
13
|
error_buffer = FFI::MemoryPointer.from_string(" " * 256)
|
|
14
|
+
extensions_ptr, extensions_str_ptrs = map_extensions(extensions)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
begin
|
|
17
|
+
response = @native_kafka.with_inner do |inner|
|
|
18
|
+
Rdkafka::Bindings.rd_kafka_oauthbearer_set_token(
|
|
19
|
+
inner, token, lifetime_ms, principal_name,
|
|
20
|
+
extensions_ptr, extension_size(extensions), error_buffer, 256
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
ensure
|
|
24
|
+
extensions_str_ptrs&.each { |ptr| ptr.free }
|
|
25
|
+
extensions_ptr&.free
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
return response if response.zero?
|
|
@@ -41,14 +46,41 @@ module Rdkafka
|
|
|
41
46
|
|
|
42
47
|
private
|
|
43
48
|
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
# Convert extensions hash to FFI::MemoryPointer (`const char **`).
|
|
50
|
+
#
|
|
51
|
+
# @param extensions [Hash, nil] extension key-value pairs
|
|
52
|
+
# @return [Array<FFI::MemoryPointer, Array<FFI::MemoryPointer>>] array pointer and string pointers
|
|
53
|
+
# @note The returned pointers must be freed manually (autorelease = false).
|
|
54
|
+
def map_extensions(extensions)
|
|
55
|
+
return [nil, nil] if extensions.nil? || extensions.empty?
|
|
56
|
+
|
|
57
|
+
# https://github.com/confluentinc/librdkafka/blob/master/src/rdkafka_sasl_oauthbearer.c#L327-L347
|
|
58
|
+
|
|
59
|
+
# The method argument is const char **
|
|
60
|
+
array_ptr = FFI::MemoryPointer.new(:pointer, extension_size(extensions))
|
|
61
|
+
array_ptr.autorelease = false
|
|
62
|
+
str_ptrs = []
|
|
63
|
+
|
|
64
|
+
# Element i is the key, i + 1 is the value.
|
|
65
|
+
extensions.each_with_index do |(k, v), i|
|
|
66
|
+
k_ptr = FFI::MemoryPointer.from_string(k.to_s)
|
|
67
|
+
k_ptr.autorelease = false
|
|
68
|
+
str_ptrs << k_ptr
|
|
69
|
+
v_ptr = FFI::MemoryPointer.from_string(v.to_s)
|
|
70
|
+
v_ptr.autorelease = false
|
|
71
|
+
str_ptrs << v_ptr
|
|
72
|
+
array_ptr[i * 2].put_pointer(0, k_ptr)
|
|
73
|
+
array_ptr[i * 2 + 1].put_pointer(0, v_ptr)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
[array_ptr, str_ptrs]
|
|
48
77
|
end
|
|
49
78
|
|
|
50
|
-
#
|
|
51
|
-
#
|
|
79
|
+
# Returns the extension size (number of keys + values).
|
|
80
|
+
#
|
|
81
|
+
# @param extensions [Hash, nil] extension key-value pairs
|
|
82
|
+
# @return [Integer] non-negative even number representing keys + values count
|
|
83
|
+
# @see https://github.com/confluentinc/librdkafka/blob/master/src/rdkafka_sasl_oauthbearer.c#L327-L347
|
|
52
84
|
def extension_size(extensions)
|
|
53
85
|
return 0 unless extensions
|
|
54
86
|
extensions.size * 2
|
data/lib/rdkafka/helpers/time.rb
CHANGED
|
@@ -9,6 +9,11 @@ module Rdkafka
|
|
|
9
9
|
def monotonic_now
|
|
10
10
|
::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
11
11
|
end
|
|
12
|
+
|
|
13
|
+
# @return [Integer] current monotonic time in milliseconds
|
|
14
|
+
def monotonic_now_ms
|
|
15
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
|
|
16
|
+
end
|
|
12
17
|
end
|
|
13
18
|
end
|
|
14
19
|
end
|
data/lib/rdkafka/metadata.rb
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Rdkafka
|
|
4
|
+
# Provides cluster metadata information
|
|
4
5
|
class Metadata
|
|
5
|
-
|
|
6
|
+
# @return [Array<Hash>] list of broker metadata
|
|
7
|
+
attr_reader :brokers
|
|
8
|
+
# @return [Array<Hash>] list of topic metadata
|
|
9
|
+
attr_reader :topics
|
|
6
10
|
|
|
7
11
|
# Errors upon which we retry the metadata fetch
|
|
8
12
|
RETRIED_ERRORS = %i[
|
|
@@ -12,13 +16,64 @@ module Rdkafka
|
|
|
12
16
|
|
|
13
17
|
private_constant :RETRIED_ERRORS
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
# Fetches metadata from the Kafka cluster
|
|
20
|
+
#
|
|
21
|
+
# @param native_client [FFI::Pointer] pointer to the native Kafka client
|
|
22
|
+
# @param topic_name [String, nil] specific topic to fetch metadata for, or nil for all topics
|
|
23
|
+
# @param timeout_ms [Integer] timeout in milliseconds
|
|
24
|
+
# @raise [RdkafkaError] when metadata fetch fails
|
|
25
|
+
def initialize(native_client, topic_name = nil, timeout_ms = Defaults::METADATA_TIMEOUT_MS)
|
|
26
|
+
attempt = 0
|
|
27
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) +
|
|
28
|
+
Defaults::METADATA_RETRY_BUDGET_MS / 1_000.0
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
attempt += 1
|
|
32
|
+
fetch_metadata(native_client, topic_name, timeout_ms)
|
|
33
|
+
rescue ::Rdkafka::RdkafkaError => e
|
|
34
|
+
raise unless RETRIED_ERRORS.include?(e.code)
|
|
35
|
+
raise if attempt > Defaults::METADATA_MAX_RETRIES
|
|
36
|
+
|
|
37
|
+
# Stop once the wall-clock retry budget is spent, but only after at least
|
|
38
|
+
# METADATA_MIN_ATTEMPTS tries so a slow broker (whose requests each consume the full
|
|
39
|
+
# timeout) still gets a few tries rather than being cut off after one or two.
|
|
40
|
+
raise if attempt >= Defaults::METADATA_MIN_ATTEMPTS &&
|
|
41
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC) >= deadline
|
|
42
|
+
|
|
43
|
+
# Exponential backoff between attempts, capped so a long retry sequence cannot block for
|
|
44
|
+
# minutes. The request timeout (`timeout_ms`) is intentionally left unchanged: it used to be
|
|
45
|
+
# overwritten with the backoff value, which shrank the first retries below the configured
|
|
46
|
+
# timeout (near-guaranteeing another timeout) and then inflated later ones to ~100s.
|
|
47
|
+
backoff_ms = [
|
|
48
|
+
(2**attempt) * Defaults::METADATA_RETRY_BACKOFF_BASE_MS,
|
|
49
|
+
Defaults::METADATA_RETRY_BACKOFF_MAX_MS
|
|
50
|
+
].min
|
|
51
|
+
|
|
52
|
+
sleep(backoff_ms / 1_000.0)
|
|
53
|
+
|
|
54
|
+
retry
|
|
21
55
|
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Performs a single metadata fetch attempt, freeing this attempt's native resources.
|
|
61
|
+
#
|
|
62
|
+
# Kept separate from {#initialize} so each retried attempt frees its own `native_topic` and
|
|
63
|
+
# metadata struct. `retry` re-enters `initialize`'s `begin` without running an `ensure` placed
|
|
64
|
+
# there, so doing the cleanup per attempt here is what prevents the per-retry native leak. The
|
|
65
|
+
# metadata struct is only allocated on success, so it is read and destroyed only after the
|
|
66
|
+
# result has been validated (avoids destroying a NULL/garbage pointer on a failed fetch).
|
|
67
|
+
#
|
|
68
|
+
# @param native_client [FFI::Pointer] pointer to the native Kafka client
|
|
69
|
+
# @param topic_name [String, nil] specific topic to fetch metadata for, or nil for all topics
|
|
70
|
+
# @param timeout_ms [Integer] timeout in milliseconds
|
|
71
|
+
# @raise [RdkafkaError] when the metadata fetch fails
|
|
72
|
+
def fetch_metadata(native_client, topic_name, timeout_ms)
|
|
73
|
+
native_topic = nil
|
|
74
|
+
metadata_ptr = nil
|
|
75
|
+
|
|
76
|
+
native_topic = Rdkafka::Bindings.rd_kafka_topic_new(native_client, topic_name, nil) if topic_name
|
|
22
77
|
|
|
23
78
|
ptr = FFI::MemoryPointer.new(:pointer)
|
|
24
79
|
|
|
@@ -32,24 +87,18 @@ module Rdkafka
|
|
|
32
87
|
# Error Handling
|
|
33
88
|
raise Rdkafka::RdkafkaError.new(result) unless result.zero?
|
|
34
89
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
raise if attempt > 10
|
|
39
|
-
|
|
40
|
-
backoff_factor = 2**attempt
|
|
41
|
-
timeout = backoff_factor * 0.1
|
|
90
|
+
# rd_kafka_metadata only allocates the struct on success, so we read the pointer to destroy
|
|
91
|
+
# only after the result has been confirmed successful.
|
|
92
|
+
metadata_ptr = ptr.read_pointer
|
|
42
93
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
retry
|
|
94
|
+
metadata_from_native(metadata_ptr)
|
|
46
95
|
ensure
|
|
47
|
-
Rdkafka::Bindings.rd_kafka_topic_destroy(native_topic) if
|
|
48
|
-
Rdkafka::Bindings.rd_kafka_metadata_destroy(
|
|
96
|
+
Rdkafka::Bindings.rd_kafka_topic_destroy(native_topic) if native_topic
|
|
97
|
+
Rdkafka::Bindings.rd_kafka_metadata_destroy(metadata_ptr) if metadata_ptr && !metadata_ptr.null?
|
|
49
98
|
end
|
|
50
99
|
|
|
51
|
-
|
|
52
|
-
|
|
100
|
+
# Extracts metadata from native pointer
|
|
101
|
+
# @param ptr [FFI::Pointer] pointer to native metadata
|
|
53
102
|
def metadata_from_native(ptr)
|
|
54
103
|
metadata = Metadata.new(ptr)
|
|
55
104
|
@brokers = Array.new(metadata[:brokers_count]) do |i|
|
|
@@ -69,7 +118,11 @@ module Rdkafka
|
|
|
69
118
|
end
|
|
70
119
|
end
|
|
71
120
|
|
|
121
|
+
# Base class for metadata FFI structs with hash conversion
|
|
122
|
+
# @private
|
|
72
123
|
class CustomFFIStruct < FFI::Struct
|
|
124
|
+
# Converts struct to a hash
|
|
125
|
+
# @return [Hash]
|
|
73
126
|
def to_h
|
|
74
127
|
members.each_with_object({}) do |mem, hsh|
|
|
75
128
|
val = self.[](mem)
|
|
@@ -80,36 +133,74 @@ module Rdkafka
|
|
|
80
133
|
end
|
|
81
134
|
end
|
|
82
135
|
|
|
136
|
+
# @private
|
|
137
|
+
# FFI struct for rd_kafka_metadata_t
|
|
83
138
|
class Metadata < CustomFFIStruct
|
|
84
139
|
layout :brokers_count, :int,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
140
|
+
:brokers_metadata, :pointer,
|
|
141
|
+
:topics_count, :int,
|
|
142
|
+
:topics_metadata, :pointer,
|
|
143
|
+
:broker_id, :int32,
|
|
144
|
+
:broker_name, :string
|
|
90
145
|
end
|
|
91
146
|
|
|
147
|
+
# @private
|
|
148
|
+
# FFI struct for rd_kafka_metadata_broker_t
|
|
92
149
|
class BrokerMetadata < CustomFFIStruct
|
|
93
150
|
layout :broker_id, :int32,
|
|
94
|
-
|
|
95
|
-
|
|
151
|
+
:broker_name, :string,
|
|
152
|
+
:broker_port, :int
|
|
96
153
|
end
|
|
97
154
|
|
|
155
|
+
# @private
|
|
156
|
+
# FFI struct for rd_kafka_metadata_topic_t
|
|
98
157
|
class TopicMetadata < CustomFFIStruct
|
|
99
158
|
layout :topic_name, :string,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
159
|
+
:partition_count, :int,
|
|
160
|
+
:partitions_metadata, :pointer,
|
|
161
|
+
:rd_kafka_resp_err, :int
|
|
103
162
|
end
|
|
104
163
|
|
|
164
|
+
# @private
|
|
165
|
+
# FFI struct for rd_kafka_metadata_partition_t
|
|
105
166
|
class PartitionMetadata < CustomFFIStruct
|
|
106
167
|
layout :partition_id, :int32,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
168
|
+
:rd_kafka_resp_err, :int,
|
|
169
|
+
:leader, :int32,
|
|
170
|
+
:replica_count, :int,
|
|
171
|
+
:replicas, :pointer,
|
|
172
|
+
:in_sync_replica_brokers, :int,
|
|
173
|
+
:isrs, :pointer
|
|
174
|
+
|
|
175
|
+
# The base `#to_h` skips FFI pointer members, which would drop the replica and in-sync
|
|
176
|
+
# replica assignments entirely. We dereference those pointers here so the partition hash
|
|
177
|
+
# exposes the broker ids backing the partition (needed e.g. to plan replication changes).
|
|
178
|
+
#
|
|
179
|
+
# @return [Hash{Symbol => Integer, Array<Integer>}] partition metadata:
|
|
180
|
+
# * +:partition_id+ (Integer) - partition id
|
|
181
|
+
# * +:leader+ (Integer) - broker id of the partition leader
|
|
182
|
+
# * +:replica_count+ (Integer) - number of assigned replicas
|
|
183
|
+
# * +:in_sync_replica_brokers+ (Integer) - number of in-sync replicas
|
|
184
|
+
# * +:replicas+ (Array<Integer>) - broker ids of the assigned replicas
|
|
185
|
+
# * +:isrs+ (Array<Integer>) - broker ids of the in-sync replicas
|
|
186
|
+
def to_h
|
|
187
|
+
super.merge(
|
|
188
|
+
replicas: read_broker_ids(self[:replicas], self[:replica_count]),
|
|
189
|
+
isrs: read_broker_ids(self[:isrs], self[:in_sync_replica_brokers])
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private
|
|
194
|
+
|
|
195
|
+
# Reads `count` broker ids (int32) from a replicas/isrs pointer.
|
|
196
|
+
# @param pointer [FFI::Pointer] pointer to the broker ids array
|
|
197
|
+
# @param count [Integer] number of broker ids to read
|
|
198
|
+
# @return [Array<Integer>] broker ids (empty when there are none)
|
|
199
|
+
def read_broker_ids(pointer, count)
|
|
200
|
+
return [] if count.zero? || pointer.null?
|
|
201
|
+
|
|
202
|
+
pointer.read_array_of_int32(count)
|
|
203
|
+
end
|
|
113
204
|
end
|
|
114
205
|
end
|
|
115
206
|
end
|