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
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
|
|
@@ -47,8 +55,7 @@ module Rdkafka
|
|
|
47
55
|
Rdkafka::Bindings.rd_kafka_poll(@inner, 0)
|
|
48
56
|
|
|
49
57
|
if @run_polling_thread
|
|
50
|
-
# Start thread to poll client for delivery callbacks,
|
|
51
|
-
# not used in consumer.
|
|
58
|
+
# Start thread to poll client for delivery callbacks, not used in consumer.
|
|
52
59
|
@polling_thread = Thread.new do
|
|
53
60
|
loop do
|
|
54
61
|
@poll_mutex.synchronize do
|
|
@@ -62,13 +69,17 @@ module Rdkafka
|
|
|
62
69
|
end
|
|
63
70
|
end
|
|
64
71
|
|
|
65
|
-
@polling_thread.name = "rdkafka.native_kafka##{Rdkafka::Bindings.rd_kafka_name(@inner).gsub(
|
|
72
|
+
@polling_thread.name = "rdkafka.native_kafka##{Rdkafka::Bindings.rd_kafka_name(@inner).gsub("rdkafka", "")}"
|
|
66
73
|
@polling_thread.abort_on_exception = true
|
|
67
74
|
@polling_thread[:closing] = false
|
|
68
75
|
end
|
|
69
76
|
end
|
|
70
77
|
end
|
|
71
78
|
|
|
79
|
+
# Executes a block with the inner native Kafka handle
|
|
80
|
+
# @yield [FFI::Pointer] the inner native Kafka handle
|
|
81
|
+
# @return [Object] the result of the block
|
|
82
|
+
# @raise [ClosedInnerError] when the inner handle is nil
|
|
72
83
|
def with_inner
|
|
73
84
|
if @access_mutex.owned?
|
|
74
85
|
@operations_in_progress += 1
|
|
@@ -81,27 +92,100 @@ module Rdkafka
|
|
|
81
92
|
@decrement_mutex.synchronize { @operations_in_progress -= 1 }
|
|
82
93
|
end
|
|
83
94
|
|
|
95
|
+
# Executes a block while holding exclusive access to the native Kafka handle
|
|
96
|
+
# @param block [Proc] block to execute with the native handle
|
|
97
|
+
# @yield [FFI::Pointer] the inner native Kafka handle
|
|
98
|
+
# @return [Object] the result of the block
|
|
84
99
|
def synchronize(&block)
|
|
85
100
|
@access_mutex.synchronize do
|
|
86
101
|
# Wait for any commands using the inner to finish
|
|
87
102
|
# This can take a while on blocking operations like polling but is essential not to proceed
|
|
88
103
|
# with certain types of operations like resources destruction as it can cause the process
|
|
89
104
|
# to hang or crash
|
|
90
|
-
sleep(0
|
|
105
|
+
sleep(Defaults::NATIVE_KAFKA_SYNCHRONIZE_SLEEP_INTERVAL_MS / 1000.0) until @operations_in_progress.zero?
|
|
91
106
|
|
|
92
107
|
with_inner(&block)
|
|
93
108
|
end
|
|
94
109
|
end
|
|
95
110
|
|
|
111
|
+
# Returns a finalizer proc for closing this native Kafka handle
|
|
112
|
+
# @return [Proc] finalizer proc
|
|
96
113
|
def finalizer
|
|
97
114
|
->(_) { close }
|
|
98
115
|
end
|
|
99
116
|
|
|
117
|
+
# Returns whether this native Kafka handle is closed or closing
|
|
118
|
+
# @return [Boolean] true if closed or closing
|
|
100
119
|
def closed?
|
|
101
120
|
@closing || @inner.nil?
|
|
102
121
|
end
|
|
103
122
|
|
|
104
|
-
|
|
123
|
+
# Enable IO event notifications on the main queue
|
|
124
|
+
# Librdkafka will write to your FD when the queue transitions from empty to non-empty
|
|
125
|
+
#
|
|
126
|
+
# @note This method is incompatible with background polling threads.
|
|
127
|
+
# If background polling is enabled, use manual polling instead (e.g., consumer.poll)
|
|
128
|
+
#
|
|
129
|
+
# @param fd [Integer] your file descriptor (from IO.pipe or eventfd)
|
|
130
|
+
# @param payload [String] data to write to fd when queue has data (default: "\x01")
|
|
131
|
+
# @return [nil]
|
|
132
|
+
# @raise [ClosedInnerError] when the handle is closed
|
|
133
|
+
# @raise [RuntimeError] when background polling thread is active
|
|
134
|
+
#
|
|
135
|
+
# @example
|
|
136
|
+
# # Create your own signaling FD
|
|
137
|
+
# signal_r, signal_w = IO.pipe
|
|
138
|
+
# native_kafka.enable_main_queue_io_events(signal_w.fileno)
|
|
139
|
+
#
|
|
140
|
+
# # Monitor it with select
|
|
141
|
+
# readable, = IO.select([signal_r], nil, nil, timeout)
|
|
142
|
+
# if readable
|
|
143
|
+
# consumer.poll(0) # Get messages
|
|
144
|
+
# end
|
|
145
|
+
def enable_main_queue_io_events(fd, payload = "\x01")
|
|
146
|
+
if @run_polling_thread
|
|
147
|
+
raise "Cannot enable IO events while background polling thread is active. " \
|
|
148
|
+
"Either disable background polling by setting run_polling_thread: false, " \
|
|
149
|
+
"or use manual polling with consumer.poll() instead of the FD API."
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
with_inner do |inner|
|
|
153
|
+
queue_ptr = Bindings.rd_kafka_queue_get_main(inner)
|
|
154
|
+
Bindings.rd_kafka_queue_io_event_enable(queue_ptr, fd, payload, payload.bytesize)
|
|
155
|
+
Bindings.rd_kafka_queue_destroy(queue_ptr)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Enable IO event notifications on the background queue
|
|
160
|
+
# Librdkafka will write to your FD when the background queue transitions from empty to non-empty
|
|
161
|
+
#
|
|
162
|
+
# @note This method is incompatible with background polling threads.
|
|
163
|
+
# If background polling is enabled, use manual polling instead (e.g., consumer.poll)
|
|
164
|
+
#
|
|
165
|
+
# @param fd [Integer] your file descriptor (from IO.pipe or eventfd)
|
|
166
|
+
# @param payload [String] data to write to fd when queue has data (default: "\x01")
|
|
167
|
+
# @return [nil]
|
|
168
|
+
# @raise [ClosedInnerError] when the handle is closed
|
|
169
|
+
# @raise [RuntimeError] when background polling thread is active
|
|
170
|
+
def enable_background_queue_io_events(fd, payload = "\x01")
|
|
171
|
+
if @run_polling_thread
|
|
172
|
+
raise "Cannot enable IO events while background polling thread is active. " \
|
|
173
|
+
"Either disable background polling by setting run_polling_thread: false, " \
|
|
174
|
+
"or use manual polling with consumer.poll() instead of the FD API."
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
with_inner do |inner|
|
|
178
|
+
queue_ptr = Bindings.rd_kafka_queue_get_background(inner)
|
|
179
|
+
Bindings.rd_kafka_queue_io_event_enable(queue_ptr, fd, payload, payload.bytesize)
|
|
180
|
+
Bindings.rd_kafka_queue_destroy(queue_ptr)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Closes the native Kafka handle and cleans up resources
|
|
185
|
+
# @param object_id [Integer, nil] optional object ID (unused, for finalizer compatibility)
|
|
186
|
+
# @yield optional block to execute before destroying the handle
|
|
187
|
+
# @return [nil]
|
|
188
|
+
def close(object_id = nil)
|
|
105
189
|
return if closed?
|
|
106
190
|
|
|
107
191
|
synchronize do
|
|
@@ -112,8 +196,7 @@ module Rdkafka
|
|
|
112
196
|
# Indicate to polling thread that we're closing
|
|
113
197
|
@polling_thread[:closing] = true
|
|
114
198
|
|
|
115
|
-
# Wait for the polling thread to finish up,
|
|
116
|
-
# this can be aborted in practice if this
|
|
199
|
+
# Wait for the polling thread to finish up, this can be aborted in practice if this
|
|
117
200
|
# code runs from a finalizer.
|
|
118
201
|
@polling_thread.join
|
|
119
202
|
end
|
|
@@ -6,16 +6,16 @@ module Rdkafka
|
|
|
6
6
|
# producing a message.
|
|
7
7
|
class DeliveryHandle < Rdkafka::AbstractHandle
|
|
8
8
|
layout :pending, :bool,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
:topic_name, :pointer
|
|
9
|
+
:response, :int,
|
|
10
|
+
:partition, :int,
|
|
11
|
+
:offset, :int64
|
|
13
12
|
|
|
14
13
|
# @return [Object, nil] label set during message production or nil by default
|
|
15
14
|
attr_accessor :label
|
|
16
15
|
|
|
17
16
|
# @return [String] topic where we are trying to send the message
|
|
18
|
-
#
|
|
17
|
+
# Set in `#produce`, where the topic is known upfront. Keeping it as a Ruby attribute
|
|
18
|
+
# spares a per-message native string copy in the delivery callback.
|
|
19
19
|
attr_accessor :topic
|
|
20
20
|
|
|
21
21
|
# @return [String] the name of the operation (e.g. "delivery")
|
|
@@ -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
|
|
@@ -17,9 +17,9 @@ module Rdkafka
|
|
|
17
17
|
#
|
|
18
18
|
# 2. Edge case handling
|
|
19
19
|
# If a user configures `statistics.interval.ms` much higher than the default cache TTL
|
|
20
|
-
# (
|
|
21
|
-
# occur frequently enough, the cache entries will expire
|
|
22
|
-
# blocking refresh when needed.
|
|
20
|
+
# ({Defaults::PARTITIONS_COUNT_CACHE_TTL_MS}ms), the cache will still function correctly.
|
|
21
|
+
# When statistics updates don't occur frequently enough, the cache entries will expire
|
|
22
|
+
# naturally, triggering a blocking refresh when needed.
|
|
23
23
|
#
|
|
24
24
|
# 3. User configuration awareness
|
|
25
25
|
# The cache respects user-defined settings. If `topic.metadata.refresh.interval.ms` is
|
|
@@ -37,31 +37,40 @@ module Rdkafka
|
|
|
37
37
|
# contention in multi-threaded environments while ensuring data consistency.
|
|
38
38
|
#
|
|
39
39
|
# 6. Topic recreation handling
|
|
40
|
-
# If a topic is deleted and recreated with fewer partitions, the cache
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# This also aligns with the previous cache implementation.
|
|
40
|
+
# If a topic is deleted and recreated with fewer partitions, the cache keeps reporting the
|
|
41
|
+
# higher count only until the entry's TTL expires. The first refresh after expiry performs
|
|
42
|
+
# an authoritative metadata read and adopts the lower count. Within the TTL window a lower
|
|
43
|
+
# value is still ignored, so a transient or racy lower read cannot clobber a correct higher
|
|
44
|
+
# count.
|
|
46
45
|
class PartitionsCountCache
|
|
47
46
|
include Helpers::Time
|
|
48
47
|
|
|
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
48
|
# Creates a new partition count cache
|
|
57
49
|
#
|
|
58
|
-
# @param ttl [Integer]
|
|
59
|
-
|
|
50
|
+
# @param ttl [Integer, nil] DEPRECATED: Use ttl_ms instead.
|
|
51
|
+
# Time-to-live in seconds for cached values. Will be removed in v1.0.0.
|
|
52
|
+
# @param ttl_ms [Integer, nil] Time-to-live in milliseconds for cached values.
|
|
53
|
+
# Defaults to {Defaults::PARTITIONS_COUNT_CACHE_TTL_MS}.
|
|
54
|
+
def initialize(ttl = :not_provided, ttl_ms: :not_provided)
|
|
60
55
|
@counts = {}
|
|
61
56
|
@mutex_hash = {}
|
|
62
57
|
# Used only for @mutex_hash access to ensure thread-safety when creating new mutexes
|
|
63
58
|
@mutex_for_hash = Mutex.new
|
|
64
|
-
|
|
59
|
+
|
|
60
|
+
# Determine which TTL value to use
|
|
61
|
+
if ttl != :not_provided && ttl_ms != :not_provided
|
|
62
|
+
warn "DEPRECATION WARNING: Both ttl and ttl_ms were provided to PartitionsCountCache. " \
|
|
63
|
+
"Using ttl_ms. The ttl parameter is deprecated and will be removed in v1.0.0."
|
|
64
|
+
@ttl_ms = ttl_ms
|
|
65
|
+
elsif ttl != :not_provided
|
|
66
|
+
warn "DEPRECATION WARNING: ttl (seconds) parameter for PartitionsCountCache is deprecated. " \
|
|
67
|
+
"Use ttl_ms (milliseconds) instead. This parameter will be removed in v1.0.0."
|
|
68
|
+
@ttl_ms = (ttl * 1000).to_i
|
|
69
|
+
elsif ttl_ms == :not_provided
|
|
70
|
+
@ttl_ms = Defaults::PARTITIONS_COUNT_CACHE_TTL_MS
|
|
71
|
+
else
|
|
72
|
+
@ttl_ms = ttl_ms
|
|
73
|
+
end
|
|
65
74
|
end
|
|
66
75
|
|
|
67
76
|
# Reads partition count for a topic with automatic refresh when expired
|
|
@@ -81,28 +90,14 @@ module Rdkafka
|
|
|
81
90
|
current_info = @counts[topic]
|
|
82
91
|
|
|
83
92
|
if current_info.nil? || expired?(current_info[0])
|
|
93
|
+
# The cached entry is missing or expired, so the block performs an authoritative metadata
|
|
94
|
+
# read. We hand it to `set`, which adopts a higher count always and a lower count once the
|
|
95
|
+
# entry has expired (e.g. the topic was recreated with fewer partitions). We then return
|
|
96
|
+
# whatever `set` settled on so a concurrent refresh that wrote a higher value still wins.
|
|
84
97
|
new_count = yield
|
|
98
|
+
set(topic, new_count)
|
|
85
99
|
|
|
86
|
-
|
|
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
|
|
100
|
+
return @counts[topic][1]
|
|
106
101
|
end
|
|
107
102
|
|
|
108
103
|
current_info[1]
|
|
@@ -123,8 +118,11 @@ module Rdkafka
|
|
|
123
118
|
# First check outside mutex to avoid unnecessary locking
|
|
124
119
|
current_info = @counts[topic]
|
|
125
120
|
|
|
126
|
-
#
|
|
127
|
-
|
|
121
|
+
# Within the TTL window a lower value is treated as a stale/racy read and ignored, since
|
|
122
|
+
# partition counts only grow during normal operation. Once the entry has expired a lower
|
|
123
|
+
# value is an authoritative refresh (e.g. the topic was recreated with fewer partitions),
|
|
124
|
+
# so we fall through and adopt it below.
|
|
125
|
+
if current_info && new_count < current_info[1] && !expired?(current_info[0])
|
|
128
126
|
refresh_timestamp(topic)
|
|
129
127
|
|
|
130
128
|
return
|
|
@@ -137,18 +135,16 @@ module Rdkafka
|
|
|
137
135
|
|
|
138
136
|
if current_info.nil?
|
|
139
137
|
# Create new entry
|
|
140
|
-
@counts[topic] = [
|
|
138
|
+
@counts[topic] = [monotonic_now_ms, new_count]
|
|
139
|
+
elsif new_count > current_info[1] || expired?(current_info[0])
|
|
140
|
+
# A higher count always wins; a lower count is accepted only when the existing entry
|
|
141
|
+
# has expired, so a concurrent fresh higher value (which reset the timestamp) is never
|
|
142
|
+
# clobbered by a stale lower one.
|
|
143
|
+
current_info[0] = monotonic_now_ms
|
|
144
|
+
current_info[1] = new_count
|
|
141
145
|
else
|
|
142
|
-
|
|
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
|
|
146
|
+
# Same or lower count within the TTL window: refresh the timestamp only
|
|
147
|
+
current_info[0] = monotonic_now_ms
|
|
152
148
|
end
|
|
153
149
|
end
|
|
154
150
|
end
|
|
@@ -201,15 +197,15 @@ module Rdkafka
|
|
|
201
197
|
return unless current_info
|
|
202
198
|
|
|
203
199
|
# Update the timestamp in-place
|
|
204
|
-
current_info[0] =
|
|
200
|
+
current_info[0] = monotonic_now_ms
|
|
205
201
|
end
|
|
206
202
|
|
|
207
203
|
# Check if a timestamp has expired based on the TTL
|
|
208
204
|
#
|
|
209
|
-
# @param timestamp [
|
|
205
|
+
# @param timestamp [Integer] Monotonic timestamp in milliseconds to check
|
|
210
206
|
# @return [Boolean] true if expired, false otherwise
|
|
211
207
|
def expired?(timestamp)
|
|
212
|
-
|
|
208
|
+
monotonic_now_ms - timestamp > @ttl_ms
|
|
213
209
|
end
|
|
214
210
|
end
|
|
215
211
|
end
|