karafka-rdkafka 0.30.2-aarch64-linux-gnu → 0.32.0-aarch64-linux-gnu
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +100 -36
- data/README.md +3 -1
- data/docker-compose-ssl.yml +1 -0
- data/docker-compose.yml +1 -0
- data/ext/librdkafka.so +0 -0
- data/karafka-rdkafka.gemspec +5 -4
- data/lib/rdkafka/admin/acl_binding_result.rb +4 -16
- data/lib/rdkafka/admin/alter_consumer_group_offsets_report.rb +2 -2
- data/lib/rdkafka/admin/create_acl_report.rb +1 -3
- data/lib/rdkafka/admin/create_topic_report.rb +2 -6
- data/lib/rdkafka/admin/delete_consumer_group_offsets_report.rb +2 -2
- data/lib/rdkafka/admin/delete_groups_report.rb +2 -6
- data/lib/rdkafka/admin/delete_topic_report.rb +2 -6
- data/lib/rdkafka/admin/describe_cluster_handle.rb +31 -0
- data/lib/rdkafka/admin/describe_cluster_report.rb +51 -0
- data/lib/rdkafka/admin/describe_consumer_groups_handle.rb +31 -0
- data/lib/rdkafka/admin/describe_consumer_groups_report.rb +151 -0
- data/lib/rdkafka/admin/describe_result_parsing.rb +47 -0
- data/lib/rdkafka/admin/describe_topics_handle.rb +31 -0
- data/lib/rdkafka/admin/describe_topics_report.rb +118 -0
- data/lib/rdkafka/admin/list_consumer_group_offsets_handle.rb +31 -0
- data/lib/rdkafka/admin/list_consumer_group_offsets_report.rb +52 -0
- data/lib/rdkafka/admin.rb +424 -9
- data/lib/rdkafka/bindings/admin/acls.rb +85 -0
- data/lib/rdkafka/bindings/admin/configs.rb +37 -0
- data/lib/rdkafka/bindings/admin/list_offsets.rb +26 -0
- data/lib/rdkafka/bindings/admin.rb +211 -0
- data/lib/rdkafka/bindings/callbacks.rb +155 -0
- data/lib/rdkafka/bindings/configuration.rb +50 -0
- data/lib/rdkafka/bindings/consumer.rb +125 -0
- data/lib/rdkafka/bindings/errors.rb +17 -0
- data/lib/rdkafka/bindings/handle.rb +16 -0
- data/lib/rdkafka/bindings/messages.rb +28 -0
- data/lib/rdkafka/bindings/metadata.rb +17 -0
- data/lib/rdkafka/bindings/polling.rb +17 -0
- data/lib/rdkafka/bindings/producer.rb +82 -0
- data/lib/rdkafka/bindings/share_consumer.rb +67 -0
- data/lib/rdkafka/bindings/structs.rb +62 -0
- data/lib/rdkafka/bindings.rb +16 -775
- data/lib/rdkafka/callbacks/describe_cluster_handler.rb +36 -0
- data/lib/rdkafka/callbacks/describe_consumer_groups_handler.rb +36 -0
- data/lib/rdkafka/callbacks/describe_topics_handler.rb +36 -0
- data/lib/rdkafka/callbacks/list_consumer_group_offsets_handler.rb +62 -0
- data/lib/rdkafka/callbacks.rb +15 -7
- data/lib/rdkafka/clients.rb +13 -46
- data/lib/rdkafka/config.rb +70 -0
- data/lib/rdkafka/consumer/allocator.rb +0 -2
- data/lib/rdkafka/consumer/message.rb +6 -9
- data/lib/rdkafka/consumer/topic_partition_list.rb +7 -7
- data/lib/rdkafka/consumer.rb +76 -36
- data/lib/rdkafka/defaults.rb +6 -4
- data/lib/rdkafka/native_kafka.rb +45 -31
- data/lib/rdkafka/producer/delivery_handle.rb +18 -16
- data/lib/rdkafka/producer/delivery_report.rb +51 -1
- data/lib/rdkafka/producer/partitions_count_cache.rb +109 -26
- data/lib/rdkafka/producer.rb +141 -71
- data/lib/rdkafka/share_consumer.rb +70 -34
- data/lib/rdkafka/version.rb +3 -3
- data/lib/rdkafka.rb +14 -1
- data/package-lock.json +9 -9
- data/renovate.json +7 -0
- metadata +31 -31
data/lib/rdkafka/consumer.rb
CHANGED
|
@@ -17,6 +17,9 @@ module Rdkafka
|
|
|
17
17
|
include Helpers::Metadata
|
|
18
18
|
include Helpers::ListOffsets
|
|
19
19
|
|
|
20
|
+
# Reason passed to the error callback when closing the consumer fails
|
|
21
|
+
CLOSE_ERROR_REASON = "Consumer close failed"
|
|
22
|
+
|
|
20
23
|
# @private
|
|
21
24
|
# @param native_kafka [NativeKafka] wrapper around the native Kafka consumer handle
|
|
22
25
|
def initialize(native_kafka)
|
|
@@ -31,30 +34,61 @@ module Rdkafka
|
|
|
31
34
|
ObjectSpace.define_finalizer(self, self.class.finalizer(native_kafka, @consumer_queue_holder))
|
|
32
35
|
end
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
37
|
+
class << self
|
|
38
|
+
# Builds the GC finalizer for a consumer. It mirrors {#close}: close the consumer, destroy the
|
|
39
|
+
# consumer-queue reference, then destroy the native client. The default
|
|
40
|
+
# `NativeKafka#finalizer` went straight to `rd_kafka_destroy`, leaving the consumer-queue
|
|
41
|
+
# reference (from `rd_kafka_queue_get_consumer`, taken by `poll_batch`) dangling - which can
|
|
42
|
+
# make `rd_kafka_destroy` block inside the finalizer (process hang at GC/shutdown) or leak the
|
|
43
|
+
# handle.
|
|
44
|
+
#
|
|
45
|
+
# @private
|
|
46
|
+
# @param native_kafka [NativeKafka] the wrapped native client
|
|
47
|
+
# @param queue_holder [Array] single-element holder carrying the consumer queue pointer
|
|
48
|
+
# (or empty)
|
|
49
|
+
# @return [Proc] finalizer proc that must not reference the consumer instance
|
|
50
|
+
def finalizer(native_kafka, queue_holder)
|
|
51
|
+
proc do
|
|
52
|
+
next if native_kafka.closed?
|
|
53
|
+
|
|
54
|
+
close_response = nil
|
|
55
|
+
|
|
56
|
+
native_kafka.synchronize do |inner|
|
|
57
|
+
close_response = Rdkafka::Bindings.rd_kafka_consumer_close(inner)
|
|
58
|
+
|
|
59
|
+
queue = queue_holder[0]
|
|
60
|
+
Rdkafka::Bindings.rd_kafka_queue_destroy(queue) if queue
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
report_close_error(native_kafka, close_response)
|
|
64
|
+
|
|
65
|
+
native_kafka.close
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Reports a failed `rd_kafka_consumer_close` (e.g. of a fatally-errored consumer) through
|
|
70
|
+
# {Bindings::ErrorCallback}, the same path as errors emitted by librdkafka, so it reaches
|
|
71
|
+
# {Config.error_callback} in the shape the callback expects (libraries such as karafka-core
|
|
72
|
+
# replace `ErrorCallback`). It runs after the close, but before the native client is
|
|
73
|
+
# destroyed, as the callback reads the client name and fatal error from it. Close runs during
|
|
74
|
+
# shutdown and from the GC finalizer, so the error is reported, never raised.
|
|
75
|
+
#
|
|
76
|
+
# @private
|
|
77
|
+
# @param native_kafka [NativeKafka] the wrapped native client
|
|
78
|
+
# @param response [Integer] result code of `rd_kafka_consumer_close`
|
|
79
|
+
# @return [nil]
|
|
80
|
+
def report_close_error(native_kafka, response)
|
|
81
|
+
return if response == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
82
|
+
|
|
83
|
+
native_kafka.with_inner do |inner|
|
|
84
|
+
Rdkafka::Bindings::ErrorCallback.call(inner, response, CLOSE_ERROR_REASON, nil)
|
|
55
85
|
end
|
|
56
86
|
|
|
57
|
-
|
|
87
|
+
nil
|
|
88
|
+
rescue Exception => err
|
|
89
|
+
Rdkafka::Config.logger.error("Unhandled exception: #{err.class} - #{err.message}")
|
|
90
|
+
|
|
91
|
+
nil
|
|
58
92
|
end
|
|
59
93
|
end
|
|
60
94
|
|
|
@@ -114,7 +148,7 @@ module Rdkafka
|
|
|
114
148
|
|
|
115
149
|
# Polls for events in a non-blocking loop, yielding the count after each iteration.
|
|
116
150
|
#
|
|
117
|
-
# This method processes events (stats, errors, etc.) in a single GVL
|
|
151
|
+
# This method processes events (stats, errors, etc.) in a single GVL session,
|
|
118
152
|
# which is more efficient than repeated individual polls. It uses non-blocking polls
|
|
119
153
|
# internally (no GVL release between polls).
|
|
120
154
|
#
|
|
@@ -135,8 +169,9 @@ module Rdkafka
|
|
|
135
169
|
# consumer.events_poll_nb_each do |_count|
|
|
136
170
|
# :stop if monotonic_now >= deadline
|
|
137
171
|
# end
|
|
138
|
-
# @note This method
|
|
139
|
-
# Other
|
|
172
|
+
# @note This method runs inside `with_inner` and does not hold a lock while it loops.
|
|
173
|
+
# Other operations on the same client run concurrently. `close` waits until this
|
|
174
|
+
# method returns.
|
|
140
175
|
# @note This method is thread-safe as it uses @native_kafka.with_inner synchronization
|
|
141
176
|
# @note Do NOT use this if `consumer_poll_set` was set to `true`
|
|
142
177
|
def events_poll_nb_each
|
|
@@ -153,7 +188,7 @@ module Rdkafka
|
|
|
153
188
|
|
|
154
189
|
# Polls for messages in a non-blocking loop, yielding each message to the caller.
|
|
155
190
|
#
|
|
156
|
-
# This method processes messages in a single GVL
|
|
191
|
+
# This method processes messages in a single GVL session until the queue is empty
|
|
157
192
|
# or the caller returns `:stop`. It handles the message pointer lifecycle internally,
|
|
158
193
|
# ensuring proper cleanup via `rd_kafka_message_destroy`.
|
|
159
194
|
#
|
|
@@ -180,8 +215,9 @@ module Rdkafka
|
|
|
180
215
|
# `events_poll_nb_each` which uses `rd_kafka_poll` for event callbacks (delivery reports,
|
|
181
216
|
# statistics, etc.). For consumers, use this method to receive messages and
|
|
182
217
|
# `events_poll_nb_each` for processing background events.
|
|
183
|
-
# @note This method
|
|
184
|
-
#
|
|
218
|
+
# @note This method runs inside `with_inner` and does not hold a lock while it loops.
|
|
219
|
+
# Other operations on the same client run concurrently. `close` waits until this
|
|
220
|
+
# method returns.
|
|
185
221
|
# @note Timeout/max_messages logic should be implemented by the caller
|
|
186
222
|
def poll_nb_each
|
|
187
223
|
closed_consumer_check(__method__)
|
|
@@ -213,8 +249,10 @@ module Rdkafka
|
|
|
213
249
|
return if closed?
|
|
214
250
|
ObjectSpace.undefine_finalizer(self)
|
|
215
251
|
|
|
252
|
+
close_response = nil
|
|
253
|
+
|
|
216
254
|
@native_kafka.synchronize do |inner|
|
|
217
|
-
Rdkafka::Bindings.rd_kafka_consumer_close(inner)
|
|
255
|
+
close_response = Rdkafka::Bindings.rd_kafka_consumer_close(inner)
|
|
218
256
|
|
|
219
257
|
if @consumer_queue
|
|
220
258
|
Rdkafka::Bindings.rd_kafka_queue_destroy(@consumer_queue)
|
|
@@ -223,6 +261,8 @@ module Rdkafka
|
|
|
223
261
|
end
|
|
224
262
|
end
|
|
225
263
|
|
|
264
|
+
self.class.report_close_error(@native_kafka, close_response)
|
|
265
|
+
|
|
226
266
|
@native_kafka.close
|
|
227
267
|
end
|
|
228
268
|
|
|
@@ -469,12 +509,12 @@ module Rdkafka
|
|
|
469
509
|
|
|
470
510
|
# Query broker for low (oldest/beginning) and high (newest/end) offsets for a partition.
|
|
471
511
|
#
|
|
472
|
-
# @param
|
|
512
|
+
# @param topic_name [String] The topic to query
|
|
473
513
|
# @param partition [Integer] The partition to query
|
|
474
514
|
# @param timeout_ms [Integer] The timeout for querying the broker
|
|
475
515
|
# @return [Integer] The low and high watermark
|
|
476
516
|
# @raise [RdkafkaError] When querying the broker fails.
|
|
477
|
-
def query_watermark_offsets(
|
|
517
|
+
def query_watermark_offsets(topic_name, partition, timeout_ms = Defaults::CONSUMER_QUERY_WATERMARK_TIMEOUT_MS)
|
|
478
518
|
closed_consumer_check(__method__)
|
|
479
519
|
|
|
480
520
|
low = FFI::MemoryPointer.new(:int64, 1)
|
|
@@ -483,13 +523,13 @@ module Rdkafka
|
|
|
483
523
|
@native_kafka.with_inner do |inner|
|
|
484
524
|
response = Rdkafka::Bindings.rd_kafka_query_watermark_offsets(
|
|
485
525
|
inner,
|
|
486
|
-
|
|
526
|
+
topic_name,
|
|
487
527
|
partition,
|
|
488
528
|
low,
|
|
489
529
|
high,
|
|
490
530
|
timeout_ms
|
|
491
531
|
)
|
|
492
|
-
Rdkafka::RdkafkaError.validate!(response, "Error querying watermark offsets for partition #{partition} of #{
|
|
532
|
+
Rdkafka::RdkafkaError.validate!(response, "Error querying watermark offsets for partition #{partition} of #{topic_name}", client_ptr: inner)
|
|
493
533
|
end
|
|
494
534
|
|
|
495
535
|
[low.read_array_of_int64(1).first, high.read_array_of_int64(1).first]
|
|
@@ -651,12 +691,12 @@ module Rdkafka
|
|
|
651
691
|
# Seek to a particular message by providing the topic, partition and offset. The next poll on
|
|
652
692
|
# the topic/partition will return the message at the given offset.
|
|
653
693
|
#
|
|
654
|
-
# @param
|
|
694
|
+
# @param topic_name [String] The topic in which to seek
|
|
655
695
|
# @param partition [Integer] The partition number to seek
|
|
656
696
|
# @param offset [Integer] The partition offset to seek
|
|
657
697
|
# @return [nil]
|
|
658
698
|
# @raise [RdkafkaError] When seeking fails
|
|
659
|
-
def seek_by(
|
|
699
|
+
def seek_by(topic_name, partition, offset)
|
|
660
700
|
closed_consumer_check(__method__)
|
|
661
701
|
|
|
662
702
|
# rd_kafka_offset_store is one of the few calls that does not support
|
|
@@ -664,7 +704,7 @@ module Rdkafka
|
|
|
664
704
|
native_topic = @native_kafka.with_inner do |inner|
|
|
665
705
|
Rdkafka::Bindings.rd_kafka_topic_new(
|
|
666
706
|
inner,
|
|
667
|
-
|
|
707
|
+
topic_name,
|
|
668
708
|
nil
|
|
669
709
|
)
|
|
670
710
|
end
|
data/lib/rdkafka/defaults.rb
CHANGED
|
@@ -114,12 +114,14 @@ module Rdkafka
|
|
|
114
114
|
# @see Config#admin
|
|
115
115
|
NATIVE_KAFKA_POLL_TIMEOUT_MS = 100
|
|
116
116
|
|
|
117
|
-
# Sleep interval used in NativeKafka#synchronize wait loop
|
|
118
|
-
# @see NativeKafka#synchronize
|
|
119
|
-
NATIVE_KAFKA_SYNCHRONIZE_SLEEP_INTERVAL_MS = 10
|
|
120
|
-
|
|
121
117
|
# TTL for partitions count cache entries (30 seconds)
|
|
122
118
|
# @see Producer::PartitionsCountCache
|
|
123
119
|
PARTITIONS_COUNT_CACHE_TTL_MS = 30_000
|
|
120
|
+
|
|
121
|
+
# How long an expired partitions count is served after a refresh failed on a transient
|
|
122
|
+
# (e.g. transport) error, before the next refresh attempt. Longer than one failed metadata
|
|
123
|
+
# refresh (up to ~7 s with retries), so produces are not blocked most of the time
|
|
124
|
+
# @see Producer::PartitionsCountCache#get
|
|
125
|
+
PARTITIONS_COUNT_CACHE_STALE_BACKOFF_MS = 10_000
|
|
124
126
|
end
|
|
125
127
|
end
|
data/lib/rdkafka/native_kafka.rb
CHANGED
|
@@ -25,21 +25,15 @@ module Rdkafka
|
|
|
25
25
|
@access_mutex = Mutex.new
|
|
26
26
|
# Lock around internal polling
|
|
27
27
|
@poll_mutex = Mutex.new
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# We do not use the same mutex, because it could create a deadlock when an already
|
|
39
|
-
# incremented operation cannot decrement because `@access_lock` is now owned by a different
|
|
40
|
-
# thread in a synchronized mode and the synchronized mode is waiting on the decrement.
|
|
41
|
-
@decrement_mutex = Mutex.new
|
|
42
|
-
# counter for operations in progress using inner
|
|
28
|
+
# Signalled when `#synchronize` ends and, only while `#synchronize` waits, when the operations
|
|
29
|
+
# in progress counter reaches zero: no one else waits for zero, so other decrements skip the
|
|
30
|
+
# broadcast. `#synchronize` waits on it with `@access_mutex` released, so operations that
|
|
31
|
+
# started before it can still decrement the counter.
|
|
32
|
+
@operations_done = ConditionVariable.new
|
|
33
|
+
# Set while `#synchronize` runs, so no new operation starts until it ends
|
|
34
|
+
@synchronizing = false
|
|
35
|
+
# counter for operations in progress using inner, changed only under `@access_mutex`, so no
|
|
36
|
+
# update is lost on Rubies without a GVL (JRuby)
|
|
43
37
|
@operations_in_progress = 0
|
|
44
38
|
|
|
45
39
|
@run_polling_thread = run_polling_thread
|
|
@@ -63,8 +57,7 @@ module Rdkafka
|
|
|
63
57
|
Rdkafka::Bindings.rd_kafka_poll(@inner, 0)
|
|
64
58
|
|
|
65
59
|
if @run_polling_thread
|
|
66
|
-
# Start thread to poll client for delivery callbacks,
|
|
67
|
-
# not used in consumer.
|
|
60
|
+
# Start thread to poll client for delivery callbacks, not used in consumer.
|
|
68
61
|
@polling_thread = Thread.new do
|
|
69
62
|
loop do
|
|
70
63
|
@poll_mutex.synchronize do
|
|
@@ -90,15 +83,29 @@ module Rdkafka
|
|
|
90
83
|
# @return [Object] the result of the block
|
|
91
84
|
# @raise [ClosedInnerError] when the inner handle is nil
|
|
92
85
|
def with_inner
|
|
93
|
-
|
|
86
|
+
owned = @access_mutex.owned?
|
|
87
|
+
|
|
88
|
+
if owned
|
|
94
89
|
@operations_in_progress += 1
|
|
95
90
|
else
|
|
96
|
-
@access_mutex.synchronize
|
|
91
|
+
@access_mutex.synchronize do
|
|
92
|
+
@operations_done.wait(@access_mutex) while @synchronizing
|
|
93
|
+
@operations_in_progress += 1
|
|
94
|
+
end
|
|
97
95
|
end
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
begin
|
|
98
|
+
@inner.nil? ? raise(ClosedInnerError) : yield(@inner)
|
|
99
|
+
ensure
|
|
100
|
+
if owned
|
|
101
|
+
@operations_in_progress -= 1
|
|
102
|
+
else
|
|
103
|
+
@access_mutex.synchronize do
|
|
104
|
+
@operations_in_progress -= 1
|
|
105
|
+
@operations_done.broadcast if @synchronizing && @operations_in_progress.zero?
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
102
109
|
end
|
|
103
110
|
|
|
104
111
|
# Executes a block while holding exclusive access to the native Kafka handle
|
|
@@ -107,13 +114,21 @@ module Rdkafka
|
|
|
107
114
|
# @return [Object] the result of the block
|
|
108
115
|
def synchronize(&block)
|
|
109
116
|
@access_mutex.synchronize do
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
@operations_done.wait(@access_mutex) while @synchronizing
|
|
118
|
+
@synchronizing = true
|
|
119
|
+
|
|
120
|
+
begin
|
|
121
|
+
# Wait for any commands using the inner to finish
|
|
122
|
+
# This can take a while on blocking operations like polling but is essential not to
|
|
123
|
+
# proceed with certain types of operations like resources destruction as it can cause the
|
|
124
|
+
# process to hang or crash
|
|
125
|
+
@operations_done.wait(@access_mutex) until @operations_in_progress.zero?
|
|
126
|
+
|
|
127
|
+
with_inner(&block)
|
|
128
|
+
ensure
|
|
129
|
+
@synchronizing = false
|
|
130
|
+
@operations_done.broadcast
|
|
131
|
+
end
|
|
117
132
|
end
|
|
118
133
|
end
|
|
119
134
|
|
|
@@ -210,8 +225,7 @@ module Rdkafka
|
|
|
210
225
|
# Indicate to polling thread that we're closing
|
|
211
226
|
@polling_thread[:closing] = true
|
|
212
227
|
|
|
213
|
-
# Wait for the polling thread to finish up,
|
|
214
|
-
# this can be aborted in practice if this
|
|
228
|
+
# Wait for the polling thread to finish up, this can be aborted in practice if this
|
|
215
229
|
# code runs from a finalizer.
|
|
216
230
|
@polling_thread.join
|
|
217
231
|
end
|
|
@@ -7,7 +7,10 @@ module Rdkafka
|
|
|
7
7
|
layout :pending, :bool,
|
|
8
8
|
:response, :int,
|
|
9
9
|
:partition, :int,
|
|
10
|
-
:offset, :int64
|
|
10
|
+
:offset, :int64,
|
|
11
|
+
:status, :int,
|
|
12
|
+
:latency, :int64,
|
|
13
|
+
:broker_id, :int32
|
|
11
14
|
|
|
12
15
|
# @return [Object, nil] label set during message production or nil by default
|
|
13
16
|
attr_accessor :label
|
|
@@ -24,23 +27,22 @@ module Rdkafka
|
|
|
24
27
|
|
|
25
28
|
# @return [DeliveryReport] a report on the delivery of the message
|
|
26
29
|
def create_result
|
|
27
|
-
if self[:response] == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
28
|
-
|
|
29
|
-
self[:partition],
|
|
30
|
-
self[:offset],
|
|
31
|
-
topic,
|
|
32
|
-
nil,
|
|
33
|
-
label
|
|
34
|
-
)
|
|
30
|
+
error = if self[:response] == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
31
|
+
nil
|
|
35
32
|
else
|
|
36
|
-
|
|
37
|
-
self[:partition],
|
|
38
|
-
self[:offset],
|
|
39
|
-
topic,
|
|
40
|
-
Rdkafka::RdkafkaError.build(self[:response]),
|
|
41
|
-
label
|
|
42
|
-
)
|
|
33
|
+
Rdkafka::RdkafkaError.build(self[:response])
|
|
43
34
|
end
|
|
35
|
+
|
|
36
|
+
DeliveryReport.new(
|
|
37
|
+
self[:partition],
|
|
38
|
+
self[:offset],
|
|
39
|
+
topic,
|
|
40
|
+
error,
|
|
41
|
+
label,
|
|
42
|
+
status: self[:status],
|
|
43
|
+
latency: self[:latency],
|
|
44
|
+
broker_id: self[:broker_id]
|
|
45
|
+
)
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
end
|
|
@@ -25,6 +25,26 @@ module Rdkafka
|
|
|
25
25
|
# @return [Object, nil] label set during message production or nil by default
|
|
26
26
|
attr_reader :label
|
|
27
27
|
|
|
28
|
+
# Persistence status of the message, one of `Bindings::RD_KAFKA_MSG_STATUS_*`:
|
|
29
|
+
# - `RD_KAFKA_MSG_STATUS_NOT_PERSISTED` - the message was not written to the broker. A retry
|
|
30
|
+
# risks reordering, but not a duplicate.
|
|
31
|
+
# - `RD_KAFKA_MSG_STATUS_POSSIBLY_PERSISTED` - the message may have been written (e.g. the
|
|
32
|
+
# request timed out before an ack). A retry risks reordering and a duplicate.
|
|
33
|
+
# - `RD_KAFKA_MSG_STATUS_PERSISTED` - the broker acknowledged the message. Trust this only
|
|
34
|
+
# with `acks=all`.
|
|
35
|
+
# `nil` when the report was not built from a delivery callback.
|
|
36
|
+
# @return [Integer, nil]
|
|
37
|
+
attr_reader :status
|
|
38
|
+
|
|
39
|
+
# Time from the `produce` call until the delivery report, in microseconds, or `nil` when
|
|
40
|
+
# not available
|
|
41
|
+
# @return [Integer, nil]
|
|
42
|
+
attr_reader :latency
|
|
43
|
+
|
|
44
|
+
# Id of the broker the message was produced to, or `nil` when not known
|
|
45
|
+
# @return [Integer, nil]
|
|
46
|
+
attr_reader :broker_id
|
|
47
|
+
|
|
28
48
|
# We alias the `#topic_name` under `#topic` to make this consistent with `Consumer::Message`
|
|
29
49
|
# where the topic name is under `#topic` method. That way we have a consistent name that
|
|
30
50
|
# is present in both places
|
|
@@ -38,12 +58,42 @@ module Rdkafka
|
|
|
38
58
|
# @param topic_name [String, nil] topic name
|
|
39
59
|
# @param error [Integer, nil] error code if any
|
|
40
60
|
# @param label [Object, nil] user-defined label
|
|
41
|
-
|
|
61
|
+
# @param status [Integer, nil] `Bindings::RD_KAFKA_MSG_STATUS_*` persistence status
|
|
62
|
+
# @param latency [Integer, nil] produce latency in microseconds, negative when not available
|
|
63
|
+
# @param broker_id [Integer, nil] broker id, negative when not known
|
|
64
|
+
def initialize(
|
|
65
|
+
partition,
|
|
66
|
+
offset,
|
|
67
|
+
topic_name = nil,
|
|
68
|
+
error = nil,
|
|
69
|
+
label = nil,
|
|
70
|
+
status: nil,
|
|
71
|
+
latency: nil,
|
|
72
|
+
broker_id: nil
|
|
73
|
+
)
|
|
42
74
|
@partition = partition
|
|
43
75
|
@offset = offset
|
|
44
76
|
@topic_name = topic_name
|
|
45
77
|
@error = error
|
|
46
78
|
@label = label
|
|
79
|
+
@status = status
|
|
80
|
+
@latency = (latency.nil? || latency.negative?) ? nil : latency
|
|
81
|
+
@broker_id = (broker_id.nil? || broker_id.negative?) ? nil : broker_id
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [Boolean] true when the broker acknowledged the message
|
|
85
|
+
def persisted?
|
|
86
|
+
status == Bindings::RD_KAFKA_MSG_STATUS_PERSISTED
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @return [Boolean] true when the message may have been written, so a retry may duplicate it
|
|
90
|
+
def possibly_persisted?
|
|
91
|
+
status == Bindings::RD_KAFKA_MSG_STATUS_POSSIBLY_PERSISTED
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @return [Boolean] true when the message was not written, so a retry does not duplicate it
|
|
95
|
+
def not_persisted?
|
|
96
|
+
status == Bindings::RD_KAFKA_MSG_STATUS_NOT_PERSISTED
|
|
47
97
|
end
|
|
48
98
|
end
|
|
49
99
|
end
|