karafka-rdkafka 0.13.2 → 0.13.9

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 (59) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +9 -4
  4. data/.gitignore +4 -0
  5. data/.rspec +1 -0
  6. data/.ruby-gemset +1 -0
  7. data/.ruby-version +1 -0
  8. data/CHANGELOG.md +54 -26
  9. data/{LICENSE → MIT-LICENSE} +2 -1
  10. data/README.md +19 -20
  11. data/certs/cert_chain.pem +21 -21
  12. data/docker-compose.yml +16 -15
  13. data/ext/README.md +1 -1
  14. data/ext/Rakefile +1 -1
  15. data/karafka-rdkafka.gemspec +2 -2
  16. data/lib/rdkafka/abstract_handle.rb +41 -27
  17. data/lib/rdkafka/admin/create_partitions_handle.rb +6 -3
  18. data/lib/rdkafka/admin/create_topic_handle.rb +6 -3
  19. data/lib/rdkafka/admin/delete_topic_handle.rb +6 -3
  20. data/lib/rdkafka/admin.rb +6 -7
  21. data/lib/rdkafka/bindings.rb +24 -6
  22. data/lib/rdkafka/config.rb +53 -19
  23. data/lib/rdkafka/consumer/headers.rb +2 -4
  24. data/lib/rdkafka/consumer.rb +119 -93
  25. data/lib/rdkafka/error.rb +60 -1
  26. data/lib/rdkafka/helpers/time.rb +14 -0
  27. data/lib/rdkafka/metadata.rb +4 -4
  28. data/lib/rdkafka/native_kafka.rb +6 -1
  29. data/lib/rdkafka/producer/delivery_handle.rb +16 -1
  30. data/lib/rdkafka/producer/delivery_report.rb +3 -2
  31. data/lib/rdkafka/producer.rb +89 -17
  32. data/lib/rdkafka/version.rb +3 -3
  33. data/lib/rdkafka.rb +10 -1
  34. data/renovate.json +6 -0
  35. data/spec/rdkafka/abstract_handle_spec.rb +0 -2
  36. data/spec/rdkafka/admin/create_topic_handle_spec.rb +4 -4
  37. data/spec/rdkafka/admin/create_topic_report_spec.rb +0 -2
  38. data/spec/rdkafka/admin/delete_topic_handle_spec.rb +3 -3
  39. data/spec/rdkafka/admin/delete_topic_report_spec.rb +0 -2
  40. data/spec/rdkafka/admin_spec.rb +1 -2
  41. data/spec/rdkafka/bindings_spec.rb +0 -1
  42. data/spec/rdkafka/callbacks_spec.rb +0 -2
  43. data/spec/rdkafka/config_spec.rb +8 -2
  44. data/spec/rdkafka/consumer/headers_spec.rb +0 -2
  45. data/spec/rdkafka/consumer/message_spec.rb +0 -2
  46. data/spec/rdkafka/consumer/partition_spec.rb +0 -2
  47. data/spec/rdkafka/consumer/topic_partition_list_spec.rb +0 -2
  48. data/spec/rdkafka/consumer_spec.rb +122 -38
  49. data/spec/rdkafka/error_spec.rb +0 -2
  50. data/spec/rdkafka/metadata_spec.rb +2 -3
  51. data/spec/rdkafka/native_kafka_spec.rb +2 -3
  52. data/spec/rdkafka/producer/delivery_handle_spec.rb +15 -2
  53. data/spec/rdkafka/producer/delivery_report_spec.rb +0 -2
  54. data/spec/rdkafka/producer_spec.rb +293 -1
  55. data/spec/spec_helper.rb +7 -1
  56. data.tar.gz.sig +0 -0
  57. metadata +31 -28
  58. metadata.gz.sig +0 -0
  59. data/certs/karafka-pro.pem +0 -11
@@ -12,6 +12,7 @@ module Rdkafka
12
12
  # `each_slice` to consume batches of messages.
13
13
  class Consumer
14
14
  include Enumerable
15
+ include Helpers::Time
15
16
 
16
17
  # @private
17
18
  def initialize(native_kafka)
@@ -29,6 +30,13 @@ module Rdkafka
29
30
  ->(_) { close }
30
31
  end
31
32
 
33
+ # @return [String] consumer name
34
+ def name
35
+ @name ||= @native_kafka.with_inner do |inner|
36
+ ::Rdkafka::Bindings.rd_kafka_name(inner)
37
+ end
38
+ end
39
+
32
40
  # Close this consumer
33
41
  # @return [nil]
34
42
  def close
@@ -47,13 +55,11 @@ module Rdkafka
47
55
  @native_kafka.closed?
48
56
  end
49
57
 
50
- # Subscribe to one or more topics letting Kafka handle partition assignments.
58
+ # Subscribes to one or more topics letting Kafka handle partition assignments.
51
59
  #
52
60
  # @param topics [Array<String>] One or more topic names
53
- #
54
- # @raise [RdkafkaError] When subscribing fails
55
- #
56
61
  # @return [nil]
62
+ # @raise [RdkafkaError] When subscribing fails
57
63
  def subscribe(*topics)
58
64
  closed_consumer_check(__method__)
59
65
 
@@ -68,36 +74,33 @@ module Rdkafka
68
74
  response = @native_kafka.with_inner do |inner|
69
75
  Rdkafka::Bindings.rd_kafka_subscribe(inner, tpl)
70
76
  end
71
- if response != 0
72
- raise Rdkafka::RdkafkaError.new(response, "Error subscribing to '#{topics.join(', ')}'")
73
- end
77
+
78
+ Rdkafka::RdkafkaError.validate!(response, "Error subscribing to '#{topics.join(', ')}'")
74
79
  ensure
75
80
  Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl) unless tpl.nil?
76
81
  end
77
82
 
78
83
  # Unsubscribe from all subscribed topics.
79
84
  #
80
- # @raise [RdkafkaError] When unsubscribing fails
81
- #
82
85
  # @return [nil]
86
+ # @raise [RdkafkaError] When unsubscribing fails
83
87
  def unsubscribe
84
88
  closed_consumer_check(__method__)
85
89
 
86
90
  response = @native_kafka.with_inner do |inner|
87
91
  Rdkafka::Bindings.rd_kafka_unsubscribe(inner)
88
92
  end
89
- if response != 0
90
- raise Rdkafka::RdkafkaError.new(response)
91
- end
93
+
94
+ Rdkafka::RdkafkaError.validate!(response)
95
+
96
+ nil
92
97
  end
93
98
 
94
99
  # Pause producing or consumption for the provided list of partitions
95
100
  #
96
101
  # @param list [TopicPartitionList] The topic with partitions to pause
97
- #
98
- # @raise [RdkafkaTopicPartitionListError] When pausing subscription fails.
99
- #
100
102
  # @return [nil]
103
+ # @raise [RdkafkaTopicPartitionListError] When pausing subscription fails.
101
104
  def pause(list)
102
105
  closed_consumer_check(__method__)
103
106
 
@@ -121,13 +124,11 @@ module Rdkafka
121
124
  end
122
125
  end
123
126
 
124
- # Resume producing consumption for the provided list of partitions
127
+ # Resumes producing consumption for the provided list of partitions
125
128
  #
126
129
  # @param list [TopicPartitionList] The topic with partitions to pause
127
- #
128
- # @raise [RdkafkaError] When resume subscription fails.
129
- #
130
130
  # @return [nil]
131
+ # @raise [RdkafkaError] When resume subscription fails.
131
132
  def resume(list)
132
133
  closed_consumer_check(__method__)
133
134
 
@@ -141,19 +142,19 @@ module Rdkafka
141
142
  response = @native_kafka.with_inner do |inner|
142
143
  Rdkafka::Bindings.rd_kafka_resume_partitions(inner, tpl)
143
144
  end
144
- if response != 0
145
- raise Rdkafka::RdkafkaError.new(response, "Error resume '#{list.to_h}'")
146
- end
145
+
146
+ Rdkafka::RdkafkaError.validate!(response, "Error resume '#{list.to_h}'")
147
+
148
+ nil
147
149
  ensure
148
150
  Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl)
149
151
  end
150
152
  end
151
153
 
152
- # Return the current subscription to topics and partitions
153
- #
154
- # @raise [RdkafkaError] When getting the subscription fails.
154
+ # Returns the current subscription to topics and partitions
155
155
  #
156
156
  # @return [TopicPartitionList]
157
+ # @raise [RdkafkaError] When getting the subscription fails.
157
158
  def subscription
158
159
  closed_consumer_check(__method__)
159
160
 
@@ -162,9 +163,7 @@ module Rdkafka
162
163
  Rdkafka::Bindings.rd_kafka_subscription(inner, ptr)
163
164
  end
164
165
 
165
- if response != 0
166
- raise Rdkafka::RdkafkaError.new(response)
167
- end
166
+ Rdkafka::RdkafkaError.validate!(response)
168
167
 
169
168
  native = ptr.read_pointer
170
169
 
@@ -178,7 +177,6 @@ module Rdkafka
178
177
  # Atomic assignment of partitions to consume
179
178
  #
180
179
  # @param list [TopicPartitionList] The topic with partitions to assign
181
- #
182
180
  # @raise [RdkafkaError] When assigning fails
183
181
  def assign(list)
184
182
  closed_consumer_check(__method__)
@@ -193,9 +191,8 @@ module Rdkafka
193
191
  response = @native_kafka.with_inner do |inner|
194
192
  Rdkafka::Bindings.rd_kafka_assign(inner, tpl)
195
193
  end
196
- if response != 0
197
- raise Rdkafka::RdkafkaError.new(response, "Error assigning '#{list.to_h}'")
198
- end
194
+
195
+ Rdkafka::RdkafkaError.validate!(response, "Error assigning '#{list.to_h}'")
199
196
  ensure
200
197
  Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl)
201
198
  end
@@ -203,9 +200,8 @@ module Rdkafka
203
200
 
204
201
  # Returns the current partition assignment.
205
202
  #
206
- # @raise [RdkafkaError] When getting the assignment fails.
207
- #
208
203
  # @return [TopicPartitionList]
204
+ # @raise [RdkafkaError] When getting the assignment fails.
209
205
  def assignment
210
206
  closed_consumer_check(__method__)
211
207
 
@@ -213,9 +209,8 @@ module Rdkafka
213
209
  response = @native_kafka.with_inner do |inner|
214
210
  Rdkafka::Bindings.rd_kafka_assignment(inner, ptr)
215
211
  end
216
- if response != 0
217
- raise Rdkafka::RdkafkaError.new(response)
218
- end
212
+
213
+ Rdkafka::RdkafkaError.validate!(response)
219
214
 
220
215
  tpl = ptr.read_pointer
221
216
 
@@ -240,14 +235,14 @@ module Rdkafka
240
235
  end
241
236
 
242
237
  # Return the current committed offset per partition for this consumer group.
243
- # The offset field of each requested partition will either be set to stored offset or to -1001 in case there was no stored offset for that partition.
238
+ # The offset field of each requested partition will either be set to stored offset or to -1001
239
+ # in case there was no stored offset for that partition.
244
240
  #
245
- # @param list [TopicPartitionList, nil] The topic with partitions to get the offsets for or nil to use the current subscription.
241
+ # @param list [TopicPartitionList, nil] The topic with partitions to get the offsets for or nil
242
+ # to use the current subscription.
246
243
  # @param timeout_ms [Integer] The timeout for fetching this information.
247
- #
248
- # @raise [RdkafkaError] When getting the committed positions fails.
249
- #
250
244
  # @return [TopicPartitionList]
245
+ # @raise [RdkafkaError] When getting the committed positions fails.
251
246
  def committed(list=nil, timeout_ms=1200)
252
247
  closed_consumer_check(__method__)
253
248
 
@@ -263,24 +258,48 @@ module Rdkafka
263
258
  response = @native_kafka.with_inner do |inner|
264
259
  Rdkafka::Bindings.rd_kafka_committed(inner, tpl, timeout_ms)
265
260
  end
266
- if response != 0
267
- raise Rdkafka::RdkafkaError.new(response)
268
- end
261
+
262
+ Rdkafka::RdkafkaError.validate!(response)
263
+
269
264
  TopicPartitionList.from_native_tpl(tpl)
270
265
  ensure
271
266
  Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl)
272
267
  end
273
268
  end
274
269
 
270
+ # Return the current positions (offsets) for topics and partitions.
271
+ # The offset field of each requested partition will be set to the offset of the last consumed message + 1, or nil in case there was no previous message.
272
+ #
273
+ # @param list [TopicPartitionList, nil] The topic with partitions to get the offsets for or nil to use the current subscription.
274
+ #
275
+ # @raise [RdkafkaError] When getting the positions fails.
276
+ #
277
+ # @return [TopicPartitionList]
278
+ def position(list=nil)
279
+ if list.nil?
280
+ list = assignment
281
+ elsif !list.is_a?(TopicPartitionList)
282
+ raise TypeError.new("list has to be nil or a TopicPartitionList")
283
+ end
284
+
285
+ tpl = list.to_native_tpl
286
+
287
+ response = @native_kafka.with_inner do |inner|
288
+ Rdkafka::Bindings.rd_kafka_position(inner, tpl)
289
+ end
290
+
291
+ Rdkafka::RdkafkaError.validate!(response)
292
+
293
+ TopicPartitionList.from_native_tpl(tpl)
294
+ end
295
+
275
296
  # Query broker for low (oldest/beginning) and high (newest/end) offsets for a partition.
276
297
  #
277
298
  # @param topic [String] The topic to query
278
299
  # @param partition [Integer] The partition to query
279
300
  # @param timeout_ms [Integer] The timeout for querying the broker
280
- #
281
- # @raise [RdkafkaError] When querying the broker fails.
282
- #
283
301
  # @return [Integer] The low and high watermark
302
+ # @raise [RdkafkaError] When querying the broker fails.
284
303
  def query_watermark_offsets(topic, partition, timeout_ms=200)
285
304
  closed_consumer_check(__method__)
286
305
 
@@ -297,9 +316,8 @@ module Rdkafka
297
316
  timeout_ms,
298
317
  )
299
318
  end
300
- if response != 0
301
- raise Rdkafka::RdkafkaError.new(response, "Error querying watermark offsets for partition #{partition} of #{topic}")
302
- end
319
+
320
+ Rdkafka::RdkafkaError.validate!(response, "Error querying watermark offsets for partition #{partition} of #{topic}")
303
321
 
304
322
  return low.read_array_of_int64(1).first, high.read_array_of_int64(1).first
305
323
  ensure
@@ -314,10 +332,9 @@ module Rdkafka
314
332
  #
315
333
  # @param topic_partition_list [TopicPartitionList] The list to calculate lag for.
316
334
  # @param watermark_timeout_ms [Integer] The timeout for each query watermark call.
317
- #
335
+ # @return [Hash<String, Hash<Integer, Integer>>] A hash containing all topics with the lag
336
+ # per partition
318
337
  # @raise [RdkafkaError] When querying the broker fails.
319
- #
320
- # @return [Hash<String, Hash<Integer, Integer>>] A hash containing all topics with the lag per partition
321
338
  def lag(topic_partition_list, watermark_timeout_ms=100)
322
339
  out = {}
323
340
 
@@ -327,7 +344,7 @@ module Rdkafka
327
344
  topic_out = {}
328
345
  partitions.each do |p|
329
346
  next if p.offset.nil?
330
- low, high = query_watermark_offsets(
347
+ _, high = query_watermark_offsets(
331
348
  topic,
332
349
  p.partition,
333
350
  watermark_timeout_ms
@@ -366,10 +383,8 @@ module Rdkafka
366
383
  # When using this `enable.auto.offset.store` should be set to `false` in the config.
367
384
  #
368
385
  # @param message [Rdkafka::Consumer::Message] The message which offset will be stored
369
- #
370
- # @raise [RdkafkaError] When storing the offset fails
371
- #
372
386
  # @return [nil]
387
+ # @raise [RdkafkaError] When storing the offset fails
373
388
  def store_offset(message)
374
389
  closed_consumer_check(__method__)
375
390
 
@@ -387,9 +402,10 @@ module Rdkafka
387
402
  message.partition,
388
403
  message.offset
389
404
  )
390
- if response != 0
391
- raise Rdkafka::RdkafkaError.new(response)
392
- end
405
+
406
+ Rdkafka::RdkafkaError.validate!(response)
407
+
408
+ nil
393
409
  ensure
394
410
  if native_topic && !native_topic.null?
395
411
  Rdkafka::Bindings.rd_kafka_topic_destroy(native_topic)
@@ -400,10 +416,8 @@ module Rdkafka
400
416
  # message at the given offset.
401
417
  #
402
418
  # @param message [Rdkafka::Consumer::Message] The message to which to seek
403
- #
404
- # @raise [RdkafkaError] When seeking fails
405
- #
406
419
  # @return [nil]
420
+ # @raise [RdkafkaError] When seeking fails
407
421
  def seek(message)
408
422
  closed_consumer_check(__method__)
409
423
 
@@ -422,9 +436,9 @@ module Rdkafka
422
436
  message.offset,
423
437
  0 # timeout
424
438
  )
425
- if response != 0
426
- raise Rdkafka::RdkafkaError.new(response)
427
- end
439
+ Rdkafka::RdkafkaError.validate!(response)
440
+
441
+ nil
428
442
  ensure
429
443
  if native_topic && !native_topic.null?
430
444
  Rdkafka::Bindings.rd_kafka_topic_destroy(native_topic)
@@ -455,9 +469,7 @@ module Rdkafka
455
469
  )
456
470
  end
457
471
 
458
- if response != 0
459
- raise Rdkafka::RdkafkaError.new(response)
460
- end
472
+ Rdkafka::RdkafkaError.validate!(response)
461
473
 
462
474
  TopicPartitionList.from_native_tpl(tpl)
463
475
  ensure
@@ -475,10 +487,8 @@ module Rdkafka
475
487
  #
476
488
  # @param list [TopicPartitionList,nil] The topic with partitions to commit
477
489
  # @param async [Boolean] Whether to commit async or wait for the commit to finish
478
- #
479
- # @raise [RdkafkaError] When committing fails
480
- #
481
490
  # @return [nil]
491
+ # @raise [RdkafkaError] When committing fails
482
492
  def commit(list=nil, async=false)
483
493
  closed_consumer_check(__method__)
484
494
 
@@ -492,9 +502,10 @@ module Rdkafka
492
502
  response = @native_kafka.with_inner do |inner|
493
503
  Rdkafka::Bindings.rd_kafka_commit(inner, tpl, async)
494
504
  end
495
- if response != 0
496
- raise Rdkafka::RdkafkaError.new(response)
497
- end
505
+
506
+ Rdkafka::RdkafkaError.validate!(response)
507
+
508
+ nil
498
509
  ensure
499
510
  Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl) if tpl
500
511
  end
@@ -503,10 +514,8 @@ module Rdkafka
503
514
  # Poll for the next message on one of the subscribed topics
504
515
  #
505
516
  # @param timeout_ms [Integer] Timeout of this poll
506
- #
507
- # @raise [RdkafkaError] When polling fails
508
- #
509
517
  # @return [Message, nil] A message or nil if there was no new message within the timeout
518
+ # @raise [RdkafkaError] When polling fails
510
519
  def poll(timeout_ms)
511
520
  closed_consumer_check(__method__)
512
521
 
@@ -519,9 +528,9 @@ module Rdkafka
519
528
  # Create struct wrapper
520
529
  native_message = Rdkafka::Bindings::Message.new(message_ptr)
521
530
  # Raise error if needed
522
- if native_message[:err] != 0
523
- raise Rdkafka::RdkafkaError.new(native_message[:err])
524
- end
531
+
532
+ Rdkafka::RdkafkaError.validate!(native_message[:err])
533
+
525
534
  # Create a message to pass out
526
535
  Rdkafka::Consumer::Message.new(native_message)
527
536
  end
@@ -532,17 +541,40 @@ module Rdkafka
532
541
  end
533
542
  end
534
543
 
544
+ # Polls the main rdkafka queue (not the consumer one). Do **NOT** use it if `consumer_poll_set`
545
+ # was set to `true`.
546
+ #
547
+ # Events will cause application-provided callbacks to be called.
548
+ #
549
+ # Events (in the context of the consumer):
550
+ # - error callbacks
551
+ # - stats callbacks
552
+ # - any other callbacks supported by librdkafka that are not part of the consumer_poll, that
553
+ # would have a callback configured and activated.
554
+ #
555
+ # This method needs to be called at regular intervals to serve any queued callbacks waiting to
556
+ # be called. When in use, does **NOT** replace `#poll` but needs to run complementary with it.
557
+ #
558
+ # @param timeout_ms [Integer] poll timeout. If set to 0 will run async, when set to -1 will
559
+ # block until any events available.
560
+ #
561
+ # @note This method technically should be called `#poll` and the current `#poll` should be
562
+ # called `#consumer_poll` though we keep the current naming convention to make it backward
563
+ # compatible.
564
+ def events_poll(timeout_ms = 0)
565
+ @native_kafka.with_inner do |inner|
566
+ Rdkafka::Bindings.rd_kafka_poll(inner, timeout_ms)
567
+ end
568
+ end
569
+
535
570
  # Poll for new messages and yield for each received one. Iteration
536
571
  # will end when the consumer is closed.
537
572
  #
538
- # If `enable.partition.eof` is turned on in the config this will raise an
539
- # error when an eof is reached, so you probably want to disable that when
540
- # using this method of iteration.
573
+ # If `enable.partition.eof` is turned on in the config this will raise an error when an eof is
574
+ # reached, so you probably want to disable that when using this method of iteration.
541
575
  #
542
576
  # @raise [RdkafkaError] When polling fails
543
- #
544
577
  # @yieldparam message [Message] Received message
545
- #
546
578
  # @return [nil]
547
579
  def each
548
580
  loop do
@@ -595,9 +627,7 @@ module Rdkafka
595
627
  # that you may or may not see again.
596
628
  #
597
629
  # @param max_items [Integer] Maximum size of the yielded array of messages
598
- #
599
630
  # @param bytes_threshold [Integer] Threshold number of total message bytes in the yielded array of messages
600
- #
601
631
  # @param timeout_ms [Integer] max time to wait for up to max_items
602
632
  #
603
633
  # @raise [RdkafkaError] When polling fails
@@ -644,10 +674,6 @@ module Rdkafka
644
674
  end
645
675
 
646
676
  private
647
- def monotonic_now
648
- # needed because Time.now can go backwards
649
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
650
- end
651
677
 
652
678
  def closed_consumer_check(method)
653
679
  raise Rdkafka::ClosedConsumerError.new(method) if closed?
data/lib/rdkafka/error.rb CHANGED
@@ -18,12 +18,59 @@ module Rdkafka
18
18
  # @return [String]
19
19
  attr_reader :broker_message
20
20
 
21
+ class << self
22
+ def build_from_c(response_ptr, message_prefix = nil, broker_message: nil)
23
+ code = Rdkafka::Bindings.rd_kafka_error_code(response_ptr)
24
+
25
+ return false if code.zero?
26
+
27
+ message = broker_message || Rdkafka::Bindings.rd_kafka_err2str(code)
28
+ fatal = !Rdkafka::Bindings.rd_kafka_error_is_fatal(response_ptr).zero?
29
+ retryable = !Rdkafka::Bindings.rd_kafka_error_is_retriable(response_ptr).zero?
30
+ abortable = !Rdkafka::Bindings.rd_kafka_error_txn_requires_abort(response_ptr).zero?
31
+
32
+ Rdkafka::Bindings.rd_kafka_error_destroy(response_ptr)
33
+
34
+ new(
35
+ code,
36
+ message_prefix,
37
+ broker_message: message,
38
+ fatal: fatal,
39
+ retryable: retryable,
40
+ abortable: abortable
41
+ )
42
+ end
43
+
44
+ def build(response_ptr_or_code, message_prefix = nil, broker_message: nil)
45
+ if response_ptr_or_code.is_a?(Integer)
46
+ response_ptr_or_code.zero? ? false : new(response_ptr_or_code, message_prefix, broker_message: broker_message)
47
+ else
48
+ build_from_c(response_ptr_or_code, message_prefix)
49
+ end
50
+ end
51
+
52
+ def validate!(response_ptr_or_code, message_prefix = nil, broker_message: nil)
53
+ error = build(response_ptr_or_code, message_prefix, broker_message: broker_message)
54
+ error ? raise(error) : false
55
+ end
56
+ end
57
+
21
58
  # @private
22
- def initialize(response, message_prefix=nil, broker_message: nil)
59
+ def initialize(
60
+ response,
61
+ message_prefix=nil,
62
+ broker_message: nil,
63
+ fatal: false,
64
+ retryable: false,
65
+ abortable: false
66
+ )
23
67
  raise TypeError.new("Response has to be an integer") unless response.is_a? Integer
24
68
  @rdkafka_response = response
25
69
  @message_prefix = message_prefix
26
70
  @broker_message = broker_message
71
+ @fatal = fatal
72
+ @retryable = retryable
73
+ @abortable = abortable
27
74
  end
28
75
 
29
76
  # This error's code, for example `:partition_eof`, `:msg_size_too_large`.
@@ -58,6 +105,18 @@ module Rdkafka
58
105
  def ==(another_error)
59
106
  another_error.is_a?(self.class) && (self.to_s == another_error.to_s)
60
107
  end
108
+
109
+ def fatal?
110
+ @fatal
111
+ end
112
+
113
+ def retryable?
114
+ @retryable
115
+ end
116
+
117
+ def abortable?
118
+ @abortable
119
+ end
61
120
  end
62
121
 
63
122
  # Error with topic partition list returned by the underlying rdkafka library.
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ # Namespace for some small utilities used in multiple components
5
+ module Helpers
6
+ # Time related methods used across Karafka
7
+ module Time
8
+ # @return [Float] current monotonic time in seconds with microsecond precision
9
+ def monotonic_now
10
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -29,8 +29,7 @@ module Rdkafka
29
29
  # Retrieve the Metadata
30
30
  result = Rdkafka::Bindings.rd_kafka_metadata(native_client, topic_flag, native_topic, ptr, timeout_ms)
31
31
 
32
- # Error Handling
33
- raise Rdkafka::RdkafkaError.new(result) unless result.zero?
32
+ Rdkafka::RdkafkaError.validate!(result)
34
33
 
35
34
  metadata_from_native(ptr.read_pointer)
36
35
  rescue ::Rdkafka::RdkafkaError => e
@@ -58,11 +57,12 @@ module Rdkafka
58
57
 
59
58
  @topics = Array.new(metadata[:topics_count]) do |i|
60
59
  topic = TopicMetadata.new(metadata[:topics_metadata] + (i * TopicMetadata.size))
61
- raise Rdkafka::RdkafkaError.new(topic[:rd_kafka_resp_err]) unless topic[:rd_kafka_resp_err].zero?
60
+
61
+ RdkafkaError.validate!(topic[:rd_kafka_resp_err])
62
62
 
63
63
  partitions = Array.new(topic[:partition_count]) do |j|
64
64
  partition = PartitionMetadata.new(topic[:partitions_metadata] + (j * PartitionMetadata.size))
65
- raise Rdkafka::RdkafkaError.new(partition[:rd_kafka_resp_err]) unless partition[:rd_kafka_resp_err].zero?
65
+ RdkafkaError.validate!(partition[:rd_kafka_resp_err])
66
66
  partition.to_h
67
67
  end
68
68
  topic.to_h.merge!(partitions: partitions)
@@ -4,8 +4,9 @@ module Rdkafka
4
4
  # @private
5
5
  # A wrapper around a native kafka that polls and cleanly exits
6
6
  class NativeKafka
7
- def initialize(inner, run_polling_thread:)
7
+ def initialize(inner, run_polling_thread:, opaque:)
8
8
  @inner = inner
9
+ @opaque = opaque
9
10
  # Lock around external access
10
11
  @access_mutex = Mutex.new
11
12
  # Lock around internal polling
@@ -27,6 +28,9 @@ module Rdkafka
27
28
  # counter for operations in progress using inner
28
29
  @operations_in_progress = 0
29
30
 
31
+ # Trigger initial poll to make sure oauthbearer cb and other initial cb are handled
32
+ Rdkafka::Bindings.rd_kafka_poll(inner, 0)
33
+
30
34
  if run_polling_thread
31
35
  # Start thread to poll client for delivery callbacks,
32
36
  # not used in consumer.
@@ -109,6 +113,7 @@ module Rdkafka
109
113
 
110
114
  Rdkafka::Bindings.rd_kafka_destroy(@inner)
111
115
  @inner = nil
116
+ @opaque = nil
112
117
  end
113
118
  end
114
119
  end
@@ -18,7 +18,22 @@ module Rdkafka
18
18
 
19
19
  # @return [DeliveryReport] a report on the delivery of the message
20
20
  def create_result
21
- DeliveryReport.new(self[:partition], self[:offset], self[:topic_name].read_string)
21
+ if self[:response] == 0
22
+ DeliveryReport.new(
23
+ self[:partition],
24
+ self[:offset],
25
+ self[:topic_name].read_string
26
+ )
27
+ else
28
+ DeliveryReport.new(
29
+ self[:partition],
30
+ self[:offset],
31
+ # For part of errors, we will not get a topic name reference and in cases like this
32
+ # we should not return it
33
+ self[:topic_name].null? ? nil : self[:topic_name].read_string,
34
+ Rdkafka::RdkafkaError.build(self[:response])
35
+ )
36
+ end
22
37
  end
23
38
  end
24
39
  end
@@ -12,8 +12,9 @@ module Rdkafka
12
12
  # @return [Integer]
13
13
  attr_reader :offset
14
14
 
15
- # The name of the topic this message was produced to.
16
- # @return [String]
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
+ # @return [String, nil]
17
18
  attr_reader :topic_name
18
19
 
19
20
  # Error in case happen during produce.