karafka-rdkafka 0.13.2 → 0.13.9

Sign up to get free protection for your applications and to get access to all the features.
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.