karafka-rdkafka 0.29.0-aarch64-linux-gnu → 0.30.1-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 +58 -21
- data/README.md +2 -1
- data/bin/verify_kafka_warnings +2 -0
- data/docker-compose-ssl.yml +13 -0
- data/docker-compose.yml +11 -0
- data/ext/librdkafka.so +0 -0
- data/lib/rdkafka/admin/alter_consumer_group_offsets_handle.rb +31 -0
- data/lib/rdkafka/admin/alter_consumer_group_offsets_report.rb +50 -0
- data/lib/rdkafka/admin/delete_consumer_group_offsets_handle.rb +31 -0
- data/lib/rdkafka/admin/delete_consumer_group_offsets_report.rb +50 -0
- data/lib/rdkafka/admin.rb +120 -0
- data/lib/rdkafka/bindings.rb +117 -4
- data/lib/rdkafka/callbacks/alter_consumer_group_offsets_handler.rb +65 -0
- data/lib/rdkafka/callbacks/delete_consumer_group_offsets_handler.rb +65 -0
- data/lib/rdkafka/callbacks.rb +14 -16
- data/lib/rdkafka/config.rb +143 -14
- data/lib/rdkafka/defaults.rb +8 -0
- data/lib/rdkafka/error.rb +13 -2
- data/lib/rdkafka/helpers/oauth.rb +24 -6
- data/lib/rdkafka/share_consumer/message.rb +40 -0
- data/lib/rdkafka/share_consumer.rb +573 -0
- data/lib/rdkafka/version.rb +3 -3
- data/lib/rdkafka.rb +8 -0
- metadata +10 -2
data/lib/rdkafka/bindings.rb
CHANGED
|
@@ -201,7 +201,7 @@ module Rdkafka
|
|
|
201
201
|
RD_KAFKA_ALTER_CONFIG_OP_TYPE_SUBTRACT = 3
|
|
202
202
|
|
|
203
203
|
# List Offsets
|
|
204
|
-
RD_KAFKA_ADMIN_OP_LISTOFFSETS =
|
|
204
|
+
RD_KAFKA_ADMIN_OP_LISTOFFSETS = 21
|
|
205
205
|
RD_KAFKA_EVENT_LISTOFFSETS_RESULT = 0x400000
|
|
206
206
|
|
|
207
207
|
# rd_kafka_IsolationLevel_t
|
|
@@ -412,9 +412,17 @@ module Rdkafka
|
|
|
412
412
|
# ```
|
|
413
413
|
OAuthbearerTokenRefreshCallback = FFI::Function.new(
|
|
414
414
|
:void, [:pointer, :string, :pointer]
|
|
415
|
-
) do |client_ptr, config,
|
|
415
|
+
) do |client_ptr, config, opaque_ptr|
|
|
416
416
|
if Rdkafka::Config.oauthbearer_token_refresh_callback && !client_ptr.null?
|
|
417
|
-
|
|
417
|
+
client_name = Rdkafka::Bindings.rd_kafka_name(client_ptr)
|
|
418
|
+
|
|
419
|
+
# Share consumers have no way to query their librdkafka client name (the share handle
|
|
420
|
+
# exposes no name accessor), so hand it over here - where librdkafka provides it -
|
|
421
|
+
# letting applications correlate this callback's client name with a ShareConsumer via
|
|
422
|
+
# ShareConsumer#name.
|
|
423
|
+
Rdkafka::Config.opaques[opaque_ptr.to_i]&.capture_client_name(client_name)
|
|
424
|
+
|
|
425
|
+
Rdkafka::Config.oauthbearer_token_refresh_callback.call(config, client_name)
|
|
418
426
|
end
|
|
419
427
|
end
|
|
420
428
|
|
|
@@ -503,6 +511,68 @@ module Rdkafka
|
|
|
503
511
|
end
|
|
504
512
|
end
|
|
505
513
|
|
|
514
|
+
# Share consumer (KIP-932, preview)
|
|
515
|
+
#
|
|
516
|
+
# The share consumer is a distinct librdkafka handle type (rd_kafka_share_t) created with
|
|
517
|
+
# rd_kafka_share_consumer_new instead of rd_kafka_new. It is single-threaded by design and
|
|
518
|
+
# librdkafka itself rejects concurrent access with RD_KAFKA_RESP_ERR__CONFLICT.
|
|
519
|
+
|
|
520
|
+
# Minimal mirror of the head of librdkafka's `struct rd_kafka_share_s`, whose first member is
|
|
521
|
+
# the wrapped `rd_kafka_t` (`rkshare_rk`). `rd_kafka_share_t` has no public accessors of its
|
|
522
|
+
# own - not even for the client name - but the handle it wraps is a normal `rd_kafka_t` and is
|
|
523
|
+
# the very handle librdkafka passes to the statistics, error and OAuthBearer callbacks. Reading
|
|
524
|
+
# it lets a share consumer report a name that matches its statistics `name` field, which
|
|
525
|
+
# `rd_kafka_name` cannot do when handed the `rd_kafka_share_t` directly (it would read an
|
|
526
|
+
# unrelated field and return garbage). This mirrors how the gem already maps other librdkafka
|
|
527
|
+
# structs by layout for the pinned librdkafka version.
|
|
528
|
+
class NativeShareConsumer < FFI::Struct
|
|
529
|
+
layout :rkshare_rk, :pointer
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# Acknowledge types for share consumer records
|
|
533
|
+
RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT = 1
|
|
534
|
+
RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_RELEASE = 2
|
|
535
|
+
RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_REJECT = 3
|
|
536
|
+
|
|
537
|
+
attach_function :rd_kafka_share_consumer_new, [:pointer, :pointer, :size_t], :pointer
|
|
538
|
+
attach_function :rd_kafka_share_consumer_close, [:pointer], :pointer, blocking: true
|
|
539
|
+
attach_function :rd_kafka_share_consumer_closed, [:pointer], :int
|
|
540
|
+
attach_function :rd_kafka_share_destroy, [:pointer], :pointer, blocking: true
|
|
541
|
+
attach_function :rd_kafka_share_set_log_queue, [:pointer, :pointer], :pointer
|
|
542
|
+
|
|
543
|
+
attach_function :rd_kafka_share_subscribe, [:pointer, :pointer], :int, blocking: true
|
|
544
|
+
attach_function :rd_kafka_share_unsubscribe, [:pointer], :int, blocking: true
|
|
545
|
+
attach_function :rd_kafka_share_subscription, [:pointer, :pointer], :int, blocking: true
|
|
546
|
+
|
|
547
|
+
attach_function :rd_kafka_share_poll, [:pointer, :int, :pointer], :pointer, blocking: true
|
|
548
|
+
attach_function :rd_kafka_messages_count, [:pointer], :size_t
|
|
549
|
+
attach_function :rd_kafka_messages_get, [:pointer, :size_t], :pointer
|
|
550
|
+
attach_function :rd_kafka_messages_destroy, [:pointer], :void
|
|
551
|
+
attach_function :rd_kafka_message_delivery_count, [:pointer], :int16
|
|
552
|
+
|
|
553
|
+
# The by-coordinates acknowledge variant is the only one we bind: Ruby messages are copies
|
|
554
|
+
# that do not retain the native rd_kafka_message_t pointer, so the message-pointer variants
|
|
555
|
+
# (rd_kafka_share_acknowledge / rd_kafka_share_acknowledge_type) cannot be used safely here.
|
|
556
|
+
attach_function :rd_kafka_share_acknowledge_offset, [:pointer, :string, :int32, :int64, :int], :int
|
|
557
|
+
|
|
558
|
+
attach_function :rd_kafka_share_commit_sync, [:pointer, :int, :pointer], :pointer, blocking: true
|
|
559
|
+
attach_function :rd_kafka_share_commit_async, [:pointer], :pointer
|
|
560
|
+
|
|
561
|
+
callback :share_acknowledgement_commit_cb, [:pointer, :pointer, :int, :pointer], :void
|
|
562
|
+
attach_function :rd_kafka_share_set_acknowledgement_commit_cb, [:pointer, :share_acknowledgement_commit_cb, :pointer], :pointer
|
|
563
|
+
|
|
564
|
+
# Accessors for the rd_kafka_share_partition_offsets_list_t handed to the acknowledgement
|
|
565
|
+
# commit callback. The list is owned by librdkafka for the duration of the callback: read it
|
|
566
|
+
# inside the callback only, never destroy or retain it.
|
|
567
|
+
attach_function :rd_kafka_share_partition_offsets_list_count, [:pointer], :size_t
|
|
568
|
+
attach_function :rd_kafka_share_partition_offsets_list_get, [:pointer, :size_t], :pointer
|
|
569
|
+
attach_function :rd_kafka_share_partition_offsets_partition, [:pointer], :pointer
|
|
570
|
+
attach_function :rd_kafka_share_partition_offsets_offsets, [:pointer], :pointer
|
|
571
|
+
attach_function :rd_kafka_share_partition_offsets_offsets_cnt, [:pointer], :size_t
|
|
572
|
+
|
|
573
|
+
attach_function :rd_kafka_share_oauthbearer_set_token, [:pointer, :string, :int64, :string, :pointer, :size_t, :pointer, :size_t], :int
|
|
574
|
+
attach_function :rd_kafka_share_oauthbearer_set_token_failure, [:pointer, :string], :int
|
|
575
|
+
|
|
506
576
|
# Stats
|
|
507
577
|
|
|
508
578
|
attach_function :rd_kafka_query_watermark_offsets, [:pointer, :string, :int, :pointer, :pointer, :int], :int, blocking: true
|
|
@@ -559,9 +629,31 @@ module Rdkafka
|
|
|
559
629
|
raise Rdkafka::Config::ConfigError.new("Unknown partitioner: #{partitioner}")
|
|
560
630
|
end
|
|
561
631
|
|
|
562
|
-
public_send(method_name, topic_ptr, str_ptr, str
|
|
632
|
+
public_send(method_name, topic_ptr, str_ptr, partition_key_length(str), partition_count, nil, nil)
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
# Partition key length as the character count (legacy default).
|
|
636
|
+
#
|
|
637
|
+
# @param str [String] the partition key string
|
|
638
|
+
# @return [Integer]
|
|
639
|
+
def self.partition_key_size(str)
|
|
640
|
+
str.size
|
|
563
641
|
end
|
|
564
642
|
|
|
643
|
+
# Partition key length as the byte count, which is what librdkafka hashes (UTF-8 bytes copied
|
|
644
|
+
# into the key pointer) and what other Kafka clients use.
|
|
645
|
+
#
|
|
646
|
+
# @param str [String] the partition key string
|
|
647
|
+
# @return [Integer]
|
|
648
|
+
def self.partition_key_bytesize(str)
|
|
649
|
+
str.bytesize
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
# Length of the partition key passed to librdkafka. Aliased to one of the methods above by
|
|
653
|
+
# `Rdkafka::Config.partitioner_key_uses_bytesize=`, so the per-message path does not check the
|
|
654
|
+
# setting on every call.
|
|
655
|
+
singleton_class.alias_method :partition_key_length, :partition_key_size
|
|
656
|
+
|
|
565
657
|
# Create Topics
|
|
566
658
|
RD_KAFKA_ADMIN_OP_CREATETOPICS = 1 # rd_kafka_admin_op_t
|
|
567
659
|
RD_KAFKA_EVENT_CREATETOPICS_RESULT = 100 # rd_kafka_event_type_t
|
|
@@ -603,6 +695,26 @@ module Rdkafka
|
|
|
603
695
|
attach_function :rd_kafka_event_DeleteGroups_result, [:pointer], :pointer, blocking: true # rd_kafka_event_t* => rd_kafka_DeleteGroups_result_t*
|
|
604
696
|
attach_function :rd_kafka_DeleteGroups_result_groups, [:pointer, :pointer], :pointer, blocking: true # rd_kafka_DeleteGroups_result_t*, size_t* => rd_kafka_group_result_t**
|
|
605
697
|
|
|
698
|
+
# Alter Consumer Group Offsets
|
|
699
|
+
RD_KAFKA_ADMIN_OP_ALTERCONSUMERGROUPOFFSETS = 15 # rd_kafka_admin_op_t
|
|
700
|
+
RD_KAFKA_EVENT_ALTERCONSUMERGROUPOFFSETS_RESULT = 0x10000 # rd_kafka_event_type_t
|
|
701
|
+
|
|
702
|
+
attach_function :rd_kafka_AlterConsumerGroupOffsets, [:pointer, :pointer, :size_t, :pointer, :pointer], :void, blocking: true
|
|
703
|
+
attach_function :rd_kafka_AlterConsumerGroupOffsets_new, [:pointer, :pointer], :pointer, blocking: true # const char*, const rd_kafka_topic_partition_list_t* => rd_kafka_AlterConsumerGroupOffsets_t*
|
|
704
|
+
attach_function :rd_kafka_AlterConsumerGroupOffsets_destroy, [:pointer], :void, blocking: true
|
|
705
|
+
attach_function :rd_kafka_event_AlterConsumerGroupOffsets_result, [:pointer], :pointer, blocking: true # rd_kafka_event_t* => rd_kafka_AlterConsumerGroupOffsets_result_t*
|
|
706
|
+
attach_function :rd_kafka_AlterConsumerGroupOffsets_result_groups, [:pointer, :pointer], :pointer, blocking: true # rd_kafka_AlterConsumerGroupOffsets_result_t*, size_t* => rd_kafka_group_result_t**
|
|
707
|
+
|
|
708
|
+
# Delete Consumer Group Offsets
|
|
709
|
+
RD_KAFKA_ADMIN_OP_DELETECONSUMERGROUPOFFSETS = 8 # rd_kafka_admin_op_t
|
|
710
|
+
RD_KAFKA_EVENT_DELETECONSUMERGROUPOFFSETS_RESULT = 107 # rd_kafka_event_type_t
|
|
711
|
+
|
|
712
|
+
attach_function :rd_kafka_DeleteConsumerGroupOffsets, [:pointer, :pointer, :size_t, :pointer, :pointer], :void, blocking: true
|
|
713
|
+
attach_function :rd_kafka_DeleteConsumerGroupOffsets_new, [:pointer, :pointer], :pointer, blocking: true # const char*, const rd_kafka_topic_partition_list_t* => rd_kafka_DeleteConsumerGroupOffsets_t*
|
|
714
|
+
attach_function :rd_kafka_DeleteConsumerGroupOffsets_destroy, [:pointer], :void, blocking: true
|
|
715
|
+
attach_function :rd_kafka_event_DeleteConsumerGroupOffsets_result, [:pointer], :pointer, blocking: true # rd_kafka_event_t* => rd_kafka_DeleteConsumerGroupOffsets_result_t*
|
|
716
|
+
attach_function :rd_kafka_DeleteConsumerGroupOffsets_result_groups, [:pointer, :pointer], :pointer, blocking: true # rd_kafka_DeleteConsumerGroupOffsets_result_t*, size_t* => rd_kafka_group_result_t**
|
|
717
|
+
|
|
606
718
|
# Delete Records
|
|
607
719
|
RD_KAFKA_ADMIN_OP_DELETERECORDS = 6 # rd_kafka_admin_op_t
|
|
608
720
|
RD_KAFKA_EVENT_DELETERECORDS_RESULT = 105 # rd_kafka_event_type_t
|
|
@@ -752,5 +864,6 @@ module Rdkafka
|
|
|
752
864
|
|
|
753
865
|
attach_function :rd_kafka_group_result_error, [:pointer], NativeError.by_ref # rd_kafka_group_result_t* => rd_kafka_error_t*
|
|
754
866
|
attach_function :rd_kafka_group_result_name, [:pointer], :pointer
|
|
867
|
+
attach_function :rd_kafka_group_result_partitions, [:pointer], :pointer # rd_kafka_group_result_t* => rd_kafka_topic_partition_list_t*
|
|
755
868
|
end
|
|
756
869
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
module Callbacks
|
|
5
|
+
# Handles `RD_KAFKA_EVENT_ALTERCONSUMERGROUPOFFSETS_RESULT` events
|
|
6
|
+
# @private
|
|
7
|
+
class AlterConsumerGroupOffsetsHandler < BaseHandler
|
|
8
|
+
class << self
|
|
9
|
+
# Resolves the alter consumer group offsets handle from its result event
|
|
10
|
+
# @param event_ptr [FFI::Pointer] pointer to the event
|
|
11
|
+
# @return [void]
|
|
12
|
+
def call(event_ptr)
|
|
13
|
+
result = Rdkafka::Bindings.rd_kafka_event_AlterConsumerGroupOffsets_result(event_ptr)
|
|
14
|
+
|
|
15
|
+
pointer_to_size_t = FFI::MemoryPointer.new(:size_t)
|
|
16
|
+
group_result_array = Rdkafka::Bindings.rd_kafka_AlterConsumerGroupOffsets_result_groups(result, pointer_to_size_t)
|
|
17
|
+
group_results = GroupResult.create_group_results_from_array(pointer_to_size_t.read_int, group_result_array)
|
|
18
|
+
handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
|
|
19
|
+
|
|
20
|
+
if (handle = Rdkafka::Admin::AlterConsumerGroupOffsetsHandle.remove(handle_ptr.address))
|
|
21
|
+
unless resolve_operation_error(event_ptr, handle)
|
|
22
|
+
group_result = group_results[0]
|
|
23
|
+
|
|
24
|
+
# librdkafka only supports one group per invocation, so a missing group result
|
|
25
|
+
# means the broker returned nothing to act on rather than a partial success.
|
|
26
|
+
if group_result.nil?
|
|
27
|
+
handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
28
|
+
handle.result = Rdkafka::Admin::AlterConsumerGroupOffsetsReport.new(
|
|
29
|
+
FFI::Pointer::NULL,
|
|
30
|
+
FFI::Pointer::NULL
|
|
31
|
+
)
|
|
32
|
+
else
|
|
33
|
+
report = Rdkafka::Admin::AlterConsumerGroupOffsetsReport.new(
|
|
34
|
+
group_result.error_string,
|
|
35
|
+
group_result.result_name,
|
|
36
|
+
Rdkafka::Bindings.rd_kafka_group_result_partitions(group_result_array.read_pointer)
|
|
37
|
+
)
|
|
38
|
+
handle.result = report
|
|
39
|
+
|
|
40
|
+
# Kafka reports a rejected offset change per partition, leaving the group level
|
|
41
|
+
# error unset - a group with live members comes back with the group error NULL
|
|
42
|
+
# and `unknown_member_id` on each partition. Resolving on the group error alone
|
|
43
|
+
# would hand back a report that looks like success, so any failed partition has
|
|
44
|
+
# to fail the handle too.
|
|
45
|
+
failed = report.partitions.find { |partition| partition[:error] }
|
|
46
|
+
|
|
47
|
+
if !group_result.result_error.zero?
|
|
48
|
+
handle[:response] = group_result.result_error
|
|
49
|
+
handle.broker_message = report.error_string
|
|
50
|
+
elsif failed
|
|
51
|
+
handle[:response] = failed[:error].rdkafka_response
|
|
52
|
+
handle.broker_message = "Failed for partition #{failed[:partition]} of '#{failed[:topic]}'"
|
|
53
|
+
else
|
|
54
|
+
handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
handle.unlock
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
module Callbacks
|
|
5
|
+
# Handles `RD_KAFKA_EVENT_DELETECONSUMERGROUPOFFSETS_RESULT` events
|
|
6
|
+
# @private
|
|
7
|
+
class DeleteConsumerGroupOffsetsHandler < BaseHandler
|
|
8
|
+
class << self
|
|
9
|
+
# Resolves the delete consumer group offsets handle from its result event
|
|
10
|
+
# @param event_ptr [FFI::Pointer] pointer to the event
|
|
11
|
+
# @return [void]
|
|
12
|
+
def call(event_ptr)
|
|
13
|
+
result = Rdkafka::Bindings.rd_kafka_event_DeleteConsumerGroupOffsets_result(event_ptr)
|
|
14
|
+
|
|
15
|
+
pointer_to_size_t = FFI::MemoryPointer.new(:size_t)
|
|
16
|
+
group_result_array = Rdkafka::Bindings.rd_kafka_DeleteConsumerGroupOffsets_result_groups(result, pointer_to_size_t)
|
|
17
|
+
group_results = GroupResult.create_group_results_from_array(pointer_to_size_t.read_int, group_result_array)
|
|
18
|
+
handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
|
|
19
|
+
|
|
20
|
+
if (handle = Rdkafka::Admin::DeleteConsumerGroupOffsetsHandle.remove(handle_ptr.address))
|
|
21
|
+
unless resolve_operation_error(event_ptr, handle)
|
|
22
|
+
group_result = group_results[0]
|
|
23
|
+
|
|
24
|
+
# librdkafka only supports one group per invocation, so a missing group result
|
|
25
|
+
# means the broker returned nothing to act on rather than a partial success.
|
|
26
|
+
if group_result.nil?
|
|
27
|
+
handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
28
|
+
handle.result = Rdkafka::Admin::DeleteConsumerGroupOffsetsReport.new(
|
|
29
|
+
FFI::Pointer::NULL,
|
|
30
|
+
FFI::Pointer::NULL
|
|
31
|
+
)
|
|
32
|
+
else
|
|
33
|
+
report = Rdkafka::Admin::DeleteConsumerGroupOffsetsReport.new(
|
|
34
|
+
group_result.error_string,
|
|
35
|
+
group_result.result_name,
|
|
36
|
+
Rdkafka::Bindings.rd_kafka_group_result_partitions(group_result_array.read_pointer)
|
|
37
|
+
)
|
|
38
|
+
handle.result = report
|
|
39
|
+
|
|
40
|
+
# Kafka reports a rejected offset change per partition, leaving the group level
|
|
41
|
+
# error unset - a group with live members comes back with the group error NULL
|
|
42
|
+
# and `unknown_member_id` on each partition. Resolving on the group error alone
|
|
43
|
+
# would hand back a report that looks like success, so any failed partition has
|
|
44
|
+
# to fail the handle too.
|
|
45
|
+
failed = report.partitions.find { |partition| partition[:error] }
|
|
46
|
+
|
|
47
|
+
if !group_result.result_error.zero?
|
|
48
|
+
handle[:response] = group_result.result_error
|
|
49
|
+
handle.broker_message = report.error_string
|
|
50
|
+
elsif failed
|
|
51
|
+
handle[:response] = failed[:error].rdkafka_response
|
|
52
|
+
handle.broker_message = "Failed for partition #{failed[:partition]} of '#{failed[:topic]}'"
|
|
53
|
+
else
|
|
54
|
+
handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
handle.unlock
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/rdkafka/callbacks.rb
CHANGED
|
@@ -21,10 +21,9 @@ module Rdkafka
|
|
|
21
21
|
# @param array_pointer [FFI::Pointer] pointer to the results array
|
|
22
22
|
# @return [Array<TopicResult>] array of topic results
|
|
23
23
|
def self.create_topic_results_from_array(count, array_pointer)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
end
|
|
24
|
+
return [] if count.zero?
|
|
25
|
+
|
|
26
|
+
array_pointer.read_array_of_pointer(count).map { |result_pointer| new(result_pointer) }
|
|
28
27
|
end
|
|
29
28
|
end
|
|
30
29
|
|
|
@@ -53,10 +52,9 @@ module Rdkafka
|
|
|
53
52
|
# @param array_pointer [FFI::Pointer] pointer to the results array
|
|
54
53
|
# @return [Array<GroupResult>] array of group results
|
|
55
54
|
def self.create_group_results_from_array(count, array_pointer)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
end
|
|
55
|
+
return [] if count.zero?
|
|
56
|
+
|
|
57
|
+
array_pointer.read_array_of_pointer(count).map { |result_pointer| new(result_pointer) }
|
|
60
58
|
end
|
|
61
59
|
end
|
|
62
60
|
|
|
@@ -77,10 +75,9 @@ module Rdkafka
|
|
|
77
75
|
# @param array_pointer [FFI::Pointer] pointer to the results array
|
|
78
76
|
# @return [Array<CreateAclResult>] array of ACL results
|
|
79
77
|
def self.create_acl_results_from_array(count, array_pointer)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
end
|
|
78
|
+
return [] if count.zero?
|
|
79
|
+
|
|
80
|
+
array_pointer.read_array_of_pointer(count).map { |result_pointer| new(result_pointer) }
|
|
84
81
|
end
|
|
85
82
|
end
|
|
86
83
|
|
|
@@ -108,10 +105,9 @@ module Rdkafka
|
|
|
108
105
|
# @param array_pointer [FFI::Pointer] pointer to the results array
|
|
109
106
|
# @return [Array<DeleteAclResult>] array of delete ACL results
|
|
110
107
|
def self.delete_acl_results_from_array(count, array_pointer)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
end
|
|
108
|
+
return [] if count.zero?
|
|
109
|
+
|
|
110
|
+
array_pointer.read_array_of_pointer(count).map { |result_pointer| new(result_pointer) }
|
|
115
111
|
end
|
|
116
112
|
end
|
|
117
113
|
|
|
@@ -219,6 +215,8 @@ module Rdkafka
|
|
|
219
215
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETETOPICS_RESULT then DeleteTopicHandler
|
|
220
216
|
when Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_CREATEPARTITIONS_RESULT then CreatePartitionsHandler
|
|
221
217
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEGROUPS_RESULT then DeleteGroupsHandler
|
|
218
|
+
when Rdkafka::Bindings::RD_KAFKA_EVENT_ALTERCONSUMERGROUPOFFSETS_RESULT then AlterConsumerGroupOffsetsHandler
|
|
219
|
+
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETECONSUMERGROUPOFFSETS_RESULT then DeleteConsumerGroupOffsetsHandler
|
|
222
220
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETERECORDS_RESULT then DeleteRecordsHandler
|
|
223
221
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_CREATEACLS_RESULT then CreateAclHandler
|
|
224
222
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEACLS_RESULT then DeleteAclHandler
|
data/lib/rdkafka/config.rb
CHANGED
|
@@ -22,6 +22,12 @@ module Rdkafka
|
|
|
22
22
|
@@log_mutex = Mutex.new
|
|
23
23
|
# @!visibility private
|
|
24
24
|
@@oauthbearer_token_refresh_callback = nil
|
|
25
|
+
# @!visibility private
|
|
26
|
+
# Whether the partition key length passed to the partitioner is the byte length (`bytesize`)
|
|
27
|
+
# rather than the character length (`size`). `false` (character length) is the legacy default
|
|
28
|
+
# to avoid changing partition assignment for existing multibyte/binary keys; set to `true` to
|
|
29
|
+
# opt into the correct, other-client-consistent byte-length hashing.
|
|
30
|
+
@@partitioner_key_uses_bytesize = false
|
|
25
31
|
|
|
26
32
|
# Returns the current logger, by default this is a logger to stdout.
|
|
27
33
|
#
|
|
@@ -122,6 +128,34 @@ module Rdkafka
|
|
|
122
128
|
@@oauthbearer_token_refresh_callback
|
|
123
129
|
end
|
|
124
130
|
|
|
131
|
+
# Whether the partitioner hashes the partition key by its byte length instead of its character
|
|
132
|
+
# length. Defaults to `false` (character length) to preserve existing partition assignment for
|
|
133
|
+
# multibyte/binary keys. Passing the byte length is the correct, other-client-consistent
|
|
134
|
+
# behavior; enable it (and be aware it can move such keys to a different partition) with
|
|
135
|
+
# `Rdkafka::Config.partitioner_key_uses_bytesize = true`. This default is expected to change in
|
|
136
|
+
# a future major version.
|
|
137
|
+
#
|
|
138
|
+
# @return [Boolean]
|
|
139
|
+
def self.partitioner_key_uses_bytesize
|
|
140
|
+
@@partitioner_key_uses_bytesize
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Sets whether the partitioner hashes the partition key by its byte length rather than its
|
|
144
|
+
# character length. The key length method is resolved here once rather than on every produced
|
|
145
|
+
# message, and each call re-resolves it, so changing the setting at runtime takes effect
|
|
146
|
+
# immediately.
|
|
147
|
+
#
|
|
148
|
+
# @param value [Boolean] true to hash by byte length, false (default) for character length
|
|
149
|
+
# @return [void]
|
|
150
|
+
def self.partitioner_key_uses_bytesize=(value)
|
|
151
|
+
@@partitioner_key_uses_bytesize = value
|
|
152
|
+
|
|
153
|
+
Rdkafka::Bindings.singleton_class.alias_method(
|
|
154
|
+
:partition_key_length,
|
|
155
|
+
value ? :partition_key_bytesize : :partition_key_size
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
|
|
125
159
|
# @private
|
|
126
160
|
def self.opaques
|
|
127
161
|
@@opaques
|
|
@@ -221,6 +255,73 @@ module Rdkafka
|
|
|
221
255
|
end
|
|
222
256
|
end
|
|
223
257
|
|
|
258
|
+
# Creates a KIP-932 share group consumer with this configuration.
|
|
259
|
+
#
|
|
260
|
+
# `:"group.id"` is required. The acknowledgement mode is selected with the optional
|
|
261
|
+
# `:"share.acknowledgement.mode"` property (`implicit` by default, or `explicit`). librdkafka
|
|
262
|
+
# rejects a number of regular-consumer properties for share consumers (for example
|
|
263
|
+
# `enable.auto.commit`, `auto.offset.reset` or `partition.assignment.strategy`) with a
|
|
264
|
+
# {ClientCreationError}.
|
|
265
|
+
#
|
|
266
|
+
# @return [ShareConsumer] The created share consumer
|
|
267
|
+
#
|
|
268
|
+
# @raise [ConfigError] When the configuration contains invalid options or a rebalance
|
|
269
|
+
# listener is set (share group assignment is broker-driven and has no rebalances)
|
|
270
|
+
# @raise [ClientCreationError] When the native client cannot be created
|
|
271
|
+
#
|
|
272
|
+
# @note The share consumer is a preview librdkafka feature and requires a broker with share
|
|
273
|
+
# groups enabled (Apache Kafka 4.2.0+).
|
|
274
|
+
def share_consumer
|
|
275
|
+
if @consumer_rebalance_listener
|
|
276
|
+
raise ConfigError.new("`consumer_rebalance_listener` is not supported for share consumers")
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
opaque = Opaque.new
|
|
280
|
+
config = native_config(opaque)
|
|
281
|
+
|
|
282
|
+
handle = create_native_handle(config) do |error_buffer|
|
|
283
|
+
Rdkafka::Bindings.rd_kafka_share_consumer_new(config, error_buffer, 256)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
share_consumer = nil
|
|
287
|
+
|
|
288
|
+
# Share consumers cannot go through `build_native_client`: they own the native handle
|
|
289
|
+
# directly rather than a `NativeKafka` wrapper (single-threaded by design, no polling
|
|
290
|
+
# thread). They still need both of the things that factory provides - registration, so the
|
|
291
|
+
# `at_exit` hook closes them before Ruby's shutdown finalization rather than letting
|
|
292
|
+
# librdkafka be `dlclose`d with its threads still running, and a teardown that destroys the
|
|
293
|
+
# handle when construction fails part-way - so both are done here.
|
|
294
|
+
begin
|
|
295
|
+
# Forward the log queue (enabled via REQUIRED_CONFIG) to the main queue, which
|
|
296
|
+
# rd_kafka_share_poll drains. There is no background polling thread for share consumers:
|
|
297
|
+
# log, statistics and error callbacks are all serviced from within ShareConsumer#poll.
|
|
298
|
+
log_queue_error = Rdkafka::Bindings.rd_kafka_share_set_log_queue(handle, FFI::Pointer::NULL)
|
|
299
|
+
Rdkafka::Bindings.rd_kafka_error_destroy(log_queue_error) unless log_queue_error.null?
|
|
300
|
+
|
|
301
|
+
share_consumer = Rdkafka::ShareConsumer.new(handle, opaque: opaque)
|
|
302
|
+
opaque.share_consumer = share_consumer
|
|
303
|
+
|
|
304
|
+
Rdkafka::Clients.register(share_consumer)
|
|
305
|
+
rescue Exception
|
|
306
|
+
begin
|
|
307
|
+
if share_consumer
|
|
308
|
+
share_consumer.close
|
|
309
|
+
else
|
|
310
|
+
destroy_error = Rdkafka::Bindings.rd_kafka_share_destroy(handle)
|
|
311
|
+
Rdkafka::Bindings.rd_kafka_error_destroy(destroy_error) unless destroy_error.null?
|
|
312
|
+
end
|
|
313
|
+
rescue Exception => err
|
|
314
|
+
# Never let a teardown failure replace the error that caused it: that error is what the
|
|
315
|
+
# caller needs to see, and it is re-raised below regardless.
|
|
316
|
+
Rdkafka::Config.logger.error("Unhandled exception: #{err.class} - #{err.message}")
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
raise
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
share_consumer
|
|
323
|
+
end
|
|
324
|
+
|
|
224
325
|
# Create a producer with this configuration.
|
|
225
326
|
#
|
|
226
327
|
# @param native_kafka_auto_start [Boolean] should the native kafka operations be started
|
|
@@ -442,28 +543,44 @@ module Rdkafka
|
|
|
442
543
|
raise
|
|
443
544
|
end
|
|
444
545
|
|
|
445
|
-
# Creates a native
|
|
546
|
+
# Creates a native client handle via the given creation block, enforcing the conf ownership
|
|
547
|
+
# rule shared by all librdkafka constructors: on success librdkafka takes ownership of the
|
|
548
|
+
# conf and frees it itself; on failure the application keeps ownership (see rdkafka.c), so
|
|
549
|
+
# it must be destroyed here to avoid leaking the conf on every failed client creation.
|
|
550
|
+
#
|
|
446
551
|
# @param config [FFI::Pointer] pointer to the native config
|
|
447
|
-
# @
|
|
448
|
-
# @
|
|
552
|
+
# @yield [error_buffer] performs the native creation call
|
|
553
|
+
# @yieldparam error_buffer [FFI::MemoryPointer] 256-byte buffer for the error string
|
|
554
|
+
# @return [FFI::Pointer] non-NULL native handle
|
|
555
|
+
# @raise [ClientCreationError] When the native client cannot be created
|
|
449
556
|
# @private
|
|
450
|
-
def
|
|
451
|
-
error_buffer = FFI::MemoryPointer.
|
|
452
|
-
handle =
|
|
453
|
-
type,
|
|
454
|
-
config,
|
|
455
|
-
error_buffer,
|
|
456
|
-
256
|
|
457
|
-
)
|
|
557
|
+
def create_native_handle(config)
|
|
558
|
+
error_buffer = FFI::MemoryPointer.new(:char, 256)
|
|
559
|
+
handle = yield(error_buffer)
|
|
458
560
|
|
|
459
561
|
if handle.null?
|
|
460
|
-
# On success rd_kafka_new takes ownership of the conf and frees it itself; on failure
|
|
461
|
-
# librdkafka keeps application ownership (see rdkafka.c), so we must destroy it here to
|
|
462
|
-
# avoid leaking the conf on every failed client creation.
|
|
463
562
|
Rdkafka::Bindings.rd_kafka_conf_destroy(config)
|
|
464
563
|
raise ClientCreationError.new(error_buffer.read_string)
|
|
465
564
|
end
|
|
466
565
|
|
|
566
|
+
handle
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Creates a native Kafka handle
|
|
570
|
+
# @param config [FFI::Pointer] pointer to the native config
|
|
571
|
+
# @param type [Symbol] type of client (:rd_kafka_producer or :rd_kafka_consumer)
|
|
572
|
+
# @return [FFI::Pointer] pointer to the native Kafka handle
|
|
573
|
+
# @private
|
|
574
|
+
def native_kafka(config, type)
|
|
575
|
+
handle = create_native_handle(config) do |error_buffer|
|
|
576
|
+
Rdkafka::Bindings.rd_kafka_new(
|
|
577
|
+
type,
|
|
578
|
+
config,
|
|
579
|
+
error_buffer,
|
|
580
|
+
256
|
|
581
|
+
)
|
|
582
|
+
end
|
|
583
|
+
|
|
467
584
|
# Redirect log to handle's queue
|
|
468
585
|
main_queue = Rdkafka::Bindings.rd_kafka_queue_get_main(handle)
|
|
469
586
|
Rdkafka::Bindings.rd_kafka_set_log_queue(handle, main_queue)
|
|
@@ -481,6 +598,18 @@ module Rdkafka
|
|
|
481
598
|
class Opaque
|
|
482
599
|
attr_accessor :producer
|
|
483
600
|
attr_accessor :consumer_rebalance_listener
|
|
601
|
+
# Backreference set for share consumers so conf-level callbacks (which receive the opaque)
|
|
602
|
+
# can hand instance-scoped data - currently the librdkafka client name - back to the
|
|
603
|
+
# ShareConsumer, which has no native accessor for it
|
|
604
|
+
attr_accessor :share_consumer
|
|
605
|
+
|
|
606
|
+
# Forwards the librdkafka client name to clients that cannot query it themselves. Regular
|
|
607
|
+
# clients resolve their name lazily via rd_kafka_name on their own handle, so only the
|
|
608
|
+
# share consumer (whose handle exposes no name accessor) consumes this.
|
|
609
|
+
# @param name [String] librdkafka client name (e.g. "rdkafka#consumer-1")
|
|
610
|
+
def capture_client_name(name)
|
|
611
|
+
share_consumer&.name = name
|
|
612
|
+
end
|
|
484
613
|
|
|
485
614
|
# Invokes the delivery callback on the producer if one is set
|
|
486
615
|
# @param delivery_report [Rdkafka::Producer::DeliveryReport] the delivery report
|
data/lib/rdkafka/defaults.rb
CHANGED
|
@@ -47,6 +47,14 @@ module Rdkafka
|
|
|
47
47
|
# @see Consumer#events_poll
|
|
48
48
|
CONSUMER_EVENTS_POLL_TIMEOUT_MS = 0
|
|
49
49
|
|
|
50
|
+
# Share consumer timeout for poll operations (used in each iteration)
|
|
51
|
+
# @see ShareConsumer#poll
|
|
52
|
+
SHARE_CONSUMER_POLL_TIMEOUT_MS = 250
|
|
53
|
+
|
|
54
|
+
# Share consumer timeout for synchronous acknowledgement commits
|
|
55
|
+
# @see ShareConsumer#commit_sync
|
|
56
|
+
SHARE_CONSUMER_COMMIT_SYNC_TIMEOUT_MS = 5_000
|
|
57
|
+
|
|
50
58
|
# Producer timeout for flush operations
|
|
51
59
|
# @see Producer#flush
|
|
52
60
|
PRODUCER_FLUSH_TIMEOUT_MS = 5_000
|
data/lib/rdkafka/error.rb
CHANGED
|
@@ -40,9 +40,20 @@ module Rdkafka
|
|
|
40
40
|
def build_from_c(response_ptr, message_prefix = nil, broker_message: nil, instance_name: nil)
|
|
41
41
|
code = Rdkafka::Bindings.rd_kafka_error_code(response_ptr)
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
if code == Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
44
|
+
# A no-op error object still has to be freed (librdkafka can construct code-0 errors)
|
|
45
|
+
Rdkafka::Bindings.rd_kafka_error_destroy(response_ptr) unless response_ptr.null?
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
return false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Prefer the human readable error string attached to this error object; librdkafka falls
|
|
51
|
+
# back to the generic per-code description internally when none was attached
|
|
52
|
+
message = broker_message
|
|
53
|
+
if message.nil?
|
|
54
|
+
message_ptr = Rdkafka::Bindings.rd_kafka_error_string(response_ptr)
|
|
55
|
+
message = message_ptr.null? ? Rdkafka::Bindings.rd_kafka_err2str(code) : message_ptr.read_string
|
|
56
|
+
end
|
|
46
57
|
fatal = !Rdkafka::Bindings.rd_kafka_error_is_fatal(response_ptr).zero?
|
|
47
58
|
retryable = !Rdkafka::Bindings.rd_kafka_error_is_retriable(response_ptr).zero?
|
|
48
59
|
abortable = !Rdkafka::Bindings.rd_kafka_error_txn_requires_abort(response_ptr).zero?
|
|
@@ -18,12 +18,10 @@ module Rdkafka
|
|
|
18
18
|
extensions_ptr, extensions_str_ptrs = map_extensions(extensions)
|
|
19
19
|
|
|
20
20
|
begin
|
|
21
|
-
response =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
)
|
|
26
|
-
end
|
|
21
|
+
response = oauthbearer_native_set_token(
|
|
22
|
+
token, lifetime_ms, principal_name,
|
|
23
|
+
extensions_ptr, extension_size(extensions), error_buffer
|
|
24
|
+
)
|
|
27
25
|
ensure
|
|
28
26
|
extensions_str_ptrs&.each { |ptr| ptr.free }
|
|
29
27
|
extensions_ptr&.free
|
|
@@ -50,6 +48,26 @@ module Rdkafka
|
|
|
50
48
|
|
|
51
49
|
private
|
|
52
50
|
|
|
51
|
+
# Performs the native token set call. The default targets the regular client handle;
|
|
52
|
+
# clients with a different handle type (e.g. the share consumer) override just this hook
|
|
53
|
+
# while reusing the buffer/extension plumbing above.
|
|
54
|
+
#
|
|
55
|
+
# @param token [String] the token value
|
|
56
|
+
# @param lifetime_ms [Integer] token expiry, in milliseconds since the epoch
|
|
57
|
+
# @param principal_name [String] the Kafka principal name associated with the token
|
|
58
|
+
# @param extensions_ptr [FFI::Pointer, nil] `const char **` built by {#map_extensions}
|
|
59
|
+
# @param extensions_size [Integer] number of key-value pairs pointed to by `extensions_ptr`
|
|
60
|
+
# @param error_buffer [FFI::MemoryPointer] 256-byte buffer for the error string
|
|
61
|
+
# @return [Integer] 0 on success
|
|
62
|
+
def oauthbearer_native_set_token(token, lifetime_ms, principal_name, extensions_ptr, extensions_size, error_buffer)
|
|
63
|
+
@native_kafka.with_inner do |inner|
|
|
64
|
+
Rdkafka::Bindings.rd_kafka_oauthbearer_set_token(
|
|
65
|
+
inner, token, lifetime_ms, principal_name,
|
|
66
|
+
extensions_ptr, extensions_size, error_buffer, 256
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
53
71
|
# Convert extensions hash to FFI::MemoryPointer (`const char **`).
|
|
54
72
|
#
|
|
55
73
|
# @param extensions [Hash, nil] extension key-value pairs
|