rdkafka 0.22.2 → 0.27.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 +63 -3
- data/Gemfile +8 -0
- data/Gemfile.lint +14 -0
- data/Gemfile.lint.lock +123 -0
- data/README.md +19 -14
- data/Rakefile +21 -21
- data/bin/verify_kafka_warnings +39 -0
- data/dist/{librdkafka-2.8.0.tar.gz → librdkafka-2.14.0.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 +23 -5
- data/lib/rdkafka/admin/acl_binding_result.rb +5 -5
- data/lib/rdkafka/admin/config_resource_binding_result.rb +1 -0
- data/lib/rdkafka/admin/create_acl_handle.rb +7 -4
- data/lib/rdkafka/admin/create_acl_report.rb +3 -2
- data/lib/rdkafka/admin/create_partitions_handle.rb +8 -5
- data/lib/rdkafka/admin/create_partitions_report.rb +1 -0
- data/lib/rdkafka/admin/create_topic_handle.rb +8 -5
- data/lib/rdkafka/admin/create_topic_report.rb +3 -0
- data/lib/rdkafka/admin/delete_acl_handle.rb +9 -6
- data/lib/rdkafka/admin/delete_acl_report.rb +5 -3
- data/lib/rdkafka/admin/delete_groups_handle.rb +10 -5
- data/lib/rdkafka/admin/delete_groups_report.rb +3 -0
- data/lib/rdkafka/admin/delete_topic_handle.rb +8 -5
- data/lib/rdkafka/admin/delete_topic_report.rb +3 -0
- data/lib/rdkafka/admin/describe_acl_handle.rb +9 -6
- data/lib/rdkafka/admin/describe_acl_report.rb +5 -3
- data/lib/rdkafka/admin/describe_configs_handle.rb +7 -4
- data/lib/rdkafka/admin/describe_configs_report.rb +7 -1
- data/lib/rdkafka/admin/incremental_alter_configs_handle.rb +7 -4
- data/lib/rdkafka/admin/incremental_alter_configs_report.rb +7 -1
- data/lib/rdkafka/admin/list_offsets_handle.rb +36 -0
- data/lib/rdkafka/admin/list_offsets_report.rb +51 -0
- data/lib/rdkafka/admin.rb +301 -135
- data/lib/rdkafka/bindings.rb +199 -110
- data/lib/rdkafka/callbacks.rb +124 -21
- data/lib/rdkafka/config.rb +81 -33
- data/lib/rdkafka/consumer/headers.rb +3 -2
- data/lib/rdkafka/consumer/message.rb +12 -11
- data/lib/rdkafka/consumer/partition.rb +8 -4
- data/lib/rdkafka/consumer/topic_partition_list.rb +21 -17
- data/lib/rdkafka/consumer.rb +397 -45
- data/lib/rdkafka/defaults.rb +106 -0
- data/lib/rdkafka/error.rb +40 -14
- data/lib/rdkafka/helpers/oauth.rb +45 -13
- data/lib/rdkafka/helpers/time.rb +5 -0
- data/lib/rdkafka/metadata.rb +45 -21
- data/lib/rdkafka/native_kafka.rb +89 -4
- data/lib/rdkafka/producer/delivery_handle.rb +5 -5
- data/lib/rdkafka/producer/delivery_report.rb +10 -6
- data/lib/rdkafka/producer/partitions_count_cache.rb +29 -19
- data/lib/rdkafka/producer.rb +168 -82
- data/lib/rdkafka/version.rb +6 -3
- data/lib/rdkafka.rb +3 -0
- data/package-lock.json +331 -0
- data/package.json +9 -0
- data/rdkafka.gemspec +57 -36
- data/renovate.json +29 -24
- metadata +29 -124
- 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/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,106 @@
|
|
|
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 querying watermark offsets
|
|
27
|
+
# @see Consumer#query_watermark_offsets
|
|
28
|
+
CONSUMER_QUERY_WATERMARK_TIMEOUT_MS = 1_000
|
|
29
|
+
|
|
30
|
+
# Default timeout for lag calculation watermark queries
|
|
31
|
+
# @see Consumer#lag
|
|
32
|
+
CONSUMER_LAG_TIMEOUT_MS = 1_000
|
|
33
|
+
|
|
34
|
+
# Default timeout for offsets_for_times operation
|
|
35
|
+
# @see Consumer#offsets_for_times
|
|
36
|
+
CONSUMER_OFFSETS_FOR_TIMES_TIMEOUT_MS = 1_000
|
|
37
|
+
|
|
38
|
+
# Default poll timeout for Consumer#each iterator
|
|
39
|
+
# @see Consumer#each
|
|
40
|
+
CONSUMER_POLL_TIMEOUT_MS = 250
|
|
41
|
+
|
|
42
|
+
# Seek operation timeout (0 = non-blocking)
|
|
43
|
+
# @see Consumer#seek_by
|
|
44
|
+
CONSUMER_SEEK_TIMEOUT_MS = 0
|
|
45
|
+
|
|
46
|
+
# Events poll timeout (0 = non-blocking/async)
|
|
47
|
+
# @see Consumer#events_poll
|
|
48
|
+
CONSUMER_EVENTS_POLL_TIMEOUT_MS = 0
|
|
49
|
+
|
|
50
|
+
# Producer timeouts (in milliseconds)
|
|
51
|
+
|
|
52
|
+
# Default timeout for producer flush operation
|
|
53
|
+
# @see Producer#flush
|
|
54
|
+
PRODUCER_FLUSH_TIMEOUT_MS = 5_000
|
|
55
|
+
|
|
56
|
+
# Default flush timeout during purge operation
|
|
57
|
+
# @see Producer#purge
|
|
58
|
+
PRODUCER_PURGE_FLUSH_TIMEOUT_MS = 100
|
|
59
|
+
|
|
60
|
+
# Metadata timeouts (in milliseconds)
|
|
61
|
+
|
|
62
|
+
# Default timeout for metadata requests
|
|
63
|
+
# @see Admin#metadata
|
|
64
|
+
# @see Metadata#initialize
|
|
65
|
+
METADATA_TIMEOUT_MS = 2_000
|
|
66
|
+
|
|
67
|
+
# Handle wait timeouts (in milliseconds)
|
|
68
|
+
|
|
69
|
+
# Default maximum wait timeout for async handles (delivery, admin operations)
|
|
70
|
+
# @see AbstractHandle#wait
|
|
71
|
+
HANDLE_WAIT_TIMEOUT_MS = 60_000
|
|
72
|
+
|
|
73
|
+
# Native Kafka polling (in milliseconds)
|
|
74
|
+
|
|
75
|
+
# Default poll timeout for producer/admin native polling thread
|
|
76
|
+
# @see Config#producer
|
|
77
|
+
# @see Config#admin
|
|
78
|
+
NATIVE_KAFKA_POLL_TIMEOUT_MS = 100
|
|
79
|
+
|
|
80
|
+
# Internal timing (in milliseconds)
|
|
81
|
+
|
|
82
|
+
# Sleep interval during purge wait loop
|
|
83
|
+
# @see Producer#purge
|
|
84
|
+
PRODUCER_PURGE_SLEEP_INTERVAL_MS = 1
|
|
85
|
+
|
|
86
|
+
# Sleep interval while waiting for operations to complete in NativeKafka#synchronize
|
|
87
|
+
# @see NativeKafka#synchronize
|
|
88
|
+
NATIVE_KAFKA_SYNCHRONIZE_SLEEP_INTERVAL_MS = 10
|
|
89
|
+
|
|
90
|
+
# Base backoff factor for metadata retry in milliseconds (multiplied by 2^attempt)
|
|
91
|
+
# @see Metadata#initialize
|
|
92
|
+
METADATA_RETRY_BACKOFF_BASE_MS = 100
|
|
93
|
+
|
|
94
|
+
# Cache settings (in milliseconds)
|
|
95
|
+
|
|
96
|
+
# Default time-to-live for cached partition counts
|
|
97
|
+
# @see Producer::PartitionsCountCache
|
|
98
|
+
PARTITIONS_COUNT_CACHE_TTL_MS = 30_000
|
|
99
|
+
|
|
100
|
+
# Configuration values (not time-based)
|
|
101
|
+
|
|
102
|
+
# Maximum number of metadata fetch retry attempts
|
|
103
|
+
# @see Metadata#initialize
|
|
104
|
+
METADATA_MAX_RETRIES = 10
|
|
105
|
+
end
|
|
106
|
+
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
|
|
@@ -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,7 +16,13 @@ module Rdkafka
|
|
|
12
16
|
|
|
13
17
|
private_constant :RETRIED_ERRORS
|
|
14
18
|
|
|
15
|
-
|
|
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)
|
|
16
26
|
attempt ||= 0
|
|
17
27
|
attempt += 1
|
|
18
28
|
|
|
@@ -35,12 +45,12 @@ module Rdkafka
|
|
|
35
45
|
metadata_from_native(ptr.read_pointer)
|
|
36
46
|
rescue ::Rdkafka::RdkafkaError => e
|
|
37
47
|
raise unless RETRIED_ERRORS.include?(e.code)
|
|
38
|
-
raise if attempt >
|
|
48
|
+
raise if attempt > Defaults::METADATA_MAX_RETRIES
|
|
39
49
|
|
|
40
50
|
backoff_factor = 2**attempt
|
|
41
|
-
|
|
51
|
+
timeout_ms = backoff_factor * Defaults::METADATA_RETRY_BACKOFF_BASE_MS
|
|
42
52
|
|
|
43
|
-
sleep(
|
|
53
|
+
sleep(timeout_ms / 1000.0)
|
|
44
54
|
|
|
45
55
|
retry
|
|
46
56
|
ensure
|
|
@@ -50,6 +60,8 @@ module Rdkafka
|
|
|
50
60
|
|
|
51
61
|
private
|
|
52
62
|
|
|
63
|
+
# Extracts metadata from native pointer
|
|
64
|
+
# @param ptr [FFI::Pointer] pointer to native metadata
|
|
53
65
|
def metadata_from_native(ptr)
|
|
54
66
|
metadata = Metadata.new(ptr)
|
|
55
67
|
@brokers = Array.new(metadata[:brokers_count]) do |i|
|
|
@@ -69,7 +81,11 @@ module Rdkafka
|
|
|
69
81
|
end
|
|
70
82
|
end
|
|
71
83
|
|
|
84
|
+
# Base class for metadata FFI structs with hash conversion
|
|
85
|
+
# @private
|
|
72
86
|
class CustomFFIStruct < FFI::Struct
|
|
87
|
+
# Converts struct to a hash
|
|
88
|
+
# @return [Hash]
|
|
73
89
|
def to_h
|
|
74
90
|
members.each_with_object({}) do |mem, hsh|
|
|
75
91
|
val = self.[](mem)
|
|
@@ -80,36 +96,44 @@ module Rdkafka
|
|
|
80
96
|
end
|
|
81
97
|
end
|
|
82
98
|
|
|
99
|
+
# @private
|
|
100
|
+
# FFI struct for rd_kafka_metadata_t
|
|
83
101
|
class Metadata < CustomFFIStruct
|
|
84
102
|
layout :brokers_count, :int,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
:brokers_metadata, :pointer,
|
|
104
|
+
:topics_count, :int,
|
|
105
|
+
:topics_metadata, :pointer,
|
|
106
|
+
:broker_id, :int32,
|
|
107
|
+
:broker_name, :string
|
|
90
108
|
end
|
|
91
109
|
|
|
110
|
+
# @private
|
|
111
|
+
# FFI struct for rd_kafka_metadata_broker_t
|
|
92
112
|
class BrokerMetadata < CustomFFIStruct
|
|
93
113
|
layout :broker_id, :int32,
|
|
94
|
-
|
|
95
|
-
|
|
114
|
+
:broker_name, :string,
|
|
115
|
+
:broker_port, :int
|
|
96
116
|
end
|
|
97
117
|
|
|
118
|
+
# @private
|
|
119
|
+
# FFI struct for rd_kafka_metadata_topic_t
|
|
98
120
|
class TopicMetadata < CustomFFIStruct
|
|
99
121
|
layout :topic_name, :string,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
122
|
+
:partition_count, :int,
|
|
123
|
+
:partitions_metadata, :pointer,
|
|
124
|
+
:rd_kafka_resp_err, :int
|
|
103
125
|
end
|
|
104
126
|
|
|
127
|
+
# @private
|
|
128
|
+
# FFI struct for rd_kafka_metadata_partition_t
|
|
105
129
|
class PartitionMetadata < CustomFFIStruct
|
|
106
130
|
layout :partition_id, :int32,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
:rd_kafka_resp_err, :int,
|
|
132
|
+
:leader, :int32,
|
|
133
|
+
:replica_count, :int,
|
|
134
|
+
:replicas, :pointer,
|
|
135
|
+
:in_sync_replica_brokers, :int,
|
|
136
|
+
:isrs, :pointer
|
|
113
137
|
end
|
|
114
138
|
end
|
|
115
139
|
end
|
data/lib/rdkafka/native_kafka.rb
CHANGED
|
@@ -4,7 +4,13 @@ module Rdkafka
|
|
|
4
4
|
# @private
|
|
5
5
|
# A wrapper around a native kafka that polls and cleanly exits
|
|
6
6
|
class NativeKafka
|
|
7
|
-
|
|
7
|
+
# Creates a new NativeKafka wrapper
|
|
8
|
+
# @param inner [FFI::Pointer] pointer to the native Kafka handle
|
|
9
|
+
# @param run_polling_thread [Boolean] whether to run a background polling thread
|
|
10
|
+
# @param opaque [Rdkafka::Opaque] opaque object for callback context
|
|
11
|
+
# @param auto_start [Boolean] whether to start the polling thread automatically
|
|
12
|
+
# @param timeout_ms [Integer] poll timeout in milliseconds
|
|
13
|
+
def initialize(inner, run_polling_thread:, opaque:, auto_start: true, timeout_ms: Defaults::NATIVE_KAFKA_POLL_TIMEOUT_MS)
|
|
8
14
|
@inner = inner
|
|
9
15
|
@opaque = opaque
|
|
10
16
|
# Lock around external access
|
|
@@ -37,6 +43,8 @@ module Rdkafka
|
|
|
37
43
|
@closing = false
|
|
38
44
|
end
|
|
39
45
|
|
|
46
|
+
# Starts the polling thread if configured
|
|
47
|
+
# @return [nil]
|
|
40
48
|
def start
|
|
41
49
|
synchronize do
|
|
42
50
|
return if @started
|
|
@@ -62,13 +70,17 @@ module Rdkafka
|
|
|
62
70
|
end
|
|
63
71
|
end
|
|
64
72
|
|
|
65
|
-
@polling_thread.name = "rdkafka.native_kafka##{Rdkafka::Bindings.rd_kafka_name(@inner).gsub(
|
|
73
|
+
@polling_thread.name = "rdkafka.native_kafka##{Rdkafka::Bindings.rd_kafka_name(@inner).gsub("rdkafka", "")}"
|
|
66
74
|
@polling_thread.abort_on_exception = true
|
|
67
75
|
@polling_thread[:closing] = false
|
|
68
76
|
end
|
|
69
77
|
end
|
|
70
78
|
end
|
|
71
79
|
|
|
80
|
+
# Executes a block with the inner native Kafka handle
|
|
81
|
+
# @yield [FFI::Pointer] the inner native Kafka handle
|
|
82
|
+
# @return [Object] the result of the block
|
|
83
|
+
# @raise [ClosedInnerError] when the inner handle is nil
|
|
72
84
|
def with_inner
|
|
73
85
|
if @access_mutex.owned?
|
|
74
86
|
@operations_in_progress += 1
|
|
@@ -81,27 +93,100 @@ module Rdkafka
|
|
|
81
93
|
@decrement_mutex.synchronize { @operations_in_progress -= 1 }
|
|
82
94
|
end
|
|
83
95
|
|
|
96
|
+
# Executes a block while holding exclusive access to the native Kafka handle
|
|
97
|
+
# @param block [Proc] block to execute with the native handle
|
|
98
|
+
# @yield [FFI::Pointer] the inner native Kafka handle
|
|
99
|
+
# @return [Object] the result of the block
|
|
84
100
|
def synchronize(&block)
|
|
85
101
|
@access_mutex.synchronize do
|
|
86
102
|
# Wait for any commands using the inner to finish
|
|
87
103
|
# This can take a while on blocking operations like polling but is essential not to proceed
|
|
88
104
|
# with certain types of operations like resources destruction as it can cause the process
|
|
89
105
|
# to hang or crash
|
|
90
|
-
sleep(0
|
|
106
|
+
sleep(Defaults::NATIVE_KAFKA_SYNCHRONIZE_SLEEP_INTERVAL_MS / 1000.0) until @operations_in_progress.zero?
|
|
91
107
|
|
|
92
108
|
with_inner(&block)
|
|
93
109
|
end
|
|
94
110
|
end
|
|
95
111
|
|
|
112
|
+
# Returns a finalizer proc for closing this native Kafka handle
|
|
113
|
+
# @return [Proc] finalizer proc
|
|
96
114
|
def finalizer
|
|
97
115
|
->(_) { close }
|
|
98
116
|
end
|
|
99
117
|
|
|
118
|
+
# Returns whether this native Kafka handle is closed or closing
|
|
119
|
+
# @return [Boolean] true if closed or closing
|
|
100
120
|
def closed?
|
|
101
121
|
@closing || @inner.nil?
|
|
102
122
|
end
|
|
103
123
|
|
|
104
|
-
|
|
124
|
+
# Enable IO event notifications on the main queue
|
|
125
|
+
# Librdkafka will write to your FD when the queue transitions from empty to non-empty
|
|
126
|
+
#
|
|
127
|
+
# @note This method is incompatible with background polling threads.
|
|
128
|
+
# If background polling is enabled, use manual polling instead (e.g., consumer.poll)
|
|
129
|
+
#
|
|
130
|
+
# @param fd [Integer] your file descriptor (from IO.pipe or eventfd)
|
|
131
|
+
# @param payload [String] data to write to fd when queue has data (default: "\x01")
|
|
132
|
+
# @return [nil]
|
|
133
|
+
# @raise [ClosedInnerError] when the handle is closed
|
|
134
|
+
# @raise [RuntimeError] when background polling thread is active
|
|
135
|
+
#
|
|
136
|
+
# @example
|
|
137
|
+
# # Create your own signaling FD
|
|
138
|
+
# signal_r, signal_w = IO.pipe
|
|
139
|
+
# native_kafka.enable_main_queue_io_events(signal_w.fileno)
|
|
140
|
+
#
|
|
141
|
+
# # Monitor it with select
|
|
142
|
+
# readable, = IO.select([signal_r], nil, nil, timeout)
|
|
143
|
+
# if readable
|
|
144
|
+
# consumer.poll(0) # Get messages
|
|
145
|
+
# end
|
|
146
|
+
def enable_main_queue_io_events(fd, payload = "\x01")
|
|
147
|
+
if @run_polling_thread
|
|
148
|
+
raise "Cannot enable IO events while background polling thread is active. " \
|
|
149
|
+
"Either disable background polling by setting run_polling_thread: false, " \
|
|
150
|
+
"or use manual polling with consumer.poll() instead of the FD API."
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
with_inner do |inner|
|
|
154
|
+
queue_ptr = Bindings.rd_kafka_queue_get_main(inner)
|
|
155
|
+
Bindings.rd_kafka_queue_io_event_enable(queue_ptr, fd, payload, payload.bytesize)
|
|
156
|
+
Bindings.rd_kafka_queue_destroy(queue_ptr)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Enable IO event notifications on the background queue
|
|
161
|
+
# Librdkafka will write to your FD when the background queue transitions from empty to non-empty
|
|
162
|
+
#
|
|
163
|
+
# @note This method is incompatible with background polling threads.
|
|
164
|
+
# If background polling is enabled, use manual polling instead (e.g., consumer.poll)
|
|
165
|
+
#
|
|
166
|
+
# @param fd [Integer] your file descriptor (from IO.pipe or eventfd)
|
|
167
|
+
# @param payload [String] data to write to fd when queue has data (default: "\x01")
|
|
168
|
+
# @return [nil]
|
|
169
|
+
# @raise [ClosedInnerError] when the handle is closed
|
|
170
|
+
# @raise [RuntimeError] when background polling thread is active
|
|
171
|
+
def enable_background_queue_io_events(fd, payload = "\x01")
|
|
172
|
+
if @run_polling_thread
|
|
173
|
+
raise "Cannot enable IO events while background polling thread is active. " \
|
|
174
|
+
"Either disable background polling by setting run_polling_thread: false, " \
|
|
175
|
+
"or use manual polling with consumer.poll() instead of the FD API."
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
with_inner do |inner|
|
|
179
|
+
queue_ptr = Bindings.rd_kafka_queue_get_background(inner)
|
|
180
|
+
Bindings.rd_kafka_queue_io_event_enable(queue_ptr, fd, payload, payload.bytesize)
|
|
181
|
+
Bindings.rd_kafka_queue_destroy(queue_ptr)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Closes the native Kafka handle and cleans up resources
|
|
186
|
+
# @param object_id [Integer, nil] optional object ID (unused, for finalizer compatibility)
|
|
187
|
+
# @yield optional block to execute before destroying the handle
|
|
188
|
+
# @return [nil]
|
|
189
|
+
def close(object_id = nil)
|
|
105
190
|
return if closed?
|
|
106
191
|
|
|
107
192
|
synchronize do
|
|
@@ -6,10 +6,10 @@ module Rdkafka
|
|
|
6
6
|
# producing a message.
|
|
7
7
|
class DeliveryHandle < Rdkafka::AbstractHandle
|
|
8
8
|
layout :pending, :bool,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
:response, :int,
|
|
10
|
+
:partition, :int,
|
|
11
|
+
:offset, :int64,
|
|
12
|
+
:topic_name, :pointer
|
|
13
13
|
|
|
14
14
|
# @return [Object, nil] label set during message production or nil by default
|
|
15
15
|
attr_accessor :label
|
|
@@ -31,7 +31,7 @@ module Rdkafka
|
|
|
31
31
|
# For part of errors, we will not get a topic name reference and in cases like this
|
|
32
32
|
# we should not return it
|
|
33
33
|
topic,
|
|
34
|
-
self[:response] != 0 ? RdkafkaError.new(self[:response]) : nil,
|
|
34
|
+
(self[:response] != 0) ? RdkafkaError.new(self[:response]) : nil,
|
|
35
35
|
label
|
|
36
36
|
)
|
|
37
37
|
end
|
|
@@ -12,8 +12,8 @@ module Rdkafka
|
|
|
12
12
|
# @return [Integer]
|
|
13
13
|
attr_reader :offset
|
|
14
14
|
|
|
15
|
-
# The name of the topic this message was produced to or nil in case
|
|
16
|
-
#
|
|
15
|
+
# The name of the topic this message was produced to or nil in case delivery failed and we
|
|
16
|
+
# we not able to get the topic reference
|
|
17
17
|
#
|
|
18
18
|
# @return [String, nil]
|
|
19
19
|
attr_reader :topic_name
|
|
@@ -30,10 +30,14 @@ module Rdkafka
|
|
|
30
30
|
# is present in both places
|
|
31
31
|
#
|
|
32
32
|
# We do not remove the original `#topic_name` because of backwards compatibility
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
33
|
+
alias_method :topic, :topic_name
|
|
34
|
+
|
|
35
|
+
# @private
|
|
36
|
+
# @param partition [Integer] partition number
|
|
37
|
+
# @param offset [Integer] message offset
|
|
38
|
+
# @param topic_name [String, nil] topic name
|
|
39
|
+
# @param error [Integer, nil] error code if any
|
|
40
|
+
# @param label [Object, nil] user-defined label
|
|
37
41
|
def initialize(partition, offset, topic_name = nil, error = nil, label = nil)
|
|
38
42
|
@partition = partition
|
|
39
43
|
@offset = offset
|