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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +100 -36
  3. data/README.md +3 -1
  4. data/docker-compose-ssl.yml +1 -0
  5. data/docker-compose.yml +1 -0
  6. data/ext/librdkafka.so +0 -0
  7. data/karafka-rdkafka.gemspec +5 -4
  8. data/lib/rdkafka/admin/acl_binding_result.rb +4 -16
  9. data/lib/rdkafka/admin/alter_consumer_group_offsets_report.rb +2 -2
  10. data/lib/rdkafka/admin/create_acl_report.rb +1 -3
  11. data/lib/rdkafka/admin/create_topic_report.rb +2 -6
  12. data/lib/rdkafka/admin/delete_consumer_group_offsets_report.rb +2 -2
  13. data/lib/rdkafka/admin/delete_groups_report.rb +2 -6
  14. data/lib/rdkafka/admin/delete_topic_report.rb +2 -6
  15. data/lib/rdkafka/admin/describe_cluster_handle.rb +31 -0
  16. data/lib/rdkafka/admin/describe_cluster_report.rb +51 -0
  17. data/lib/rdkafka/admin/describe_consumer_groups_handle.rb +31 -0
  18. data/lib/rdkafka/admin/describe_consumer_groups_report.rb +151 -0
  19. data/lib/rdkafka/admin/describe_result_parsing.rb +47 -0
  20. data/lib/rdkafka/admin/describe_topics_handle.rb +31 -0
  21. data/lib/rdkafka/admin/describe_topics_report.rb +118 -0
  22. data/lib/rdkafka/admin/list_consumer_group_offsets_handle.rb +31 -0
  23. data/lib/rdkafka/admin/list_consumer_group_offsets_report.rb +52 -0
  24. data/lib/rdkafka/admin.rb +424 -9
  25. data/lib/rdkafka/bindings/admin/acls.rb +85 -0
  26. data/lib/rdkafka/bindings/admin/configs.rb +37 -0
  27. data/lib/rdkafka/bindings/admin/list_offsets.rb +26 -0
  28. data/lib/rdkafka/bindings/admin.rb +211 -0
  29. data/lib/rdkafka/bindings/callbacks.rb +155 -0
  30. data/lib/rdkafka/bindings/configuration.rb +50 -0
  31. data/lib/rdkafka/bindings/consumer.rb +125 -0
  32. data/lib/rdkafka/bindings/errors.rb +17 -0
  33. data/lib/rdkafka/bindings/handle.rb +16 -0
  34. data/lib/rdkafka/bindings/messages.rb +28 -0
  35. data/lib/rdkafka/bindings/metadata.rb +17 -0
  36. data/lib/rdkafka/bindings/polling.rb +17 -0
  37. data/lib/rdkafka/bindings/producer.rb +82 -0
  38. data/lib/rdkafka/bindings/share_consumer.rb +67 -0
  39. data/lib/rdkafka/bindings/structs.rb +62 -0
  40. data/lib/rdkafka/bindings.rb +16 -775
  41. data/lib/rdkafka/callbacks/describe_cluster_handler.rb +36 -0
  42. data/lib/rdkafka/callbacks/describe_consumer_groups_handler.rb +36 -0
  43. data/lib/rdkafka/callbacks/describe_topics_handler.rb +36 -0
  44. data/lib/rdkafka/callbacks/list_consumer_group_offsets_handler.rb +62 -0
  45. data/lib/rdkafka/callbacks.rb +15 -7
  46. data/lib/rdkafka/clients.rb +13 -46
  47. data/lib/rdkafka/config.rb +70 -0
  48. data/lib/rdkafka/consumer/allocator.rb +0 -2
  49. data/lib/rdkafka/consumer/message.rb +6 -9
  50. data/lib/rdkafka/consumer/topic_partition_list.rb +7 -7
  51. data/lib/rdkafka/consumer.rb +76 -36
  52. data/lib/rdkafka/defaults.rb +6 -4
  53. data/lib/rdkafka/native_kafka.rb +45 -31
  54. data/lib/rdkafka/producer/delivery_handle.rb +18 -16
  55. data/lib/rdkafka/producer/delivery_report.rb +51 -1
  56. data/lib/rdkafka/producer/partitions_count_cache.rb +109 -26
  57. data/lib/rdkafka/producer.rb +141 -71
  58. data/lib/rdkafka/share_consumer.rb +70 -34
  59. data/lib/rdkafka/version.rb +3 -3
  60. data/lib/rdkafka.rb +14 -1
  61. data/package-lock.json +9 -9
  62. data/renovate.json +7 -0
  63. metadata +31 -31
@@ -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
- # Builds the GC finalizer for a consumer. It mirrors {#close}: close the consumer, destroy the
35
- # consumer-queue reference, then destroy the native client. The default `NativeKafka#finalizer`
36
- # went straight to `rd_kafka_destroy`, leaving the consumer-queue reference (from
37
- # `rd_kafka_queue_get_consumer`, taken by `poll_batch`) dangling - which can make
38
- # `rd_kafka_destroy` block inside the finalizer (process hang at GC/shutdown) or leak the
39
- # handle.
40
- #
41
- # @private
42
- # @param native_kafka [NativeKafka] the wrapped native client
43
- # @param queue_holder [Array] single-element holder carrying the consumer queue pointer
44
- # (or empty)
45
- # @return [Proc] finalizer proc that must not reference the consumer instance
46
- def self.finalizer(native_kafka, queue_holder)
47
- proc do
48
- next if native_kafka.closed?
49
-
50
- native_kafka.synchronize do |inner|
51
- Rdkafka::Bindings.rd_kafka_consumer_close(inner)
52
-
53
- queue = queue_holder[0]
54
- Rdkafka::Bindings.rd_kafka_queue_destroy(queue) if queue
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
- native_kafka.close
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/mutex session,
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 holds the inner lock until the queue is empty or `:stop` is returned.
139
- # Other consumer operations will wait until this method returns.
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/mutex session until the queue is empty
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 holds the inner lock for the duration. Other consumer operations
184
- # will wait until this method returns.
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 topic [String] The topic to query
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(topic, partition, timeout_ms = Defaults::CONSUMER_QUERY_WATERMARK_TIMEOUT_MS)
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
- topic,
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 #{topic}", client_ptr: inner)
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 topic [String] The topic in which to seek
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(topic, partition, offset)
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
- topic,
707
+ topic_name,
668
708
  nil
669
709
  )
670
710
  end
@@ -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
@@ -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
- # Lock around decrementing the operations in progress counter
29
- # We have two mutexes - one for increment (`@access_mutex`) and one for decrement mutex
30
- # because they serve different purposes:
31
- #
32
- # - `@access_mutex` allows us to lock the execution and make sure that any operation within
33
- # the `#synchronize` is the only one running and that there are no other running
34
- # operations.
35
- # - `@decrement_mutex` ensures, that our decrement operation is thread-safe for any Ruby
36
- # implementation.
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
- if @access_mutex.owned?
86
+ owned = @access_mutex.owned?
87
+
88
+ if owned
94
89
  @operations_in_progress += 1
95
90
  else
96
- @access_mutex.synchronize { @operations_in_progress += 1 }
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
- @inner.nil? ? raise(ClosedInnerError) : yield(@inner)
100
- ensure
101
- @decrement_mutex.synchronize { @operations_in_progress -= 1 }
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
- # Wait for any commands using the inner to finish
111
- # This can take a while on blocking operations like polling but is essential not to proceed
112
- # with certain types of operations like resources destruction as it can cause the process
113
- # to hang or crash
114
- sleep(Defaults::NATIVE_KAFKA_SYNCHRONIZE_SLEEP_INTERVAL_MS / 1_000.0) until @operations_in_progress.zero?
115
-
116
- with_inner(&block)
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
- DeliveryReport.new(
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
- DeliveryReport.new(
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
- def initialize(partition, offset, topic_name = nil, error = nil, label = nil)
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