rdkafka 0.29.0-aarch64-linux-gnu → 0.29.2-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 +41 -24
- data/README.md +2 -2
- data/bin/verify_kafka_warnings +5 -0
- data/docker-compose-ssl.yml +3 -0
- data/docker-compose.yml +3 -0
- data/ext/librdkafka.so +0 -0
- data/lib/rdkafka/admin/create_acl_report.rb +2 -1
- data/lib/rdkafka/admin/delete_records_handle.rb +31 -0
- data/lib/rdkafka/admin/delete_records_report.rb +25 -0
- data/lib/rdkafka/admin/describe_acl_report.rb +2 -1
- data/lib/rdkafka/admin/list_consumer_groups_handle.rb +31 -0
- data/lib/rdkafka/admin/list_consumer_groups_report.rb +83 -0
- data/lib/rdkafka/admin/list_offsets_report.rb +5 -2
- data/lib/rdkafka/admin.rb +318 -199
- data/lib/rdkafka/bindings.rb +53 -12
- data/lib/rdkafka/callbacks/delete_records_handler.rb +38 -0
- data/lib/rdkafka/callbacks/list_consumer_groups_handler.rb +39 -0
- data/lib/rdkafka/callbacks.rb +4 -2
- data/lib/rdkafka/clients.rb +114 -0
- data/lib/rdkafka/config.rb +108 -56
- data/lib/rdkafka/consumer/allocator.rb +38 -0
- data/lib/rdkafka/consumer/topic_partition_list.rb +15 -10
- data/lib/rdkafka/consumer.rb +147 -74
- data/lib/rdkafka/defaults.rb +5 -7
- data/lib/rdkafka/helpers/list_offsets.rb +127 -0
- data/lib/rdkafka/helpers/oauth.rb +10 -5
- data/lib/rdkafka/native_kafka.rb +20 -7
- data/lib/rdkafka/producer/delivery_handle.rb +1 -2
- data/lib/rdkafka/producer.rb +25 -17
- data/lib/rdkafka/version.rb +1 -1
- data/lib/rdkafka.rb +11 -0
- data/package-lock.json +6 -6
- data/renovate.json +1 -1
- metadata +11 -2
data/lib/rdkafka/bindings.rb
CHANGED
|
@@ -58,6 +58,7 @@ module Rdkafka
|
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
RD_KAFKA_RESP_ERR__TIMED_OUT = -185
|
|
61
62
|
RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS = -175
|
|
62
63
|
RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS = -174
|
|
63
64
|
RD_KAFKA_RESP_ERR__STATE = -172
|
|
@@ -81,7 +82,7 @@ module Rdkafka
|
|
|
81
82
|
|
|
82
83
|
# This function comes from our patch on top of librdkafka. It allows os to load all the
|
|
83
84
|
# librdkafka components without initializing the client
|
|
84
|
-
#
|
|
85
|
+
# See: https://github.com/confluentinc/librdkafka/issues/4590
|
|
85
86
|
attach_function :rd_kafka_global_init, [], :void
|
|
86
87
|
|
|
87
88
|
# Polling
|
|
@@ -160,6 +161,7 @@ module Rdkafka
|
|
|
160
161
|
|
|
161
162
|
attach_function :rd_kafka_DescribeConfigs, [:pointer, :pointer, :size_t, :pointer, :pointer], :void, blocking: true
|
|
162
163
|
attach_function :rd_kafka_ConfigResource_new, [:int32, :pointer], :pointer
|
|
164
|
+
attach_function :rd_kafka_ConfigResource_destroy, [:pointer], :void
|
|
163
165
|
attach_function :rd_kafka_ConfigResource_destroy_array, [:pointer, :int32], :void
|
|
164
166
|
attach_function :rd_kafka_event_DescribeConfigs_result, [:pointer], :pointer
|
|
165
167
|
attach_function :rd_kafka_DescribeConfigs_result_resources, [:pointer, :pointer], :pointer
|
|
@@ -235,6 +237,8 @@ module Rdkafka
|
|
|
235
237
|
|
|
236
238
|
attach_function :rd_kafka_conf_new, [], :pointer
|
|
237
239
|
attach_function :rd_kafka_conf_set, [:pointer, :string, :string, :pointer, :int], :kafka_config_response
|
|
240
|
+
attach_function :rd_kafka_conf_get, [:pointer, :string, :pointer, :pointer], :kafka_config_response
|
|
241
|
+
attach_function :rd_kafka_conf, [:pointer], :pointer
|
|
238
242
|
attach_function :rd_kafka_conf_dump, [:pointer, :pointer], :pointer
|
|
239
243
|
attach_function :rd_kafka_conf_dump_free, [:pointer, :size_t], :void
|
|
240
244
|
attach_function :rd_kafka_conf_destroy, [:pointer], :void
|
|
@@ -257,10 +261,11 @@ module Rdkafka
|
|
|
257
261
|
|
|
258
262
|
# Queue IO Event Support - for fiber scheduler integration
|
|
259
263
|
# Enables notifications to a custom FD when queue transitions from empty to non-empty
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
+
# Arguments:
|
|
265
|
+
# - queue (rd_kafka_queue_t*) - the queue to monitor
|
|
266
|
+
# - fd (int) - file descriptor to write to (provide your own pipe/eventfd)
|
|
267
|
+
# - payload (const void*) - data to write to fd
|
|
268
|
+
# - size (size_t) - size of payload
|
|
264
269
|
attach_function :rd_kafka_queue_io_event_enable, [:pointer, :int, :pointer, :size_t], :void
|
|
265
270
|
# Per topic configs
|
|
266
271
|
attach_function :rd_kafka_topic_conf_new, [], :pointer
|
|
@@ -335,9 +340,10 @@ module Rdkafka
|
|
|
335
340
|
end
|
|
336
341
|
end
|
|
337
342
|
|
|
338
|
-
# The OAuth callback is currently global and contextless.
|
|
339
|
-
#
|
|
340
|
-
# The instance name will be provided in the callback, allowing the callback to
|
|
343
|
+
# The OAuth callback is currently global and contextless. This means that the callback will be
|
|
344
|
+
# called for all instances, and the callback must be able to determine to which instance it is
|
|
345
|
+
# associated. The instance name will be provided in the callback, allowing the callback to
|
|
346
|
+
# reference the correct instance.
|
|
341
347
|
#
|
|
342
348
|
# An example of how to use the instance name in the callback is given below.
|
|
343
349
|
# The `refresh_token` is configured as the `oauthbearer_token_refresh_callback`.
|
|
@@ -541,6 +547,37 @@ module Rdkafka
|
|
|
541
547
|
attach_function :rd_kafka_event_DeleteGroups_result, [:pointer], :pointer, blocking: true # rd_kafka_event_t* => rd_kafka_DeleteGroups_result_t*
|
|
542
548
|
attach_function :rd_kafka_DeleteGroups_result_groups, [:pointer, :pointer], :pointer, blocking: true # rd_kafka_DeleteGroups_result_t*, size_t* => rd_kafka_group_result_t**
|
|
543
549
|
|
|
550
|
+
# Delete Records
|
|
551
|
+
RD_KAFKA_ADMIN_OP_DELETERECORDS = 6 # rd_kafka_admin_op_t
|
|
552
|
+
RD_KAFKA_EVENT_DELETERECORDS_RESULT = 105 # rd_kafka_event_type_t
|
|
553
|
+
|
|
554
|
+
attach_function :rd_kafka_DeleteRecords, [:pointer, :pointer, :size_t, :pointer, :pointer], :void, blocking: true
|
|
555
|
+
attach_function :rd_kafka_DeleteRecords_new, [:pointer], :pointer, blocking: true
|
|
556
|
+
attach_function :rd_kafka_DeleteRecords_destroy, [:pointer], :void, blocking: true
|
|
557
|
+
attach_function :rd_kafka_event_DeleteRecords_result, [:pointer], :pointer, blocking: true # rd_kafka_event_t* => rd_kafka_DeleteRecords_result_t*
|
|
558
|
+
attach_function :rd_kafka_DeleteRecords_result_offsets, [:pointer], :pointer, blocking: true # rd_kafka_DeleteRecords_result_t* => rd_kafka_topic_partition_list_t*
|
|
559
|
+
|
|
560
|
+
# List Consumer Groups
|
|
561
|
+
RD_KAFKA_ADMIN_OP_LISTCONSUMERGROUPS = 12 # rd_kafka_admin_op_t
|
|
562
|
+
RD_KAFKA_EVENT_LISTCONSUMERGROUPS_RESULT = 0x2000 # rd_kafka_event_type_t
|
|
563
|
+
|
|
564
|
+
# Consumer group states (rd_kafka_consumer_group_state_t)
|
|
565
|
+
RD_KAFKA_CONSUMER_GROUP_STATE_UNKNOWN = 0
|
|
566
|
+
RD_KAFKA_CONSUMER_GROUP_STATE_PREPARING_REBALANCE = 1
|
|
567
|
+
RD_KAFKA_CONSUMER_GROUP_STATE_COMPLETING_REBALANCE = 2
|
|
568
|
+
RD_KAFKA_CONSUMER_GROUP_STATE_STABLE = 3
|
|
569
|
+
RD_KAFKA_CONSUMER_GROUP_STATE_DEAD = 4
|
|
570
|
+
RD_KAFKA_CONSUMER_GROUP_STATE_EMPTY = 5
|
|
571
|
+
|
|
572
|
+
attach_function :rd_kafka_ListConsumerGroups, [:pointer, :pointer, :pointer], :void, blocking: true
|
|
573
|
+
attach_function :rd_kafka_event_ListConsumerGroups_result, [:pointer], :pointer, blocking: true # rd_kafka_event_t* => rd_kafka_ListConsumerGroups_result_t*
|
|
574
|
+
attach_function :rd_kafka_ListConsumerGroups_result_valid, [:pointer, :pointer], :pointer, blocking: true # result*, size_t* => rd_kafka_ConsumerGroupListing_t**
|
|
575
|
+
attach_function :rd_kafka_ListConsumerGroups_result_errors, [:pointer, :pointer], :pointer, blocking: true # result*, size_t* => rd_kafka_error_t**
|
|
576
|
+
attach_function :rd_kafka_ConsumerGroupListing_group_id, [:pointer], :pointer, blocking: true # => const char*
|
|
577
|
+
attach_function :rd_kafka_ConsumerGroupListing_is_simple_consumer_group, [:pointer], :int, blocking: true
|
|
578
|
+
attach_function :rd_kafka_ConsumerGroupListing_state, [:pointer], :int, blocking: true
|
|
579
|
+
attach_function :rd_kafka_consumer_group_state_name, [:int], :pointer, blocking: true # => const char*
|
|
580
|
+
|
|
544
581
|
# Background Queue and Callback
|
|
545
582
|
|
|
546
583
|
attach_function :rd_kafka_conf_set_background_event_cb, [:pointer, :pointer], :void
|
|
@@ -603,21 +640,24 @@ module Rdkafka
|
|
|
603
640
|
attach_function :rd_kafka_AclBindingFilter_new, [:int32, :pointer, :int32, :pointer, :pointer, :int32, :int32, :pointer, :size_t], :pointer
|
|
604
641
|
attach_function :rd_kafka_AclBinding_destroy, [:pointer], :void
|
|
605
642
|
|
|
606
|
-
# rd_kafka_ResourceType_t -
|
|
643
|
+
# rd_kafka_ResourceType_t -
|
|
644
|
+
# https://github.com/confluentinc/librdkafka/blob/292d2a66b992/src/rdkafka.h#L7307
|
|
607
645
|
RD_KAFKA_RESOURCE_ANY = 1
|
|
608
646
|
RD_KAFKA_RESOURCE_TOPIC = 2
|
|
609
647
|
RD_KAFKA_RESOURCE_GROUP = 3
|
|
610
648
|
RD_KAFKA_RESOURCE_BROKER = 4
|
|
611
649
|
RD_KAFKA_RESOURCE_TRANSACTIONAL_ID = 5
|
|
612
650
|
|
|
613
|
-
# rd_kafka_ResourcePatternType_t -
|
|
651
|
+
# rd_kafka_ResourcePatternType_t -
|
|
652
|
+
# https://github.com/confluentinc/librdkafka/blob/292d2a66b992/src/rdkafka.h#L7320
|
|
614
653
|
RD_KAFKA_RESOURCE_PATTERN_UNKNOWN = 0
|
|
615
654
|
RD_KAFKA_RESOURCE_PATTERN_ANY = 1
|
|
616
655
|
RD_KAFKA_RESOURCE_PATTERN_MATCH = 2
|
|
617
656
|
RD_KAFKA_RESOURCE_PATTERN_LITERAL = 3
|
|
618
657
|
RD_KAFKA_RESOURCE_PATTERN_PREFIXED = 4
|
|
619
658
|
|
|
620
|
-
# rd_kafka_AclOperation_t -
|
|
659
|
+
# rd_kafka_AclOperation_t -
|
|
660
|
+
# https://github.com/confluentinc/librdkafka/blob/292d2a66b992/src/rdkafka.h#L8403
|
|
621
661
|
RD_KAFKA_ACL_OPERATION_ANY = 1
|
|
622
662
|
RD_KAFKA_ACL_OPERATION_ALL = 2
|
|
623
663
|
RD_KAFKA_ACL_OPERATION_READ = 3
|
|
@@ -631,7 +671,8 @@ module Rdkafka
|
|
|
631
671
|
RD_KAFKA_ACL_OPERATION_ALTER_CONFIGS = 11
|
|
632
672
|
RD_KAFKA_ACL_OPERATION_IDEMPOTENT_WRITE = 12
|
|
633
673
|
|
|
634
|
-
# rd_kafka_AclPermissionType_t -
|
|
674
|
+
# rd_kafka_AclPermissionType_t -
|
|
675
|
+
# https://github.com/confluentinc/librdkafka/blob/292d2a66b992/src/rdkafka.h#L8435
|
|
635
676
|
RD_KAFKA_ACL_PERMISSION_TYPE_ANY = 1
|
|
636
677
|
RD_KAFKA_ACL_PERMISSION_TYPE_DENY = 2
|
|
637
678
|
RD_KAFKA_ACL_PERMISSION_TYPE_ALLOW = 3
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
module Callbacks
|
|
5
|
+
# Handles `RD_KAFKA_EVENT_DELETERECORDS_RESULT` events
|
|
6
|
+
# @private
|
|
7
|
+
class DeleteRecordsHandler < BaseHandler
|
|
8
|
+
class << self
|
|
9
|
+
# Resolves the delete-records 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_ptr = Rdkafka::Bindings.rd_kafka_event_DeleteRecords_result(event_ptr)
|
|
14
|
+
handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
|
|
15
|
+
|
|
16
|
+
return unless (handle = Rdkafka::Admin::DeleteRecordsHandle.remove(handle_ptr.address))
|
|
17
|
+
|
|
18
|
+
# An operation-level error (e.g. timeout or a closed client) is delivered on the event
|
|
19
|
+
# itself, with no per-partition result to parse.
|
|
20
|
+
return if resolve_operation_error(event_ptr, handle)
|
|
21
|
+
|
|
22
|
+
handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
23
|
+
|
|
24
|
+
# Parsing must copy everything out of event-owned memory before the event is destroyed.
|
|
25
|
+
# An exception here is captured and re-raised on the waiting thread, since it cannot
|
|
26
|
+
# unwind through librdkafka native frames.
|
|
27
|
+
handle.result = begin
|
|
28
|
+
Rdkafka::Admin::DeleteRecordsReport.new(result_ptr)
|
|
29
|
+
rescue => e
|
|
30
|
+
e
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
handle.unlock
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
module Callbacks
|
|
5
|
+
# Handles `RD_KAFKA_EVENT_LISTCONSUMERGROUPS_RESULT` events
|
|
6
|
+
# @private
|
|
7
|
+
class ListConsumerGroupsHandler < BaseHandler
|
|
8
|
+
class << self
|
|
9
|
+
# Resolves the list-consumer-groups 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_ptr = Rdkafka::Bindings.rd_kafka_event_ListConsumerGroups_result(event_ptr)
|
|
14
|
+
handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
|
|
15
|
+
|
|
16
|
+
return unless (handle = Rdkafka::Admin::ListConsumerGroupsHandle.remove(handle_ptr.address))
|
|
17
|
+
|
|
18
|
+
# An operation-level error (e.g. timeout or a closed client) is delivered on the event
|
|
19
|
+
# itself; per the source feature's all-or-nothing contract we surface it as the result
|
|
20
|
+
# rather than returning a partial listing.
|
|
21
|
+
return if resolve_operation_error(event_ptr, handle)
|
|
22
|
+
|
|
23
|
+
handle[:response] = Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
|
|
24
|
+
|
|
25
|
+
# Parsing must copy everything out of event-owned memory before the event is destroyed.
|
|
26
|
+
# An exception here is captured and re-raised on the waiting thread, since it cannot
|
|
27
|
+
# unwind through librdkafka native frames.
|
|
28
|
+
handle.result = begin
|
|
29
|
+
Rdkafka::Admin::ListConsumerGroupsReport.new(result_ptr)
|
|
30
|
+
rescue => e
|
|
31
|
+
e
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
handle.unlock
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/rdkafka/callbacks.rb
CHANGED
|
@@ -213,12 +213,14 @@ module Rdkafka
|
|
|
213
213
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETETOPICS_RESULT then DeleteTopicHandler
|
|
214
214
|
when Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_CREATEPARTITIONS_RESULT then CreatePartitionsHandler
|
|
215
215
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEGROUPS_RESULT then DeleteGroupsHandler
|
|
216
|
+
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETERECORDS_RESULT then DeleteRecordsHandler
|
|
216
217
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_CREATEACLS_RESULT then CreateAclHandler
|
|
217
218
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DELETEACLS_RESULT then DeleteAclHandler
|
|
218
219
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBEACLS_RESULT then DescribeAclHandler
|
|
219
220
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_DESCRIBECONFIGS_RESULT then DescribeConfigsHandler
|
|
220
221
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_INCREMENTALALTERCONFIGS_RESULT then IncrementalAlterConfigsHandler
|
|
221
222
|
when Rdkafka::Bindings::RD_KAFKA_EVENT_LISTOFFSETS_RESULT then ListOffsetsHandler
|
|
223
|
+
when Rdkafka::Bindings::RD_KAFKA_EVENT_LISTCONSUMERGROUPS_RESULT then ListConsumerGroupsHandler
|
|
222
224
|
end
|
|
223
225
|
|
|
224
226
|
handler&.call(event_ptr)
|
|
@@ -280,9 +282,9 @@ module Rdkafka
|
|
|
280
282
|
end
|
|
281
283
|
end
|
|
282
284
|
|
|
283
|
-
#
|
|
285
|
+
# @!visibility private
|
|
284
286
|
@@mutex = Mutex.new
|
|
285
|
-
#
|
|
287
|
+
# @!visibility private
|
|
286
288
|
@@current_pid = nil
|
|
287
289
|
|
|
288
290
|
class << self
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
# Tracks live public clients so they can be closed before Ruby begins forced finalization.
|
|
5
|
+
# @private
|
|
6
|
+
module Clients
|
|
7
|
+
# Ruby rewrote `ObjectSpace::WeakMap` in 3.3. On 3.2 and earlier its `[]` can hand back a
|
|
8
|
+
# foreign or already-freed object for a key, which raises `NoMethodError` or segfaults the VM
|
|
9
|
+
# the moment the value is touched. Track weakly only where that is safe and fall back to a
|
|
10
|
+
# strong registry - pruned as clients close - on older Rubies. The strong registry holds an
|
|
11
|
+
# open, un-closed client until shutdown, which is exactly when this cleanup needs it anyway.
|
|
12
|
+
WEAK_TRACKING = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.3")
|
|
13
|
+
|
|
14
|
+
@pid = Process.pid
|
|
15
|
+
@mutex = Mutex.new
|
|
16
|
+
|
|
17
|
+
if WEAK_TRACKING
|
|
18
|
+
@clients = ObjectSpace::WeakMap.new
|
|
19
|
+
@tokens = [].freeze
|
|
20
|
+
else
|
|
21
|
+
@clients = {}.freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Registers a successfully constructed client so it can be closed during process shutdown.
|
|
25
|
+
# @param client [Consumer, Producer, Admin] client to close during process shutdown
|
|
26
|
+
# @return [Consumer, Producer, Admin] the registered client
|
|
27
|
+
def self.register(client)
|
|
28
|
+
reset_after_fork
|
|
29
|
+
|
|
30
|
+
@mutex.synchronize do
|
|
31
|
+
if WEAK_TRACKING
|
|
32
|
+
live_clients
|
|
33
|
+
|
|
34
|
+
# The strongly-held token is the key and the client is the weak value, so registering a
|
|
35
|
+
# client never keeps it alive. Enumerating weak keys can expose reclaimed slots, so we
|
|
36
|
+
# look the value up by token instead.
|
|
37
|
+
token = Object.new
|
|
38
|
+
@clients[token] = client
|
|
39
|
+
@tokens = (@tokens + [token]).freeze
|
|
40
|
+
else
|
|
41
|
+
# Strong references, so drop clients that have already been closed to bound growth.
|
|
42
|
+
live = @clients.reject { |_id, existing| existing.closed? }
|
|
43
|
+
live[client.object_id] = client
|
|
44
|
+
@clients = live.freeze
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
client
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Closes each live client registered in this process.
|
|
52
|
+
# @return [nil]
|
|
53
|
+
# @raise [StandardError] the first close error, after attempting every client
|
|
54
|
+
def self.close_all
|
|
55
|
+
return unless @pid == Process.pid
|
|
56
|
+
|
|
57
|
+
clients = @mutex.synchronize { current_clients }
|
|
58
|
+
first_error = nil
|
|
59
|
+
|
|
60
|
+
clients.each do |client|
|
|
61
|
+
client.close unless client.closed?
|
|
62
|
+
rescue => error
|
|
63
|
+
first_error ||= error
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
raise first_error if first_error
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns the currently registered live clients.
|
|
70
|
+
# @return [Array<Consumer, Producer, Admin>] live clients
|
|
71
|
+
# @private
|
|
72
|
+
def self.current_clients
|
|
73
|
+
WEAK_TRACKING ? live_clients : @clients.values
|
|
74
|
+
end
|
|
75
|
+
private_class_method :current_clients
|
|
76
|
+
|
|
77
|
+
# Returns the live clients and releases tokens whose clients have been collected.
|
|
78
|
+
# Weak-tracking Rubies only.
|
|
79
|
+
# @return [Array<Consumer, Producer, Admin>] live clients
|
|
80
|
+
# @private
|
|
81
|
+
def self.live_clients
|
|
82
|
+
clients = []
|
|
83
|
+
tokens = []
|
|
84
|
+
|
|
85
|
+
@tokens.each do |token|
|
|
86
|
+
client = @clients[token]
|
|
87
|
+
clients << client if client
|
|
88
|
+
tokens << token if client
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
@tokens = tokens.freeze
|
|
92
|
+
clients
|
|
93
|
+
end
|
|
94
|
+
private_class_method :live_clients
|
|
95
|
+
|
|
96
|
+
# Replaces inherited state before registering a client in a forked child.
|
|
97
|
+
# @return [nil]
|
|
98
|
+
# @private
|
|
99
|
+
def self.reset_after_fork
|
|
100
|
+
return if @pid == Process.pid
|
|
101
|
+
|
|
102
|
+
if WEAK_TRACKING
|
|
103
|
+
@clients = ObjectSpace::WeakMap.new
|
|
104
|
+
@tokens = [].freeze
|
|
105
|
+
else
|
|
106
|
+
@clients = {}.freeze
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@pid = Process.pid
|
|
110
|
+
@mutex = Mutex.new
|
|
111
|
+
end
|
|
112
|
+
private_class_method :reset_after_fork
|
|
113
|
+
end
|
|
114
|
+
end
|
data/lib/rdkafka/config.rb
CHANGED
|
@@ -3,24 +3,24 @@
|
|
|
3
3
|
module Rdkafka
|
|
4
4
|
# Configuration for a Kafka consumer or producer. You can create an instance and use
|
|
5
5
|
# the consumer and producer methods to create a client. Documentation of the available
|
|
6
|
-
# configuration options is available on
|
|
6
|
+
# configuration options is available on
|
|
7
|
+
# https://github.com/confluentinc/librdkafka/blob/master/CONFIGURATION.md.
|
|
7
8
|
class Config
|
|
8
|
-
#
|
|
9
|
+
# @!visibility private
|
|
9
10
|
@@logger = Logger.new($stdout)
|
|
10
|
-
#
|
|
11
|
+
# @!visibility private
|
|
11
12
|
@@statistics_callback = nil
|
|
12
|
-
#
|
|
13
|
+
# @!visibility private
|
|
13
14
|
@@error_callback = nil
|
|
14
|
-
#
|
|
15
|
+
# @!visibility private
|
|
15
16
|
@@opaques = ObjectSpace::WeakMap.new
|
|
16
|
-
#
|
|
17
|
+
# @!visibility private
|
|
17
18
|
@@log_queue = Queue.new
|
|
18
|
-
# We memoize thread on
|
|
19
|
-
# This allows us also to restart logger thread on forks
|
|
19
|
+
# We memoize thread on first log flush. This allows us also to restart logger thread on forks
|
|
20
20
|
@@log_thread = nil
|
|
21
|
-
#
|
|
21
|
+
# @!visibility private
|
|
22
22
|
@@log_mutex = Mutex.new
|
|
23
|
-
#
|
|
23
|
+
# @!visibility private
|
|
24
24
|
@@oauthbearer_token_refresh_callback = nil
|
|
25
25
|
|
|
26
26
|
# Returns the current logger, by default this is a logger to stdout.
|
|
@@ -69,7 +69,8 @@ module Rdkafka
|
|
|
69
69
|
|
|
70
70
|
# Set a callback that will be called every time the underlying client emits statistics.
|
|
71
71
|
# You can configure if and how often this happens using `statistics.interval.ms`.
|
|
72
|
-
# The callback is called with a hash that's documented here:
|
|
72
|
+
# The callback is called with a hash that's documented here:
|
|
73
|
+
# https://github.com/confluentinc/librdkafka/blob/master/STATISTICS.md
|
|
73
74
|
#
|
|
74
75
|
# @param callback [Proc, #call, nil] callable object or nil to clear
|
|
75
76
|
# @return [nil]
|
|
@@ -86,7 +87,8 @@ module Rdkafka
|
|
|
86
87
|
end
|
|
87
88
|
|
|
88
89
|
# Set a callback that will be called every time the underlying client emits an error.
|
|
89
|
-
# If this callback is not set, global errors such as brokers becoming unavailable will only be
|
|
90
|
+
# If this callback is not set, global errors such as brokers becoming unavailable will only be
|
|
91
|
+
# sent to the logger, as defined by librdkafka.
|
|
90
92
|
# The callback is called with an instance of RdKafka::Error.
|
|
91
93
|
#
|
|
92
94
|
# @param callback [Proc, #call, nil] callable object to handle errors or nil to clear
|
|
@@ -197,26 +199,26 @@ module Rdkafka
|
|
|
197
199
|
opaque = Opaque.new
|
|
198
200
|
config = native_config(opaque)
|
|
199
201
|
|
|
202
|
+
# Set callback to receive background events, so admin-style operations issued on the
|
|
203
|
+
# consumer handle (e.g. Consumer#list_offsets) get their results dispatched back to their
|
|
204
|
+
# handles. librdkafka spawns its internally-managed background thread because of this.
|
|
205
|
+
Rdkafka::Bindings.rd_kafka_conf_set_background_event_cb(config, Rdkafka::Callbacks::BackgroundEventCallbackFunction)
|
|
206
|
+
|
|
200
207
|
if @consumer_rebalance_listener
|
|
201
208
|
opaque.consumer_rebalance_listener = @consumer_rebalance_listener
|
|
202
209
|
Rdkafka::Bindings.rd_kafka_conf_set_rebalance_cb(config, Rdkafka::Bindings::RebalanceCallback)
|
|
203
210
|
end
|
|
204
211
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
Rdkafka::
|
|
214
|
-
|
|
215
|
-
run_polling_thread: false,
|
|
216
|
-
opaque: opaque,
|
|
217
|
-
auto_start: native_kafka_auto_start
|
|
218
|
-
)
|
|
219
|
-
)
|
|
212
|
+
build_native_client(
|
|
213
|
+
config,
|
|
214
|
+
:rd_kafka_consumer,
|
|
215
|
+
opaque: opaque,
|
|
216
|
+
run_polling_thread: false,
|
|
217
|
+
auto_start: native_kafka_auto_start
|
|
218
|
+
) do |kafka, native_kafka|
|
|
219
|
+
Rdkafka::Bindings.rd_kafka_poll_set_consumer(kafka) if @consumer_poll_set
|
|
220
|
+
Rdkafka::Consumer.new(native_kafka)
|
|
221
|
+
end
|
|
220
222
|
end
|
|
221
223
|
|
|
222
224
|
# Create a producer with this configuration.
|
|
@@ -240,19 +242,17 @@ module Rdkafka
|
|
|
240
242
|
# Return producer with Kafka client
|
|
241
243
|
partitioner_name = self[:partitioner] || self["partitioner"]
|
|
242
244
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
).tap do |producer|
|
|
255
|
-
opaque.producer = producer
|
|
245
|
+
build_native_client(
|
|
246
|
+
config,
|
|
247
|
+
:rd_kafka_producer,
|
|
248
|
+
opaque: opaque,
|
|
249
|
+
run_polling_thread: run_polling_thread,
|
|
250
|
+
auto_start: native_kafka_auto_start,
|
|
251
|
+
timeout_ms: native_kafka_poll_timeout_ms
|
|
252
|
+
) do |_kafka, native_kafka|
|
|
253
|
+
Rdkafka::Producer.new(native_kafka, partitioner_name).tap do |producer|
|
|
254
|
+
opaque.producer = producer
|
|
255
|
+
end
|
|
256
256
|
end
|
|
257
257
|
end
|
|
258
258
|
|
|
@@ -272,17 +272,16 @@ module Rdkafka
|
|
|
272
272
|
config = native_config(opaque)
|
|
273
273
|
Rdkafka::Bindings.rd_kafka_conf_set_background_event_cb(config, Rdkafka::Callbacks::BackgroundEventCallbackFunction)
|
|
274
274
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
)
|
|
275
|
+
build_native_client(
|
|
276
|
+
config,
|
|
277
|
+
:rd_kafka_producer,
|
|
278
|
+
opaque: opaque,
|
|
279
|
+
run_polling_thread: run_polling_thread,
|
|
280
|
+
auto_start: native_kafka_auto_start,
|
|
281
|
+
timeout_ms: native_kafka_poll_timeout_ms
|
|
282
|
+
) do |_kafka, native_kafka|
|
|
283
|
+
Rdkafka::Admin.new(native_kafka)
|
|
284
|
+
end
|
|
286
285
|
end
|
|
287
286
|
|
|
288
287
|
# Returns all configuration properties and their current values for this config.
|
|
@@ -290,6 +289,9 @@ module Rdkafka
|
|
|
290
289
|
# Uses `rd_kafka_conf_dump` to retrieve every property (including defaults and
|
|
291
290
|
# internal properties like `client.software.name`) as a flat Hash.
|
|
292
291
|
#
|
|
292
|
+
# @return [Hash{Symbol => String}] property names mapped to their current values
|
|
293
|
+
#
|
|
294
|
+
# @raise [ConfigError] When the configuration contains invalid options
|
|
293
295
|
# @note The librdkafka C API does not distinguish between producer-only, consumer-only,
|
|
294
296
|
# and global properties at the configuration level. All properties are returned
|
|
295
297
|
# regardless of the intended client type.
|
|
@@ -297,10 +299,6 @@ module Rdkafka
|
|
|
297
299
|
# @note The returned Hash may include sensitive values such as authentication
|
|
298
300
|
# credentials and key passwords. Do not log or serialize the returned data
|
|
299
301
|
# unless you have explicitly redacted secret entries.
|
|
300
|
-
#
|
|
301
|
-
# @return [Hash{Symbol => String}] property names mapped to their current values
|
|
302
|
-
#
|
|
303
|
-
# @raise [ConfigError] When the configuration contains invalid options
|
|
304
302
|
def describe_properties
|
|
305
303
|
config = nil
|
|
306
304
|
dump_ptr = nil
|
|
@@ -325,7 +323,8 @@ module Rdkafka
|
|
|
325
323
|
Rdkafka::Bindings.rd_kafka_conf_destroy(config) if config
|
|
326
324
|
end
|
|
327
325
|
|
|
328
|
-
# Error that is returned by the underlying rdkafka error if an invalid configuration option is
|
|
326
|
+
# Error that is returned by the underlying rdkafka error if an invalid configuration option is
|
|
327
|
+
# present.
|
|
329
328
|
class ConfigError < RuntimeError; end
|
|
330
329
|
|
|
331
330
|
# Error that is returned by the underlying rdkafka library if the client cannot be created.
|
|
@@ -336,6 +335,56 @@ module Rdkafka
|
|
|
336
335
|
|
|
337
336
|
private
|
|
338
337
|
|
|
338
|
+
# Builds a public client around a native Kafka handle, destroying the current owner if setup
|
|
339
|
+
# fails before ownership is transferred to the caller.
|
|
340
|
+
# @param config [FFI::Pointer] native rdkafka configuration
|
|
341
|
+
# @param type [Symbol] native client type
|
|
342
|
+
# @param opaque [Object] callback context retained for the native client
|
|
343
|
+
# @param run_polling_thread [Boolean] whether to run a Ruby polling thread
|
|
344
|
+
# @param auto_start [Boolean] whether to start the polling thread after wrapping the client
|
|
345
|
+
# @param timeout_ms [Integer] polling timeout in milliseconds
|
|
346
|
+
# @yieldparam kafka [FFI::Pointer] native Kafka handle
|
|
347
|
+
# @yieldparam native_kafka [NativeKafka] Ruby owner of the native handle
|
|
348
|
+
# @return [Consumer, Producer, Admin] the constructed public client
|
|
349
|
+
# @private
|
|
350
|
+
def build_native_client(
|
|
351
|
+
config,
|
|
352
|
+
type,
|
|
353
|
+
opaque:,
|
|
354
|
+
run_polling_thread:,
|
|
355
|
+
auto_start:,
|
|
356
|
+
timeout_ms: Defaults::NATIVE_KAFKA_POLL_TIMEOUT_MS
|
|
357
|
+
)
|
|
358
|
+
kafka = native_kafka(config, type)
|
|
359
|
+
native_kafka = nil
|
|
360
|
+
client = nil
|
|
361
|
+
|
|
362
|
+
begin
|
|
363
|
+
native_kafka = Rdkafka::NativeKafka.new(
|
|
364
|
+
kafka,
|
|
365
|
+
run_polling_thread: run_polling_thread,
|
|
366
|
+
opaque: opaque,
|
|
367
|
+
auto_start: false,
|
|
368
|
+
timeout_ms: timeout_ms
|
|
369
|
+
)
|
|
370
|
+
client = yield(kafka, native_kafka)
|
|
371
|
+
native_kafka.start if auto_start
|
|
372
|
+
Rdkafka::Clients.register(client)
|
|
373
|
+
rescue Exception
|
|
374
|
+
owner = client || native_kafka
|
|
375
|
+
|
|
376
|
+
begin
|
|
377
|
+
owner ? owner.close : Rdkafka::Bindings.rd_kafka_destroy(kafka)
|
|
378
|
+
rescue Exception => err
|
|
379
|
+
# Never let a teardown failure replace the error that caused it: that error is what the
|
|
380
|
+
# caller needs to see, and it is re-raised below regardless.
|
|
381
|
+
Rdkafka::Config.logger.error("Unhandled exception: #{err.class} - #{err.message}")
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
raise
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
339
388
|
# This method is only intended to be used to create a client,
|
|
340
389
|
# using it in another way will leak memory.
|
|
341
390
|
#
|
|
@@ -422,6 +471,9 @@ module Rdkafka
|
|
|
422
471
|
|
|
423
472
|
# Return handle which should be closed using rd_kafka_destroy after usage.
|
|
424
473
|
handle
|
|
474
|
+
rescue Exception
|
|
475
|
+
Rdkafka::Bindings.rd_kafka_destroy(handle) if handle && !handle.null?
|
|
476
|
+
raise
|
|
425
477
|
end
|
|
426
478
|
end
|
|
427
479
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rdkafka
|
|
4
|
+
class Consumer
|
|
5
|
+
# Creates FFI pointers from the process-global allocator so that memory handed to librdkafka
|
|
6
|
+
# can be released by it with the matching `free`.
|
|
7
|
+
module Allocator
|
|
8
|
+
extend FFI::Library
|
|
9
|
+
|
|
10
|
+
ffi_lib FFI::CURRENT_PROCESS # use process-global malloc not FFI::Library::LIBC
|
|
11
|
+
attach_function :malloc, [:size_t], :pointer
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
# Return an FFI pointer to a string allocated from the global allocator
|
|
15
|
+
# so that it can be released by another library.
|
|
16
|
+
#
|
|
17
|
+
# @private
|
|
18
|
+
#
|
|
19
|
+
# @param string [String] Ruby string that will be copied into the new
|
|
20
|
+
# string pointer.
|
|
21
|
+
#
|
|
22
|
+
# @return [FFI::Pointer]
|
|
23
|
+
def string_pointer(string)
|
|
24
|
+
bytes = string.to_s
|
|
25
|
+
pointer = malloc(bytes.bytesize + 1)
|
|
26
|
+
Kernel.raise(NoMemoryError, "malloc failed") if pointer.null?
|
|
27
|
+
|
|
28
|
+
pointer.put_bytes(0, bytes)
|
|
29
|
+
pointer.put_char(bytes.bytesize, 0) # NUL terminator
|
|
30
|
+
|
|
31
|
+
pointer.autorelease = false
|
|
32
|
+
|
|
33
|
+
pointer
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|