karafka-rdkafka 0.20.0.rc2 → 0.20.0.rc5
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/.github/workflows/ci_linux_x86_64_gnu.yml +249 -0
- data/.github/workflows/ci_linux_x86_64_musl.yml +205 -0
- data/.github/workflows/ci_macos_arm64.yml +306 -0
- data/.github/workflows/push_linux_x86_64_gnu.yml +64 -0
- data/.github/workflows/push_linux_x86_64_musl.yml +77 -0
- data/.github/workflows/push_macos_arm64.yml +54 -0
- data/.github/workflows/push_ruby.yml +37 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +22 -3
- data/README.md +2 -3
- data/Rakefile +0 -2
- data/dist/{librdkafka-2.10.0.tar.gz → librdkafka-2.8.0.tar.gz} +0 -0
- data/docker-compose.yml +1 -1
- data/ext/Rakefile +1 -1
- data/ext/build_common.sh +361 -0
- data/ext/build_linux_x86_64_gnu.sh +306 -0
- data/ext/build_linux_x86_64_musl.sh +763 -0
- data/ext/build_macos_arm64.sh +550 -0
- data/karafka-rdkafka.gemspec +26 -10
- data/lib/rdkafka/bindings.rb +31 -4
- data/lib/rdkafka/config.rb +4 -1
- data/lib/rdkafka/error.rb +8 -1
- data/lib/rdkafka/native_kafka.rb +4 -0
- data/lib/rdkafka/producer/partitions_count_cache.rb +216 -0
- data/lib/rdkafka/producer.rb +40 -28
- data/lib/rdkafka/version.rb +3 -3
- data/lib/rdkafka.rb +1 -0
- data/renovate.json +74 -0
- data/spec/rdkafka/admin_spec.rb +15 -2
- data/spec/rdkafka/bindings_spec.rb +0 -1
- data/spec/rdkafka/config_spec.rb +1 -1
- data/spec/rdkafka/consumer_spec.rb +35 -14
- data/spec/rdkafka/metadata_spec.rb +2 -2
- data/spec/rdkafka/producer/partitions_count_cache_spec.rb +359 -0
- data/spec/rdkafka/producer/partitions_count_spec.rb +359 -0
- data/spec/rdkafka/producer_spec.rb +198 -7
- data/spec/spec_helper.rb +12 -1
- metadata +43 -100
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +0 -99
- data/Guardfile +0 -19
- data/certs/cert.pem +0 -26
- data.tar.gz.sig +0 -0
- metadata.gz.sig +0 -3
data/lib/rdkafka/error.rb
CHANGED
@@ -126,7 +126,14 @@ module Rdkafka
|
|
126
126
|
else
|
127
127
|
''
|
128
128
|
end
|
129
|
-
|
129
|
+
|
130
|
+
err_str = Rdkafka::Bindings.rd_kafka_err2str(@rdkafka_response)
|
131
|
+
base = "#{message_prefix_part}#{err_str} (#{code})"
|
132
|
+
|
133
|
+
return base if broker_message.nil?
|
134
|
+
return base if broker_message.empty?
|
135
|
+
|
136
|
+
"#{base}\n#{broker_message}"
|
130
137
|
end
|
131
138
|
|
132
139
|
# Whether this error indicates the partition is EOF.
|
data/lib/rdkafka/native_kafka.rb
CHANGED
@@ -126,9 +126,13 @@ module Rdkafka
|
|
126
126
|
# and would continue to run, trying to destroy inner twice
|
127
127
|
return unless @inner
|
128
128
|
|
129
|
+
yield if block_given?
|
130
|
+
|
129
131
|
Rdkafka::Bindings.rd_kafka_destroy(@inner)
|
130
132
|
@inner = nil
|
131
133
|
@opaque = nil
|
134
|
+
@poll_mutex.unlock
|
135
|
+
@poll_mutex = nil
|
132
136
|
end
|
133
137
|
end
|
134
138
|
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rdkafka
|
4
|
+
class Producer
|
5
|
+
# Caching mechanism for Kafka topic partition counts to avoid frequent cluster queries
|
6
|
+
#
|
7
|
+
# This cache is designed to optimize the process of obtaining partition counts for topics.
|
8
|
+
# It uses several strategies to minimize Kafka cluster queries:
|
9
|
+
#
|
10
|
+
# @note Design considerations:
|
11
|
+
#
|
12
|
+
# 1. Statistics-based updates
|
13
|
+
# When statistics callbacks are enabled (via `statistics.interval.ms`), we leverage
|
14
|
+
# this data to proactively update the partition counts cache. This approach costs
|
15
|
+
# approximately 0.02ms of processing time during each statistics interval (typically
|
16
|
+
# every 5 seconds) but eliminates the need for explicit blocking metadata queries.
|
17
|
+
#
|
18
|
+
# 2. Edge case handling
|
19
|
+
# If a user configures `statistics.interval.ms` much higher than the default cache TTL
|
20
|
+
# (30 seconds), the cache will still function correctly. When statistics updates don't
|
21
|
+
# occur frequently enough, the cache entries will expire naturally, triggering a
|
22
|
+
# blocking refresh when needed.
|
23
|
+
#
|
24
|
+
# 3. User configuration awareness
|
25
|
+
# The cache respects user-defined settings. If `topic.metadata.refresh.interval.ms` is
|
26
|
+
# set very high, the responsibility for potentially stale data falls on the user. This
|
27
|
+
# is an explicit design choice to honor user configuration preferences and align with
|
28
|
+
# librdkafka settings.
|
29
|
+
#
|
30
|
+
# 4. Process-wide efficiency
|
31
|
+
# Since this cache is shared across all Rdkafka producers and consumers within a process,
|
32
|
+
# having multiple clients improves overall efficiency. Each client contributes to keeping
|
33
|
+
# the cache updated, benefiting all other clients.
|
34
|
+
#
|
35
|
+
# 5. Thread-safety approach
|
36
|
+
# The implementation uses fine-grained locking with per-topic mutexes to minimize
|
37
|
+
# contention in multi-threaded environments while ensuring data consistency.
|
38
|
+
#
|
39
|
+
# 6. Topic recreation handling
|
40
|
+
# If a topic is deleted and recreated with fewer partitions, the cache will continue to
|
41
|
+
# report the higher count until either the TTL expires or the process is restarted. This
|
42
|
+
# design choice simplifies the implementation while relying on librdkafka's error handling
|
43
|
+
# for edge cases. In production environments, topic recreation with different partition
|
44
|
+
# counts is typically accompanied by application restarts to handle structural changes.
|
45
|
+
# This also aligns with the previous cache implementation.
|
46
|
+
class PartitionsCountCache
|
47
|
+
include Helpers::Time
|
48
|
+
|
49
|
+
# Default time-to-live for cached partition counts in seconds
|
50
|
+
#
|
51
|
+
# @note This default was chosen to balance freshness of metadata with performance
|
52
|
+
# optimization. Most Kafka cluster topology changes are planned operations, making 30
|
53
|
+
# seconds a reasonable compromise.
|
54
|
+
DEFAULT_TTL = 30
|
55
|
+
|
56
|
+
# Creates a new partition count cache
|
57
|
+
#
|
58
|
+
# @param ttl [Integer] Time-to-live in seconds for cached values
|
59
|
+
def initialize(ttl = DEFAULT_TTL)
|
60
|
+
@counts = {}
|
61
|
+
@mutex_hash = {}
|
62
|
+
# Used only for @mutex_hash access to ensure thread-safety when creating new mutexes
|
63
|
+
@mutex_for_hash = Mutex.new
|
64
|
+
@ttl = ttl
|
65
|
+
end
|
66
|
+
|
67
|
+
# Reads partition count for a topic with automatic refresh when expired
|
68
|
+
#
|
69
|
+
# This method will return the cached partition count if available and not expired.
|
70
|
+
# If the value is expired or not available, it will execute the provided block
|
71
|
+
# to fetch the current value from Kafka.
|
72
|
+
#
|
73
|
+
# @param topic [String] Kafka topic name
|
74
|
+
# @yield Block that returns the current partition count when cache needs refreshing
|
75
|
+
# @yieldreturn [Integer] Current partition count retrieved from Kafka
|
76
|
+
# @return [Integer] Partition count for the topic
|
77
|
+
#
|
78
|
+
# @note The implementation prioritizes read performance over write consistency
|
79
|
+
# since partition counts typically only increase during normal operation.
|
80
|
+
def get(topic)
|
81
|
+
current_info = @counts[topic]
|
82
|
+
|
83
|
+
if current_info.nil? || expired?(current_info[0])
|
84
|
+
new_count = yield
|
85
|
+
|
86
|
+
if current_info.nil?
|
87
|
+
# No existing data, create a new entry with mutex
|
88
|
+
set(topic, new_count)
|
89
|
+
|
90
|
+
return new_count
|
91
|
+
else
|
92
|
+
current_count = current_info[1]
|
93
|
+
|
94
|
+
if new_count > current_count
|
95
|
+
# Higher value needs mutex to update both timestamp and count
|
96
|
+
set(topic, new_count)
|
97
|
+
|
98
|
+
return new_count
|
99
|
+
else
|
100
|
+
# Same or lower value, just update timestamp without mutex
|
101
|
+
refresh_timestamp(topic)
|
102
|
+
|
103
|
+
return current_count
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
current_info[1]
|
109
|
+
end
|
110
|
+
|
111
|
+
# Update partition count for a topic when needed
|
112
|
+
#
|
113
|
+
# This method updates the partition count for a topic in the cache.
|
114
|
+
# It uses a mutex to ensure thread-safety during updates.
|
115
|
+
#
|
116
|
+
# @param topic [String] Kafka topic name
|
117
|
+
# @param new_count [Integer] New partition count value
|
118
|
+
#
|
119
|
+
# @note We prioritize higher partition counts and only accept them when using
|
120
|
+
# a mutex to ensure consistency. This design decision is based on the fact that
|
121
|
+
# partition counts in Kafka only increase during normal operation.
|
122
|
+
def set(topic, new_count)
|
123
|
+
# First check outside mutex to avoid unnecessary locking
|
124
|
+
current_info = @counts[topic]
|
125
|
+
|
126
|
+
# For lower values, we don't update count but might need to refresh timestamp
|
127
|
+
if current_info && new_count < current_info[1]
|
128
|
+
refresh_timestamp(topic)
|
129
|
+
|
130
|
+
return
|
131
|
+
end
|
132
|
+
|
133
|
+
# Only lock the specific topic mutex
|
134
|
+
mutex_for(topic).synchronize do
|
135
|
+
# Check again inside the lock as another thread might have updated
|
136
|
+
current_info = @counts[topic]
|
137
|
+
|
138
|
+
if current_info.nil?
|
139
|
+
# Create new entry
|
140
|
+
@counts[topic] = [monotonic_now, new_count]
|
141
|
+
else
|
142
|
+
current_count = current_info[1]
|
143
|
+
|
144
|
+
if new_count > current_count
|
145
|
+
# Update to higher count value
|
146
|
+
current_info[0] = monotonic_now
|
147
|
+
current_info[1] = new_count
|
148
|
+
else
|
149
|
+
# Same or lower count, update timestamp only
|
150
|
+
current_info[0] = monotonic_now
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# @return [Hash] hash with ttls and partitions counts array
|
157
|
+
def to_h
|
158
|
+
@counts
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
# Get or create a mutex for a specific topic
|
164
|
+
#
|
165
|
+
# This method ensures that each topic has its own mutex,
|
166
|
+
# allowing operations on different topics to proceed in parallel.
|
167
|
+
#
|
168
|
+
# @param topic [String] Kafka topic name
|
169
|
+
# @return [Mutex] Mutex for the specified topic
|
170
|
+
#
|
171
|
+
# @note We use a separate mutex (@mutex_for_hash) to protect the creation
|
172
|
+
# of new topic mutexes. This pattern allows fine-grained locking while
|
173
|
+
# maintaining thread-safety.
|
174
|
+
def mutex_for(topic)
|
175
|
+
mutex = @mutex_hash[topic]
|
176
|
+
|
177
|
+
return mutex if mutex
|
178
|
+
|
179
|
+
# Use a separate mutex to protect the creation of new topic mutexes
|
180
|
+
@mutex_for_hash.synchronize do
|
181
|
+
# Check again in case another thread created it
|
182
|
+
@mutex_hash[topic] ||= Mutex.new
|
183
|
+
end
|
184
|
+
|
185
|
+
@mutex_hash[topic]
|
186
|
+
end
|
187
|
+
|
188
|
+
# Update the timestamp without acquiring the mutex
|
189
|
+
#
|
190
|
+
# This is an optimization that allows refreshing the TTL of existing entries
|
191
|
+
# without the overhead of mutex acquisition.
|
192
|
+
#
|
193
|
+
# @param topic [String] Kafka topic name
|
194
|
+
#
|
195
|
+
# @note This method is safe for refreshing existing data regardless of count
|
196
|
+
# because it only updates the timestamp, which doesn't affect the correctness
|
197
|
+
# of concurrent operations.
|
198
|
+
def refresh_timestamp(topic)
|
199
|
+
current_info = @counts[topic]
|
200
|
+
|
201
|
+
return unless current_info
|
202
|
+
|
203
|
+
# Update the timestamp in-place
|
204
|
+
current_info[0] = monotonic_now
|
205
|
+
end
|
206
|
+
|
207
|
+
# Check if a timestamp has expired based on the TTL
|
208
|
+
#
|
209
|
+
# @param timestamp [Float] Monotonic timestamp to check
|
210
|
+
# @return [Boolean] true if expired, false otherwise
|
211
|
+
def expired?(timestamp)
|
212
|
+
monotonic_now - timestamp > @ttl
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
data/lib/rdkafka/producer.rb
CHANGED
@@ -6,13 +6,31 @@ module Rdkafka
|
|
6
6
|
include Helpers::Time
|
7
7
|
include Helpers::OAuth
|
8
8
|
|
9
|
-
#
|
10
|
-
|
9
|
+
# @private
|
10
|
+
@@partitions_count_cache = PartitionsCountCache.new
|
11
|
+
|
12
|
+
# Global (process wide) partitions cache. We use it to store number of topics partitions,
|
13
|
+
# either from the librdkafka statistics (if enabled) or via direct inline calls every now and
|
14
|
+
# then. Since the partitions count can only grow and should be same for all consumers and
|
15
|
+
# producers, we can use a global cache as long as we ensure that updates only move up.
|
16
|
+
#
|
17
|
+
# @note It is critical to remember, that not all users may have statistics callbacks enabled,
|
18
|
+
# hence we should not make assumption that this cache is always updated from the stats.
|
19
|
+
#
|
20
|
+
# @return [Rdkafka::Producer::PartitionsCountCache]
|
21
|
+
def self.partitions_count_cache
|
22
|
+
@@partitions_count_cache
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param partitions_count_cache [Rdkafka::Producer::PartitionsCountCache]
|
26
|
+
def self.partitions_count_cache=(partitions_count_cache)
|
27
|
+
@@partitions_count_cache = partitions_count_cache
|
28
|
+
end
|
11
29
|
|
12
30
|
# Empty hash used as a default
|
13
31
|
EMPTY_HASH = {}.freeze
|
14
32
|
|
15
|
-
private_constant :
|
33
|
+
private_constant :EMPTY_HASH
|
16
34
|
|
17
35
|
# Raised when there was a critical issue when invoking rd_kafka_topic_new
|
18
36
|
# This is a temporary solution until https://github.com/karafka/rdkafka-ruby/issues/451 is
|
@@ -43,25 +61,6 @@ module Rdkafka
|
|
43
61
|
|
44
62
|
# Makes sure, that native kafka gets closed before it gets GCed by Ruby
|
45
63
|
ObjectSpace.define_finalizer(self, native_kafka.finalizer)
|
46
|
-
|
47
|
-
@_partitions_count_cache = Hash.new do |cache, topic|
|
48
|
-
topic_metadata = nil
|
49
|
-
|
50
|
-
@native_kafka.with_inner do |inner|
|
51
|
-
topic_metadata = ::Rdkafka::Metadata.new(inner, topic).topics&.first
|
52
|
-
end
|
53
|
-
|
54
|
-
partition_count = topic_metadata ? topic_metadata[:partition_count] : -1
|
55
|
-
|
56
|
-
# This approach caches the failure to fetch only for 1 second. This will make sure, that
|
57
|
-
# we do not cache the failure for too long but also "buys" us a bit of time in case there
|
58
|
-
# would be issues in the cluster so we won't overaload it with consecutive requests
|
59
|
-
cache[topic] = if partition_count.positive?
|
60
|
-
[monotonic_now, partition_count]
|
61
|
-
else
|
62
|
-
[monotonic_now - PARTITIONS_COUNT_TTL + 5, partition_count]
|
63
|
-
end
|
64
|
-
end
|
65
64
|
end
|
66
65
|
|
67
66
|
# Sets alternative set of configuration details that can be set per topic
|
@@ -284,18 +283,31 @@ module Rdkafka
|
|
284
283
|
# @note If 'allow.auto.create.topics' is set to true in the broker, the topic will be
|
285
284
|
# auto-created after returning nil.
|
286
285
|
#
|
287
|
-
# @note We cache the partition count for a given topic for given time.
|
286
|
+
# @note We cache the partition count for a given topic for given time. If statistics are
|
287
|
+
# enabled for any producer or consumer, it will take precedence over per instance fetching.
|
288
|
+
#
|
288
289
|
# This prevents us in case someone uses `partition_key` from querying for the count with
|
289
|
-
# each message. Instead we query once every 30 seconds at most if we have a valid
|
290
|
-
# count or every 5 seconds in case we were not able to obtain number of partitions
|
290
|
+
# each message. Instead we query at most once every 30 seconds at most if we have a valid
|
291
|
+
# partition count or every 5 seconds in case we were not able to obtain number of partitions.
|
291
292
|
def partition_count(topic)
|
292
293
|
closed_producer_check(__method__)
|
293
294
|
|
294
|
-
|
295
|
-
|
295
|
+
self.class.partitions_count_cache.get(topic) do
|
296
|
+
topic_metadata = nil
|
297
|
+
|
298
|
+
@native_kafka.with_inner do |inner|
|
299
|
+
topic_metadata = ::Rdkafka::Metadata.new(inner, topic).topics&.first
|
300
|
+
end
|
301
|
+
|
302
|
+
topic_metadata ? topic_metadata[:partition_count] : -1
|
296
303
|
end
|
304
|
+
rescue Rdkafka::RdkafkaError => e
|
305
|
+
# If the topic does not exist, it will be created or if not allowed another error will be
|
306
|
+
# raised. We here return -1 so this can happen without early error happening on metadata
|
307
|
+
# discovery.
|
308
|
+
return -1 if e.code == :unknown_topic_or_part
|
297
309
|
|
298
|
-
|
310
|
+
raise(e)
|
299
311
|
end
|
300
312
|
|
301
313
|
# Produces a message to a Kafka topic. The message is added to rdkafka's queue, call {DeliveryHandle#wait wait} on the returned delivery handle to make sure it is delivered.
|
data/lib/rdkafka/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Rdkafka
|
4
|
-
VERSION = "0.20.0.
|
5
|
-
LIBRDKAFKA_VERSION = "2.
|
6
|
-
LIBRDKAFKA_SOURCE_SHA256 = "
|
4
|
+
VERSION = "0.20.0.rc5"
|
5
|
+
LIBRDKAFKA_VERSION = "2.8.0"
|
6
|
+
LIBRDKAFKA_SOURCE_SHA256 = "5bd1c46f63265f31c6bfcedcde78703f77d28238eadf23821c2b43fc30be3e25"
|
7
7
|
end
|
data/lib/rdkafka.rb
CHANGED
@@ -42,6 +42,7 @@ require "rdkafka/consumer/topic_partition_list"
|
|
42
42
|
require "rdkafka/error"
|
43
43
|
require "rdkafka/metadata"
|
44
44
|
require "rdkafka/native_kafka"
|
45
|
+
require "rdkafka/producer/partitions_count_cache"
|
45
46
|
require "rdkafka/producer"
|
46
47
|
require "rdkafka/producer/delivery_handle"
|
47
48
|
require "rdkafka/producer/delivery_report"
|
data/renovate.json
CHANGED
@@ -14,5 +14,79 @@
|
|
14
14
|
],
|
15
15
|
"minimumReleaseAge": "7 days"
|
16
16
|
}
|
17
|
+
],
|
18
|
+
"customManagers": [
|
19
|
+
{
|
20
|
+
"customType": "regex",
|
21
|
+
"managerFilePatterns": [
|
22
|
+
"/^ext/build_common\\.sh$/"
|
23
|
+
],
|
24
|
+
"matchStrings": [
|
25
|
+
"readonly OPENSSL_VERSION=\"(?<currentValue>.*)\""
|
26
|
+
],
|
27
|
+
"depNameTemplate": "openssl/openssl",
|
28
|
+
"datasourceTemplate": "github-releases",
|
29
|
+
"extractVersionTemplate": "^OpenSSL_(?<version>.*)$"
|
30
|
+
},
|
31
|
+
{
|
32
|
+
"customType": "regex",
|
33
|
+
"managerFilePatterns": [
|
34
|
+
"/^ext/build_common\\.sh$/"
|
35
|
+
],
|
36
|
+
"matchStrings": [
|
37
|
+
"readonly CYRUS_SASL_VERSION=\"(?<currentValue>.*)\""
|
38
|
+
],
|
39
|
+
"depNameTemplate": "cyrusimap/cyrus-sasl",
|
40
|
+
"datasourceTemplate": "github-releases",
|
41
|
+
"extractVersionTemplate": "^cyrus-sasl-(?<version>.*)$"
|
42
|
+
},
|
43
|
+
{
|
44
|
+
"customType": "regex",
|
45
|
+
"managerFilePatterns": [
|
46
|
+
"/^ext/build_common\\.sh$/"
|
47
|
+
],
|
48
|
+
"matchStrings": [
|
49
|
+
"readonly ZLIB_VERSION=\"(?<currentValue>.*)\""
|
50
|
+
],
|
51
|
+
"depNameTemplate": "madler/zlib",
|
52
|
+
"datasourceTemplate": "github-releases",
|
53
|
+
"extractVersionTemplate": "^v(?<version>.*)$"
|
54
|
+
},
|
55
|
+
{
|
56
|
+
"customType": "regex",
|
57
|
+
"managerFilePatterns": [
|
58
|
+
"/^ext/build_common\\.sh$/"
|
59
|
+
],
|
60
|
+
"matchStrings": [
|
61
|
+
"readonly ZSTD_VERSION=\"(?<currentValue>.*)\""
|
62
|
+
],
|
63
|
+
"depNameTemplate": "facebook/zstd",
|
64
|
+
"datasourceTemplate": "github-releases",
|
65
|
+
"extractVersionTemplate": "^v(?<version>.*)$"
|
66
|
+
},
|
67
|
+
{
|
68
|
+
"customType": "regex",
|
69
|
+
"managerFilePatterns": [
|
70
|
+
"/^ext/build_common\\.sh$/"
|
71
|
+
],
|
72
|
+
"matchStrings": [
|
73
|
+
"readonly KRB5_VERSION=\"(?<currentValue>.*)\""
|
74
|
+
],
|
75
|
+
"depNameTemplate": "krb5/krb5",
|
76
|
+
"datasourceTemplate": "github-releases",
|
77
|
+
"extractVersionTemplate": "^krb5-(?<version>.*)$"
|
78
|
+
},
|
79
|
+
{
|
80
|
+
"customType": "regex",
|
81
|
+
"managerFilePatterns": [
|
82
|
+
"/^ext/build_common\\.sh$/"
|
83
|
+
],
|
84
|
+
"matchStrings": [
|
85
|
+
"readonly LIBRDKAFKA_VERSION=\"(?<currentValue>.*)\""
|
86
|
+
],
|
87
|
+
"depNameTemplate": "confluentinc/librdkafka",
|
88
|
+
"datasourceTemplate": "github-releases",
|
89
|
+
"extractVersionTemplate": "^v(?<version>.*)$"
|
90
|
+
}
|
17
91
|
]
|
18
92
|
}
|
data/spec/rdkafka/admin_spec.rb
CHANGED
@@ -34,7 +34,7 @@ describe Rdkafka::Admin do
|
|
34
34
|
describe '#describe_errors' do
|
35
35
|
let(:errors) { admin.class.describe_errors }
|
36
36
|
|
37
|
-
it { expect(errors.size).to eq(
|
37
|
+
it { expect(errors.size).to eq(170) }
|
38
38
|
it { expect(errors[-184]).to eq(code: -184, description: 'Local: Queue full', name: '_QUEUE_FULL') }
|
39
39
|
it { expect(errors[21]).to eq(code: 21, description: 'Broker: Invalid required acks value', name: 'INVALID_REQUIRED_ACKS') }
|
40
40
|
end
|
@@ -295,6 +295,8 @@ describe Rdkafka::Admin do
|
|
295
295
|
expect(resources_results.first.type).to eq(2)
|
296
296
|
expect(resources_results.first.name).to eq(topic_name)
|
297
297
|
|
298
|
+
sleep(1)
|
299
|
+
|
298
300
|
ret_config = admin.describe_configs(resources_with_configs).wait.resources.first.configs.find do |config|
|
299
301
|
config.name == 'delete.retention.ms'
|
300
302
|
end
|
@@ -325,6 +327,9 @@ describe Rdkafka::Admin do
|
|
325
327
|
expect(resources_results.size).to eq(1)
|
326
328
|
expect(resources_results.first.type).to eq(2)
|
327
329
|
expect(resources_results.first.name).to eq(topic_name)
|
330
|
+
|
331
|
+
sleep(1)
|
332
|
+
|
328
333
|
ret_config = admin.describe_configs(resources_with_configs).wait.resources.first.configs.find do |config|
|
329
334
|
config.name == 'delete.retention.ms'
|
330
335
|
end
|
@@ -356,6 +361,8 @@ describe Rdkafka::Admin do
|
|
356
361
|
expect(resources_results.first.type).to eq(2)
|
357
362
|
expect(resources_results.first.name).to eq(topic_name)
|
358
363
|
|
364
|
+
sleep(1)
|
365
|
+
|
359
366
|
ret_config = admin.describe_configs(resources_with_configs).wait.resources.first.configs.find do |config|
|
360
367
|
config.name == 'cleanup.policy'
|
361
368
|
end
|
@@ -387,6 +394,8 @@ describe Rdkafka::Admin do
|
|
387
394
|
expect(resources_results.first.type).to eq(2)
|
388
395
|
expect(resources_results.first.name).to eq(topic_name)
|
389
396
|
|
397
|
+
sleep(1)
|
398
|
+
|
390
399
|
ret_config = admin.describe_configs(resources_with_configs).wait.resources.first.configs.find do |config|
|
391
400
|
config.name == 'cleanup.policy'
|
392
401
|
end
|
@@ -622,7 +631,11 @@ describe Rdkafka::Admin do
|
|
622
631
|
|
623
632
|
consumer.subscribe(topic_name)
|
624
633
|
wait_for_assignment(consumer)
|
625
|
-
message =
|
634
|
+
message = nil
|
635
|
+
|
636
|
+
10.times do
|
637
|
+
message ||= consumer.poll(100)
|
638
|
+
end
|
626
639
|
|
627
640
|
expect(message).to_not be_nil
|
628
641
|
|
data/spec/rdkafka/config_spec.rb
CHANGED
@@ -159,7 +159,7 @@ describe Rdkafka::Config do
|
|
159
159
|
|
160
160
|
it "should use default configuration" do
|
161
161
|
config = Rdkafka::Config.new
|
162
|
-
expect(config[:"api.version.request"]).to eq
|
162
|
+
expect(config[:"api.version.request"]).to eq true
|
163
163
|
end
|
164
164
|
|
165
165
|
it "should create a consumer with valid config" do
|
@@ -170,8 +170,16 @@ describe Rdkafka::Consumer do
|
|
170
170
|
end
|
171
171
|
|
172
172
|
describe "#seek" do
|
173
|
+
let(:topic) { "it-#{SecureRandom.uuid}" }
|
174
|
+
|
175
|
+
before do
|
176
|
+
admin = rdkafka_producer_config.admin
|
177
|
+
admin.create_topic(topic, 1, 1).wait
|
178
|
+
admin.close
|
179
|
+
end
|
180
|
+
|
173
181
|
it "should raise an error when seeking fails" do
|
174
|
-
fake_msg = OpenStruct.new(topic:
|
182
|
+
fake_msg = OpenStruct.new(topic: topic, partition: 0, offset: 0)
|
175
183
|
|
176
184
|
expect(Rdkafka::Bindings).to receive(:rd_kafka_seek).and_return(20)
|
177
185
|
expect {
|
@@ -181,9 +189,12 @@ describe Rdkafka::Consumer do
|
|
181
189
|
|
182
190
|
context "subscription" do
|
183
191
|
let(:timeout) { 1000 }
|
192
|
+
# Some specs here test the manual offset commit hence we want to ensure, that we have some
|
193
|
+
# offsets in-memory that we can manually commit
|
194
|
+
let(:consumer) { rdkafka_consumer_config('auto.commit.interval.ms': 60_000).consumer }
|
184
195
|
|
185
196
|
before do
|
186
|
-
consumer.subscribe(
|
197
|
+
consumer.subscribe(topic)
|
187
198
|
|
188
199
|
# 1. partitions are assigned
|
189
200
|
wait_for_assignment(consumer)
|
@@ -196,7 +207,7 @@ describe Rdkafka::Consumer do
|
|
196
207
|
|
197
208
|
def send_one_message(val)
|
198
209
|
producer.produce(
|
199
|
-
topic:
|
210
|
+
topic: topic,
|
200
211
|
payload: "payload #{val}",
|
201
212
|
key: "key 1",
|
202
213
|
partition: 0
|
@@ -211,7 +222,7 @@ describe Rdkafka::Consumer do
|
|
211
222
|
|
212
223
|
# 4. pause the subscription
|
213
224
|
tpl = Rdkafka::Consumer::TopicPartitionList.new
|
214
|
-
tpl.add_topic(
|
225
|
+
tpl.add_topic(topic, 1)
|
215
226
|
consumer.pause(tpl)
|
216
227
|
|
217
228
|
# 5. seek to previous message
|
@@ -219,7 +230,7 @@ describe Rdkafka::Consumer do
|
|
219
230
|
|
220
231
|
# 6. resume the subscription
|
221
232
|
tpl = Rdkafka::Consumer::TopicPartitionList.new
|
222
|
-
tpl.add_topic(
|
233
|
+
tpl.add_topic(topic, 1)
|
223
234
|
consumer.resume(tpl)
|
224
235
|
|
225
236
|
# 7. ensure same message is read again
|
@@ -227,7 +238,7 @@ describe Rdkafka::Consumer do
|
|
227
238
|
|
228
239
|
# This is needed because `enable.auto.offset.store` is true but when running in CI that
|
229
240
|
# is overloaded, offset store lags
|
230
|
-
sleep(
|
241
|
+
sleep(1)
|
231
242
|
|
232
243
|
consumer.commit
|
233
244
|
expect(message1.offset).to eq message2.offset
|
@@ -259,10 +270,17 @@ describe Rdkafka::Consumer do
|
|
259
270
|
end
|
260
271
|
|
261
272
|
describe "#seek_by" do
|
262
|
-
let(:
|
273
|
+
let(:consumer) { rdkafka_consumer_config('auto.commit.interval.ms': 60_000).consumer }
|
274
|
+
let(:topic) { "it-#{SecureRandom.uuid}" }
|
263
275
|
let(:partition) { 0 }
|
264
276
|
let(:offset) { 0 }
|
265
277
|
|
278
|
+
before do
|
279
|
+
admin = rdkafka_producer_config.admin
|
280
|
+
admin.create_topic(topic, 1, 1).wait
|
281
|
+
admin.close
|
282
|
+
end
|
283
|
+
|
266
284
|
it "should raise an error when seeking fails" do
|
267
285
|
expect(Rdkafka::Bindings).to receive(:rd_kafka_seek).and_return(20)
|
268
286
|
expect {
|
@@ -283,6 +301,7 @@ describe Rdkafka::Consumer do
|
|
283
301
|
# 2. eat unrelated messages
|
284
302
|
while(consumer.poll(timeout)) do; end
|
285
303
|
end
|
304
|
+
|
286
305
|
after { consumer.unsubscribe }
|
287
306
|
|
288
307
|
def send_one_message(val)
|
@@ -813,12 +832,14 @@ describe Rdkafka::Consumer do
|
|
813
832
|
end
|
814
833
|
|
815
834
|
it "should return a message if there is one" do
|
835
|
+
topic = "it-#{SecureRandom.uuid}"
|
836
|
+
|
816
837
|
producer.produce(
|
817
|
-
topic:
|
838
|
+
topic: topic,
|
818
839
|
payload: "payload 1",
|
819
840
|
key: "key 1"
|
820
841
|
).wait
|
821
|
-
consumer.subscribe(
|
842
|
+
consumer.subscribe(topic)
|
822
843
|
message = consumer.each {|m| break m}
|
823
844
|
|
824
845
|
expect(message).to be_a Rdkafka::Consumer::Message
|
@@ -838,13 +859,13 @@ describe Rdkafka::Consumer do
|
|
838
859
|
}.to raise_error Rdkafka::RdkafkaError
|
839
860
|
end
|
840
861
|
|
841
|
-
it "expect
|
862
|
+
it "expect to raise error when polling non-existing topic" do
|
842
863
|
missing_topic = SecureRandom.uuid
|
843
864
|
consumer.subscribe(missing_topic)
|
844
865
|
|
845
|
-
|
846
|
-
|
847
|
-
|
866
|
+
expect {
|
867
|
+
consumer.poll(1_000)
|
868
|
+
}.to raise_error Rdkafka::RdkafkaError, /Subscribed topic not available: #{missing_topic}/
|
848
869
|
end
|
849
870
|
end
|
850
871
|
|
@@ -1027,7 +1048,7 @@ describe Rdkafka::Consumer do
|
|
1027
1048
|
after { Rdkafka::Config.statistics_callback = nil }
|
1028
1049
|
|
1029
1050
|
let(:consumer) do
|
1030
|
-
config = rdkafka_consumer_config('statistics.interval.ms':
|
1051
|
+
config = rdkafka_consumer_config('statistics.interval.ms': 500)
|
1031
1052
|
config.consumer_poll_set = false
|
1032
1053
|
config.consumer
|
1033
1054
|
end
|
@@ -30,7 +30,7 @@ describe Rdkafka::Metadata do
|
|
30
30
|
it "#brokers returns our single broker" do
|
31
31
|
expect(subject.brokers.length).to eq(1)
|
32
32
|
expect(subject.brokers[0][:broker_id]).to eq(1)
|
33
|
-
expect(subject.brokers[0][:broker_name])
|
33
|
+
expect(%w[127.0.0.1 localhost]).to include(subject.brokers[0][:broker_name])
|
34
34
|
expect(subject.brokers[0][:broker_port]).to eq(9092)
|
35
35
|
end
|
36
36
|
|
@@ -53,7 +53,7 @@ describe Rdkafka::Metadata do
|
|
53
53
|
it "#brokers returns our single broker" do
|
54
54
|
expect(subject.brokers.length).to eq(1)
|
55
55
|
expect(subject.brokers[0][:broker_id]).to eq(1)
|
56
|
-
expect(subject.brokers[0][:broker_name])
|
56
|
+
expect(%w[127.0.0.1 localhost]).to include(subject.brokers[0][:broker_name])
|
57
57
|
expect(subject.brokers[0][:broker_port]).to eq(9092)
|
58
58
|
end
|
59
59
|
|