rdkafka 0.22.2 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +101 -3
  3. data/README.md +21 -14
  4. data/Rakefile +21 -21
  5. data/bin/verify_kafka_warnings +39 -0
  6. data/dist/{librdkafka-2.8.0.tar.gz → librdkafka-2.14.2.tar.gz} +0 -0
  7. data/docker-compose-ssl.yml +35 -0
  8. data/docker-compose.yml +2 -2
  9. data/ext/Rakefile +27 -27
  10. data/lib/rdkafka/abstract_handle.rb +54 -7
  11. data/lib/rdkafka/admin/acl_binding_result.rb +5 -5
  12. data/lib/rdkafka/admin/config_binding_result.rb +2 -2
  13. data/lib/rdkafka/admin/config_resource_binding_result.rb +1 -0
  14. data/lib/rdkafka/admin/create_acl_handle.rb +10 -6
  15. data/lib/rdkafka/admin/create_acl_report.rb +3 -2
  16. data/lib/rdkafka/admin/create_partitions_handle.rb +8 -7
  17. data/lib/rdkafka/admin/create_partitions_report.rb +1 -0
  18. data/lib/rdkafka/admin/create_topic_handle.rb +8 -7
  19. data/lib/rdkafka/admin/create_topic_report.rb +3 -0
  20. data/lib/rdkafka/admin/delete_acl_handle.rb +9 -8
  21. data/lib/rdkafka/admin/delete_acl_report.rb +5 -3
  22. data/lib/rdkafka/admin/delete_groups_handle.rb +10 -7
  23. data/lib/rdkafka/admin/delete_groups_report.rb +3 -0
  24. data/lib/rdkafka/admin/delete_topic_handle.rb +8 -7
  25. data/lib/rdkafka/admin/delete_topic_report.rb +3 -0
  26. data/lib/rdkafka/admin/describe_acl_handle.rb +9 -8
  27. data/lib/rdkafka/admin/describe_acl_report.rb +5 -3
  28. data/lib/rdkafka/admin/describe_configs_handle.rb +7 -10
  29. data/lib/rdkafka/admin/describe_configs_report.rb +8 -6
  30. data/lib/rdkafka/admin/incremental_alter_configs_handle.rb +7 -10
  31. data/lib/rdkafka/admin/incremental_alter_configs_report.rb +8 -6
  32. data/lib/rdkafka/admin/list_offsets_handle.rb +31 -0
  33. data/lib/rdkafka/admin/list_offsets_report.rb +51 -0
  34. data/lib/rdkafka/admin.rb +357 -159
  35. data/lib/rdkafka/bindings.rb +205 -111
  36. data/lib/rdkafka/callbacks/base_handler.rb +62 -0
  37. data/lib/rdkafka/callbacks/create_acl_handler.rb +37 -0
  38. data/lib/rdkafka/callbacks/create_partitions_handler.rb +37 -0
  39. data/lib/rdkafka/callbacks/create_topic_handler.rb +37 -0
  40. data/lib/rdkafka/callbacks/delete_acl_handler.rb +42 -0
  41. data/lib/rdkafka/callbacks/delete_groups_handler.rb +37 -0
  42. data/lib/rdkafka/callbacks/delete_topic_handler.rb +37 -0
  43. data/lib/rdkafka/callbacks/describe_acl_handler.rb +35 -0
  44. data/lib/rdkafka/callbacks/describe_configs_handler.rb +42 -0
  45. data/lib/rdkafka/callbacks/incremental_alter_configs_handler.rb +42 -0
  46. data/lib/rdkafka/callbacks/list_offsets_handler.rb +42 -0
  47. data/lib/rdkafka/callbacks.rb +125 -216
  48. data/lib/rdkafka/config.rb +126 -66
  49. data/lib/rdkafka/consumer/headers.rb +22 -7
  50. data/lib/rdkafka/consumer/message.rb +12 -11
  51. data/lib/rdkafka/consumer/partition.rb +15 -4
  52. data/lib/rdkafka/consumer/topic_partition_list.rb +64 -38
  53. data/lib/rdkafka/consumer.rb +513 -59
  54. data/lib/rdkafka/defaults.rb +125 -0
  55. data/lib/rdkafka/error.rb +40 -14
  56. data/lib/rdkafka/helpers/metadata.rb +29 -0
  57. data/lib/rdkafka/helpers/oauth.rb +45 -13
  58. data/lib/rdkafka/helpers/time.rb +5 -0
  59. data/lib/rdkafka/metadata.rb +128 -37
  60. data/lib/rdkafka/native_kafka.rb +91 -8
  61. data/lib/rdkafka/producer/delivery_handle.rb +6 -6
  62. data/lib/rdkafka/producer/delivery_report.rb +10 -6
  63. data/lib/rdkafka/producer/partitions_count_cache.rb +51 -55
  64. data/lib/rdkafka/producer.rb +193 -95
  65. data/lib/rdkafka/version.rb +6 -3
  66. data/lib/rdkafka.rb +15 -0
  67. data/package-lock.json +331 -0
  68. data/package.json +9 -0
  69. data/rdkafka.gemspec +58 -36
  70. data/renovate.json +39 -23
  71. metadata +39 -125
  72. data/.github/CODEOWNERS +0 -3
  73. data/.github/FUNDING.yml +0 -1
  74. data/.github/workflows/ci_linux_x86_64_gnu.yml +0 -271
  75. data/.github/workflows/ci_linux_x86_64_musl.yml +0 -194
  76. data/.github/workflows/ci_macos_arm64.yml +0 -284
  77. data/.github/workflows/push_linux_x86_64_gnu.yml +0 -65
  78. data/.github/workflows/push_linux_x86_64_musl.yml +0 -79
  79. data/.github/workflows/push_macos_arm64.yml +0 -54
  80. data/.github/workflows/push_ruby.yml +0 -37
  81. data/.github/workflows/verify-action-pins.yml +0 -16
  82. data/.gitignore +0 -14
  83. data/.rspec +0 -2
  84. data/.ruby-gemset +0 -1
  85. data/.ruby-version +0 -1
  86. data/.yardopts +0 -2
  87. data/Gemfile +0 -5
  88. data/ext/README.md +0 -19
  89. data/ext/build_common.sh +0 -361
  90. data/ext/build_linux_x86_64_gnu.sh +0 -306
  91. data/ext/build_linux_x86_64_musl.sh +0 -763
  92. data/ext/build_macos_arm64.sh +0 -550
  93. data/spec/rdkafka/abstract_handle_spec.rb +0 -117
  94. data/spec/rdkafka/admin/create_acl_handle_spec.rb +0 -56
  95. data/spec/rdkafka/admin/create_acl_report_spec.rb +0 -18
  96. data/spec/rdkafka/admin/create_topic_handle_spec.rb +0 -52
  97. data/spec/rdkafka/admin/create_topic_report_spec.rb +0 -16
  98. data/spec/rdkafka/admin/delete_acl_handle_spec.rb +0 -85
  99. data/spec/rdkafka/admin/delete_acl_report_spec.rb +0 -72
  100. data/spec/rdkafka/admin/delete_topic_handle_spec.rb +0 -52
  101. data/spec/rdkafka/admin/delete_topic_report_spec.rb +0 -16
  102. data/spec/rdkafka/admin/describe_acl_handle_spec.rb +0 -85
  103. data/spec/rdkafka/admin/describe_acl_report_spec.rb +0 -73
  104. data/spec/rdkafka/admin_spec.rb +0 -971
  105. data/spec/rdkafka/bindings_spec.rb +0 -199
  106. data/spec/rdkafka/callbacks_spec.rb +0 -20
  107. data/spec/rdkafka/config_spec.rb +0 -258
  108. data/spec/rdkafka/consumer/headers_spec.rb +0 -73
  109. data/spec/rdkafka/consumer/message_spec.rb +0 -139
  110. data/spec/rdkafka/consumer/partition_spec.rb +0 -57
  111. data/spec/rdkafka/consumer/topic_partition_list_spec.rb +0 -248
  112. data/spec/rdkafka/consumer_spec.rb +0 -1274
  113. data/spec/rdkafka/error_spec.rb +0 -89
  114. data/spec/rdkafka/metadata_spec.rb +0 -79
  115. data/spec/rdkafka/native_kafka_spec.rb +0 -130
  116. data/spec/rdkafka/producer/delivery_handle_spec.rb +0 -45
  117. data/spec/rdkafka/producer/delivery_report_spec.rb +0 -25
  118. data/spec/rdkafka/producer/partitions_count_cache_spec.rb +0 -359
  119. data/spec/rdkafka/producer_spec.rb +0 -1345
  120. data/spec/spec_helper.rb +0 -195
@@ -2,18 +2,61 @@
2
2
 
3
3
  module Rdkafka
4
4
  # @private
5
+ #
6
+ # @note
7
+ # There are two types of responses related to errors:
8
+ # - rd_kafka_error_t - a C object that we need to remap into an error or null when no error
9
+ # - rd_kafka_resp_err_t - response error code (numeric) that we can use directly
10
+ #
11
+ # It is critical to ensure, that we handle them correctly. The result type should be:
12
+ # - rd_kafka_error_t - :pointer
13
+ # - rd_kafka_resp_err_t - :int
5
14
  module Bindings
6
15
  extend FFI::Library
7
16
 
17
+ # Returns the library extension based on the host OS
18
+ # @return [String] 'dylib' on macOS, 'so' on other systems
8
19
  def self.lib_extension
9
- if RbConfig::CONFIG['host_os'] =~ /darwin/
10
- 'dylib'
20
+ if /darwin/.match?(RbConfig::CONFIG["host_os"])
21
+ "dylib"
11
22
  else
12
- 'so'
23
+ "so"
13
24
  end
14
25
  end
15
26
 
16
- ffi_lib File.join(__dir__, "../../ext/librdkafka.#{lib_extension}")
27
+ # Wrap ffi_lib to provide better error messages for glibc compatibility issues
28
+ begin
29
+ ffi_lib File.join(__dir__, "../../ext/librdkafka.#{lib_extension}")
30
+ rescue LoadError => e
31
+ error_message = e.message
32
+
33
+ # Check if this is a glibc version mismatch error
34
+ if /GLIBC_[\d.]+['"` ]?\s*not found/i.match?(error_message)
35
+ glibc_version = error_message[/GLIBC_([\d.]+)/, 1] || "unknown"
36
+
37
+ raise Rdkafka::LibraryLoadError, <<~ERROR_MSG.strip
38
+ Failed to load librdkafka due to glibc compatibility issue.
39
+
40
+ The precompiled librdkafka binary requires glibc version #{glibc_version} or higher,
41
+ but your system has an older version installed.
42
+
43
+ To resolve this issue, you have two options:
44
+
45
+ 1. Upgrade your system to a supported platform (recommended)
46
+
47
+ 2. Force compilation from source by reinstalling without the precompiled binary:
48
+ gem install rdkafka --platform=ruby
49
+
50
+ Or if using Bundler, add to your Gemfile:
51
+ gem 'rdkafka', force_ruby_platform: true
52
+
53
+ Original error: #{error_message}
54
+ ERROR_MSG
55
+ else
56
+ # Re-raise the original error if it's not a glibc issue
57
+ raise
58
+ end
59
+ end
17
60
 
18
61
  RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS = -175
19
62
  RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS = -174
@@ -21,13 +64,17 @@ module Rdkafka
21
64
  RD_KAFKA_RESP_ERR__NOENT = -156
22
65
  RD_KAFKA_RESP_ERR_NO_ERROR = 0
23
66
 
24
- RD_KAFKA_OFFSET_END = -1
67
+ RD_KAFKA_OFFSET_END = -1
25
68
  RD_KAFKA_OFFSET_BEGINNING = -2
26
- RD_KAFKA_OFFSET_STORED = -1000
27
- RD_KAFKA_OFFSET_INVALID = -1001
69
+ RD_KAFKA_OFFSET_STORED = -1000
70
+ RD_KAFKA_OFFSET_INVALID = -1001
71
+
72
+ RD_KAFKA_PARTITION_UA = -1
73
+ RD_KAFKA_PARTITION_UA_STR = RD_KAFKA_PARTITION_UA.to_s.freeze
28
74
 
29
75
  EMPTY_HASH = {}.freeze
30
76
 
77
+ # FFI struct for size_t pointer wrapper
31
78
  class SizePtr < FFI::Struct
32
79
  layout :value, :size_t
33
80
  end
@@ -43,26 +90,32 @@ module Rdkafka
43
90
  attach_function :rd_kafka_poll, [:pointer, :int], :int, blocking: true
44
91
  attach_function :rd_kafka_outq_len, [:pointer], :int, blocking: true
45
92
 
93
+ # Non-blocking poll variants (do not release GVL)
94
+ # These are more efficient for poll(0) calls in fiber schedulers where GVL
95
+ # release/reacquire overhead is wasteful since we don't expect to wait.
96
+ # Uses the same underlying C function but with blocking: false to skip GVL release.
97
+ attach_function :rd_kafka_poll_nb, :rd_kafka_poll, [:pointer, :int], :int, blocking: false
98
+
46
99
  # Metadata
47
100
 
48
101
  attach_function :rd_kafka_name, [:pointer], :string
49
- attach_function :rd_kafka_memberid, [:pointer], :string, blocking: true
50
- attach_function :rd_kafka_clusterid, [:pointer], :string, blocking: true
102
+ attach_function :rd_kafka_memberid, [:pointer], :pointer, blocking: true
103
+ attach_function :rd_kafka_clusterid, [:pointer, :int], :pointer, blocking: true
104
+ attach_function :rd_kafka_mem_free, [:pointer, :pointer], :void
51
105
  attach_function :rd_kafka_metadata, [:pointer, :int, :pointer, :pointer, :int], :int, blocking: true
52
106
  attach_function :rd_kafka_metadata_destroy, [:pointer], :void, blocking: true
53
107
 
54
- # Message struct
55
-
108
+ # FFI struct representing a Kafka message (rd_kafka_message_t)
56
109
  class Message < FFI::Struct
57
110
  layout :err, :int,
58
- :rkt, :pointer,
59
- :partition, :int32,
60
- :payload, :pointer,
61
- :len, :size_t,
62
- :key, :pointer,
63
- :key_len, :size_t,
64
- :offset, :int64,
65
- :_private, :pointer
111
+ :rkt, :pointer,
112
+ :partition, :int32,
113
+ :payload, :pointer,
114
+ :len, :size_t,
115
+ :key, :pointer,
116
+ :key_len, :size_t,
117
+ :offset, :int64,
118
+ :_private, :pointer
66
119
  end
67
120
 
68
121
  attach_function :rd_kafka_message_destroy, [:pointer], :void
@@ -71,40 +124,38 @@ module Rdkafka
71
124
  attach_function :rd_kafka_topic_destroy, [:pointer], :pointer
72
125
  attach_function :rd_kafka_topic_name, [:pointer], :string
73
126
 
74
- # TopicPartition ad TopicPartitionList structs
75
-
127
+ # FFI struct representing a topic partition (rd_kafka_topic_partition_t)
76
128
  class TopicPartition < FFI::Struct
77
129
  layout :topic, :string,
78
- :partition, :int32,
79
- :offset, :int64,
80
- :metadata, :pointer,
81
- :metadata_size, :size_t,
82
- :opaque, :pointer,
83
- :err, :int,
84
- :_private, :pointer
130
+ :partition, :int32,
131
+ :offset, :int64,
132
+ :metadata, :pointer,
133
+ :metadata_size, :size_t,
134
+ :opaque, :pointer,
135
+ :err, :int,
136
+ :_private, :pointer
85
137
  end
86
138
 
139
+ # FFI struct representing a topic partition list (rd_kafka_topic_partition_list_t)
87
140
  class TopicPartitionList < FFI::Struct
88
141
  layout :cnt, :int,
89
- :size, :int,
90
- :elems, :pointer
142
+ :size, :int,
143
+ :elems, :pointer
91
144
  end
92
145
 
93
146
  attach_function :rd_kafka_topic_partition_list_new, [:int32], :pointer
94
- attach_function :rd_kafka_topic_partition_list_add, [:pointer, :string, :int32], :void
147
+ attach_function :rd_kafka_topic_partition_list_add, [:pointer, :string, :int32], :pointer
95
148
  attach_function :rd_kafka_topic_partition_list_set_offset, [:pointer, :string, :int32, :int64], :void
96
149
  attach_function :rd_kafka_topic_partition_list_destroy, [:pointer], :void
97
150
  attach_function :rd_kafka_topic_partition_list_copy, [:pointer], :pointer
98
151
 
99
- # Configs management
100
- #
101
- # Structs for management of configurations
102
- # Each configuration is attached to a resource and one resource can have many configuration
103
- # details. Each resource will also have separate errors results if obtaining configuration
104
- # was not possible for any reason
152
+ # FFI struct representing a config resource (rd_kafka_ConfigResource_t)
153
+ # Structs for management of configurations. Each configuration is attached to a resource
154
+ # and one resource can have many configuration details. Each resource will also have
155
+ # separate errors results if obtaining configuration was not possible for any reason
105
156
  class ConfigResource < FFI::Struct
106
157
  layout :type, :int,
107
- :name, :string
158
+ :name, :string
108
159
  end
109
160
 
110
161
  attach_function :rd_kafka_DescribeConfigs, [:pointer, :pointer, :size_t, :pointer, :pointer], :void, blocking: true
@@ -133,20 +184,45 @@ module Rdkafka
133
184
  RD_KAFKA_ADMIN_OP_INCREMENTALALTERCONFIGS = 16
134
185
  RD_KAFKA_EVENT_INCREMENTALALTERCONFIGS_RESULT = 131072
135
186
 
136
- RD_KAFKA_ALTER_CONFIG_OP_TYPE_SET = 0
137
- RD_KAFKA_ALTER_CONFIG_OP_TYPE_DELETE = 1
138
- RD_KAFKA_ALTER_CONFIG_OP_TYPE_APPEND = 2
187
+ RD_KAFKA_ALTER_CONFIG_OP_TYPE_SET = 0
188
+ RD_KAFKA_ALTER_CONFIG_OP_TYPE_DELETE = 1
189
+ RD_KAFKA_ALTER_CONFIG_OP_TYPE_APPEND = 2
139
190
  RD_KAFKA_ALTER_CONFIG_OP_TYPE_SUBTRACT = 3
140
191
 
141
- # Errors
192
+ # List Offsets
193
+ RD_KAFKA_ADMIN_OP_LISTOFFSETS = 20
194
+ RD_KAFKA_EVENT_LISTOFFSETS_RESULT = 0x400000
195
+
196
+ # rd_kafka_IsolationLevel_t
197
+ RD_KAFKA_ISOLATION_LEVEL_READ_UNCOMMITTED = 0
198
+ RD_KAFKA_ISOLATION_LEVEL_READ_COMMITTED = 1
199
+
200
+ # rd_kafka_OffsetSpec_t
201
+ RD_KAFKA_OFFSET_SPEC_MAX_TIMESTAMP = -3
202
+ RD_KAFKA_OFFSET_SPEC_EARLIEST = -2
203
+ RD_KAFKA_OFFSET_SPEC_LATEST = -1
204
+
205
+ attach_function :rd_kafka_ListOffsets, [:pointer, :pointer, :pointer, :pointer], :void, blocking: true
206
+ attach_function :rd_kafka_event_ListOffsets_result, [:pointer], :pointer
207
+ attach_function :rd_kafka_ListOffsets_result_infos, [:pointer, :pointer], :pointer
208
+ attach_function :rd_kafka_ListOffsetsResultInfo_topic_partition, [:pointer], :pointer
209
+ attach_function :rd_kafka_ListOffsetsResultInfo_timestamp, [:pointer], :int64
210
+ attach_function :rd_kafka_AdminOptions_set_isolation_level, [:pointer, :int], :pointer
211
+ attach_function :rd_kafka_topic_partition_get_leader_epoch, [:pointer], :int32
212
+
213
+ # FFI struct for error description (rd_kafka_err_desc)
142
214
  class NativeErrorDesc < FFI::Struct
143
215
  layout :code, :int,
144
- :name, :pointer,
145
- :desc, :pointer
216
+ :name, :pointer,
217
+ :desc, :pointer
146
218
  end
147
219
 
148
220
  attach_function :rd_kafka_err2name, [:int], :string
149
221
  attach_function :rd_kafka_err2str, [:int], :string
222
+ attach_function :rd_kafka_error_is_fatal, [:pointer], :int
223
+ attach_function :rd_kafka_error_is_retriable, [:pointer], :int
224
+ attach_function :rd_kafka_error_txn_requires_abort, [:pointer], :int
225
+ attach_function :rd_kafka_error_destroy, [:pointer], :void
150
226
  attach_function :rd_kafka_get_err_descs, [:pointer, :pointer], :void
151
227
 
152
228
  # Configuration
@@ -159,6 +235,9 @@ module Rdkafka
159
235
 
160
236
  attach_function :rd_kafka_conf_new, [], :pointer
161
237
  attach_function :rd_kafka_conf_set, [:pointer, :string, :string, :pointer, :int], :kafka_config_response
238
+ attach_function :rd_kafka_conf_dump, [:pointer, :pointer], :pointer
239
+ attach_function :rd_kafka_conf_dump_free, [:pointer, :size_t], :void
240
+ attach_function :rd_kafka_conf_destroy, [:pointer], :void
162
241
  callback :log_cb, [:pointer, :int, :string, :string], :void
163
242
  attach_function :rd_kafka_conf_set_log_cb, [:pointer, :log_cb], :void
164
243
  attach_function :rd_kafka_conf_set_opaque, [:pointer, :pointer], :void
@@ -174,27 +253,37 @@ module Rdkafka
174
253
  # Log queue
175
254
  attach_function :rd_kafka_set_log_queue, [:pointer, :pointer], :void
176
255
  attach_function :rd_kafka_queue_get_main, [:pointer], :pointer
256
+ attach_function :rd_kafka_queue_get_background, [:pointer], :pointer
257
+
258
+ # Queue IO Event Support - for fiber scheduler integration
259
+ # Enables notifications to a custom FD when queue transitions from empty to non-empty
260
+ # @param queue rd_kafka_queue_t* - the queue to monitor
261
+ # @param fd int - file descriptor to write to (provide your own pipe/eventfd)
262
+ # @param payload const void* - data to write to fd
263
+ # @param size size_t - size of payload
264
+ attach_function :rd_kafka_queue_io_event_enable, [:pointer, :int, :pointer, :size_t], :void
177
265
  # Per topic configs
178
266
  attach_function :rd_kafka_topic_conf_new, [], :pointer
267
+ attach_function :rd_kafka_topic_conf_destroy, [:pointer], :void
179
268
  attach_function :rd_kafka_topic_conf_set, [:pointer, :string, :string, :pointer, :int], :kafka_config_response
180
269
 
181
270
  LogCallback = FFI::Function.new(
182
271
  :void, [:pointer, :int, :string, :string]
183
272
  ) do |_client_ptr, level, _level_string, line|
184
273
  severity = case level
185
- when 0, 1, 2
186
- Logger::FATAL
187
- when 3
188
- Logger::ERROR
189
- when 4
190
- Logger::WARN
191
- when 5, 6
192
- Logger::INFO
193
- when 7
194
- Logger::DEBUG
195
- else
196
- Logger::UNKNOWN
197
- end
274
+ when 0, 1, 2
275
+ Logger::FATAL
276
+ when 3
277
+ Logger::ERROR
278
+ when 4
279
+ Logger::WARN
280
+ when 5, 6
281
+ Logger::INFO
282
+ when 7
283
+ Logger::DEBUG
284
+ else
285
+ Logger::UNKNOWN
286
+ end
198
287
 
199
288
  Rdkafka::Config.ensure_log_thread
200
289
  Rdkafka::Config.log_queue << [severity, "rdkafka: #{line}"]
@@ -220,8 +309,8 @@ module Rdkafka
220
309
  #
221
310
  # Since this cache is shared, having few consumers and/or producers in one process will
222
311
  # automatically improve the querying times even with low refresh times.
223
- (stats['topics'] || EMPTY_HASH).each do |topic_name, details|
224
- partitions_count = details['partitions'].keys.reject { |k| k == '-1' }.size
312
+ (stats["topics"] || EMPTY_HASH).each do |topic_name, details|
313
+ partitions_count = details["partitions"].keys.count { |k| !(k == RD_KAFKA_PARTITION_UA_STR) }
225
314
 
226
315
  next unless partitions_count.positive?
227
316
 
@@ -232,14 +321,15 @@ module Rdkafka
232
321
  end
233
322
 
234
323
  # Return 0 so librdkafka frees the json string
235
- 0
324
+ RD_KAFKA_RESP_ERR_NO_ERROR
236
325
  end
237
326
 
238
327
  ErrorCallback = FFI::Function.new(
239
328
  :void, [:pointer, :int, :string, :pointer]
240
- ) do |_client_prr, err_code, reason, _opaque|
329
+ ) do |client_ptr, err_code, reason, _opaque|
241
330
  if Rdkafka::Config.error_callback
242
- error = Rdkafka::RdkafkaError.new(err_code, broker_message: reason)
331
+ instance_name = client_ptr.null? ? nil : Rdkafka::Bindings.rd_kafka_name(client_ptr)
332
+ error = Rdkafka::RdkafkaError.new(err_code, broker_message: reason, instance_name: instance_name)
243
333
  error.set_backtrace(caller)
244
334
  Rdkafka::Config.error_callback.call(error)
245
335
  end
@@ -266,7 +356,7 @@ module Rdkafka
266
356
  OAuthbearerTokenRefreshCallback = FFI::Function.new(
267
357
  :void, [:pointer, :string, :pointer]
268
358
  ) do |client_ptr, config, _opaque|
269
- if Rdkafka::Config.oauthbearer_token_refresh_callback
359
+ if Rdkafka::Config.oauthbearer_token_refresh_callback && !client_ptr.null?
270
360
  Rdkafka::Config.oauthbearer_token_refresh_callback.call(config, Rdkafka::Bindings.rd_kafka_name(client_ptr))
271
361
  end
272
362
  end
@@ -296,7 +386,13 @@ module Rdkafka
296
386
  attach_function :rd_kafka_commit, [:pointer, :pointer, :bool], :int, blocking: true
297
387
  attach_function :rd_kafka_poll_set_consumer, [:pointer], :void, blocking: true
298
388
  attach_function :rd_kafka_consumer_poll, [:pointer, :int], :pointer, blocking: true
389
+ # Non-blocking consumer poll variant (does not release GVL)
390
+ # More efficient for poll(0) calls in fiber schedulers.
391
+ attach_function :rd_kafka_consumer_poll_nb, :rd_kafka_consumer_poll, [:pointer, :int], :pointer, blocking: false
299
392
  attach_function :rd_kafka_consumer_close, [:pointer], :void, blocking: true
393
+ attach_function :rd_kafka_queue_get_consumer, [:pointer], :pointer
394
+ attach_function :rd_kafka_consume_batch_queue, [:pointer, :int, :pointer, :size_t], :ssize_t, blocking: true
395
+ attach_function :rd_kafka_consume_batch_queue_nb, :rd_kafka_consume_batch_queue, [:pointer, :int, :pointer, :size_t], :ssize_t, blocking: false
300
396
  attach_function :rd_kafka_offsets_store, [:pointer, :pointer], :int, blocking: true
301
397
  attach_function :rd_kafka_pause_partitions, [:pointer, :pointer], :int, blocking: true
302
398
  attach_function :rd_kafka_resume_partitions, [:pointer, :pointer], :int, blocking: true
@@ -352,10 +448,9 @@ module Rdkafka
352
448
 
353
449
  # Stats
354
450
 
355
- attach_function :rd_kafka_query_watermark_offsets, [:pointer, :string, :int, :pointer, :pointer, :int], :int
451
+ attach_function :rd_kafka_query_watermark_offsets, [:pointer, :string, :int, :pointer, :pointer, :int], :int, blocking: true
356
452
 
357
453
  # Producer
358
-
359
454
  RD_KAFKA_VTYPE_END = 0
360
455
  RD_KAFKA_VTYPE_TOPIC = 1
361
456
  RD_KAFKA_VTYPE_RKT = 2
@@ -377,16 +472,25 @@ module Rdkafka
377
472
  callback :delivery_cb, [:pointer, :pointer, :pointer], :void
378
473
  attach_function :rd_kafka_conf_set_dr_msg_cb, [:pointer, :delivery_cb], :void
379
474
 
380
- # Partitioner
381
- PARTITIONERS = %w(random consistent consistent_random murmur2 murmur2_random fnv1a fnv1a_random).each_with_object({}) do |name, hsh|
382
- method_name = "rd_kafka_msg_partitioner_#{name}".to_sym
475
+ # Hash mapping partitioner names to their FFI function symbols
476
+ # @return [Hash{String => Symbol}]
477
+ PARTITIONERS = %w[random consistent consistent_random murmur2 murmur2_random fnv1a fnv1a_random].each_with_object({}) do |name, hsh|
478
+ method_name = :"rd_kafka_msg_partitioner_#{name}"
383
479
  attach_function method_name, [:pointer, :pointer, :size_t, :int32, :pointer, :pointer], :int32
384
480
  hsh[name] = method_name
385
481
  end
386
482
 
483
+ # Calculates the partition for a message based on the partitioner
484
+ #
485
+ # @param topic_ptr [FFI::Pointer] pointer to the topic handle
486
+ # @param str [String] the partition key string
487
+ # @param partition_count [Integer, nil] number of partitions
488
+ # @param partitioner [String] name of the partitioner to use
489
+ # @return [Integer] partition number or RD_KAFKA_PARTITION_UA if unassigned
490
+ # @raise [Rdkafka::Config::ConfigError] when an unknown partitioner is specified
387
491
  def self.partitioner(topic_ptr, str, partition_count, partitioner = "consistent_random")
388
492
  # Return RD_KAFKA_PARTITION_UA(unassigned partition) when partition count is nil/zero.
389
- return -1 unless partition_count&.nonzero?
493
+ return RD_KAFKA_PARTITION_UA unless partition_count&.nonzero?
390
494
 
391
495
  str_ptr = str.empty? ? FFI::MemoryPointer::NULL : FFI::MemoryPointer.from_string(str)
392
496
  method_name = PARTITIONERS.fetch(partitioner) do
@@ -397,8 +501,7 @@ module Rdkafka
397
501
  end
398
502
 
399
503
  # Create Topics
400
-
401
- RD_KAFKA_ADMIN_OP_CREATETOPICS = 1 # rd_kafka_admin_op_t
504
+ RD_KAFKA_ADMIN_OP_CREATETOPICS = 1 # rd_kafka_admin_op_t
402
505
  RD_KAFKA_EVENT_CREATETOPICS_RESULT = 100 # rd_kafka_event_type_t
403
506
 
404
507
  attach_function :rd_kafka_CreateTopics, [:pointer, :pointer, :size_t, :pointer, :pointer], :void, blocking: true
@@ -409,8 +512,7 @@ module Rdkafka
409
512
  attach_function :rd_kafka_CreateTopics_result_topics, [:pointer, :pointer], :pointer, blocking: true
410
513
 
411
514
  # Delete Topics
412
-
413
- RD_KAFKA_ADMIN_OP_DELETETOPICS = 2 # rd_kafka_admin_op_t
515
+ RD_KAFKA_ADMIN_OP_DELETETOPICS = 2 # rd_kafka_admin_op_t
414
516
  RD_KAFKA_EVENT_DELETETOPICS_RESULT = 101 # rd_kafka_event_type_t
415
517
 
416
518
  attach_function :rd_kafka_DeleteTopics, [:pointer, :pointer, :size_t, :pointer, :pointer], :int32, blocking: true
@@ -430,7 +532,6 @@ module Rdkafka
430
532
  attach_function :rd_kafka_CreatePartitions_result_topics, [:pointer, :pointer], :pointer
431
533
 
432
534
  # Delete Group
433
-
434
535
  RD_KAFKA_ADMIN_OP_DELETEGROUPS = 7 # rd_kafka_admin_op_t
435
536
  RD_KAFKA_EVENT_DELETEGROUPS_RESULT = 106 # rd_kafka_event_type_t
436
537
 
@@ -442,7 +543,6 @@ module Rdkafka
442
543
 
443
544
  # Background Queue and Callback
444
545
 
445
- attach_function :rd_kafka_queue_get_background, [:pointer], :pointer
446
546
  attach_function :rd_kafka_conf_set_background_event_cb, [:pointer, :pointer], :void
447
547
  attach_function :rd_kafka_queue_destroy, [:pointer], :void
448
548
 
@@ -456,6 +556,7 @@ module Rdkafka
456
556
 
457
557
  attach_function :rd_kafka_event_type, [:pointer], :int32
458
558
  attach_function :rd_kafka_event_opaque, [:pointer], :pointer
559
+ attach_function :rd_kafka_event_destroy, [:pointer], :void
459
560
 
460
561
  # Extracting data from topic results
461
562
 
@@ -464,8 +565,7 @@ module Rdkafka
464
565
  attach_function :rd_kafka_topic_result_name, [:pointer], :pointer
465
566
 
466
567
  # Create Acls
467
-
468
- RD_KAFKA_ADMIN_OP_CREATEACLS = 9
568
+ RD_KAFKA_ADMIN_OP_CREATEACLS = 9
469
569
  RD_KAFKA_EVENT_CREATEACLS_RESULT = 1024
470
570
 
471
571
  attach_function :rd_kafka_CreateAcls, [:pointer, :pointer, :size_t, :pointer, :pointer], :void
@@ -473,8 +573,7 @@ module Rdkafka
473
573
  attach_function :rd_kafka_CreateAcls_result_acls, [:pointer, :pointer], :pointer
474
574
 
475
575
  # Delete Acls
476
-
477
- RD_KAFKA_ADMIN_OP_DELETEACLS = 11
576
+ RD_KAFKA_ADMIN_OP_DELETEACLS = 11
478
577
  RD_KAFKA_EVENT_DELETEACLS_RESULT = 4096
479
578
 
480
579
  attach_function :rd_kafka_DeleteAcls, [:pointer, :pointer, :size_t, :pointer, :pointer], :void
@@ -484,8 +583,7 @@ module Rdkafka
484
583
  attach_function :rd_kafka_DeleteAcls_result_response_matching_acls, [:pointer, :pointer], :pointer
485
584
 
486
585
  # Describe Acls
487
-
488
- RD_KAFKA_ADMIN_OP_DESCRIBEACLS = 10
586
+ RD_KAFKA_ADMIN_OP_DESCRIBEACLS = 10
489
587
  RD_KAFKA_EVENT_DESCRIBEACLS_RESULT = 2048
490
588
 
491
589
  attach_function :rd_kafka_DescribeAcls, [:pointer, :pointer, :pointer, :pointer], :void
@@ -501,45 +599,42 @@ module Rdkafka
501
599
  attach_function :rd_kafka_AclBinding_host, [:pointer], :pointer
502
600
  attach_function :rd_kafka_AclBinding_operation, [:pointer], :int32
503
601
  attach_function :rd_kafka_AclBinding_permission_type, [:pointer], :int32
504
- attach_function :rd_kafka_AclBinding_new, [:int32, :pointer, :int32, :pointer, :pointer, :int32, :int32, :pointer, :size_t ], :pointer
505
- attach_function :rd_kafka_AclBindingFilter_new, [:int32, :pointer, :int32, :pointer, :pointer, :int32, :int32, :pointer, :size_t ], :pointer
602
+ attach_function :rd_kafka_AclBinding_new, [:int32, :pointer, :int32, :pointer, :pointer, :int32, :int32, :pointer, :size_t], :pointer
603
+ attach_function :rd_kafka_AclBindingFilter_new, [:int32, :pointer, :int32, :pointer, :pointer, :int32, :int32, :pointer, :size_t], :pointer
506
604
  attach_function :rd_kafka_AclBinding_destroy, [:pointer], :void
507
605
 
508
606
  # rd_kafka_ResourceType_t - https://github.com/confluentinc/librdkafka/blob/292d2a66b9921b783f08147807992e603c7af059/src/rdkafka.h#L7307
509
-
510
- RD_KAFKA_RESOURCE_ANY = 1
607
+ RD_KAFKA_RESOURCE_ANY = 1
511
608
  RD_KAFKA_RESOURCE_TOPIC = 2
512
609
  RD_KAFKA_RESOURCE_GROUP = 3
513
610
  RD_KAFKA_RESOURCE_BROKER = 4
514
611
  RD_KAFKA_RESOURCE_TRANSACTIONAL_ID = 5
515
612
 
516
613
  # rd_kafka_ResourcePatternType_t - https://github.com/confluentinc/librdkafka/blob/292d2a66b9921b783f08147807992e603c7af059/src/rdkafka.h#L7320
517
-
518
- RD_KAFKA_RESOURCE_PATTERN_ANY = 1
519
- RD_KAFKA_RESOURCE_PATTERN_MATCH = 2
520
- RD_KAFKA_RESOURCE_PATTERN_LITERAL = 3
614
+ RD_KAFKA_RESOURCE_PATTERN_UNKNOWN = 0
615
+ RD_KAFKA_RESOURCE_PATTERN_ANY = 1
616
+ RD_KAFKA_RESOURCE_PATTERN_MATCH = 2
617
+ RD_KAFKA_RESOURCE_PATTERN_LITERAL = 3
521
618
  RD_KAFKA_RESOURCE_PATTERN_PREFIXED = 4
522
619
 
523
620
  # rd_kafka_AclOperation_t - https://github.com/confluentinc/librdkafka/blob/292d2a66b9921b783f08147807992e603c7af059/src/rdkafka.h#L8403
524
-
525
- RD_KAFKA_ACL_OPERATION_ANY = 1
526
- RD_KAFKA_ACL_OPERATION_ALL = 2
527
- RD_KAFKA_ACL_OPERATION_READ = 3
528
- RD_KAFKA_ACL_OPERATION_WRITE = 4
529
- RD_KAFKA_ACL_OPERATION_CREATE = 5
530
- RD_KAFKA_ACL_OPERATION_DELETE = 6
531
- RD_KAFKA_ACL_OPERATION_ALTER = 7
532
- RD_KAFKA_ACL_OPERATION_DESCRIBE = 8
533
- RD_KAFKA_ACL_OPERATION_CLUSTER_ACTION = 9
621
+ RD_KAFKA_ACL_OPERATION_ANY = 1
622
+ RD_KAFKA_ACL_OPERATION_ALL = 2
623
+ RD_KAFKA_ACL_OPERATION_READ = 3
624
+ RD_KAFKA_ACL_OPERATION_WRITE = 4
625
+ RD_KAFKA_ACL_OPERATION_CREATE = 5
626
+ RD_KAFKA_ACL_OPERATION_DELETE = 6
627
+ RD_KAFKA_ACL_OPERATION_ALTER = 7
628
+ RD_KAFKA_ACL_OPERATION_DESCRIBE = 8
629
+ RD_KAFKA_ACL_OPERATION_CLUSTER_ACTION = 9
534
630
  RD_KAFKA_ACL_OPERATION_DESCRIBE_CONFIGS = 10
535
- RD_KAFKA_ACL_OPERATION_ALTER_CONFIGS = 11
631
+ RD_KAFKA_ACL_OPERATION_ALTER_CONFIGS = 11
536
632
  RD_KAFKA_ACL_OPERATION_IDEMPOTENT_WRITE = 12
537
633
 
538
634
  # rd_kafka_AclPermissionType_t - https://github.com/confluentinc/librdkafka/blob/292d2a66b9921b783f08147807992e603c7af059/src/rdkafka.h#L8435
539
-
540
- RD_KAFKA_ACL_PERMISSION_TYPE_ANY = 1
541
- RD_KAFKA_ACL_PERMISSION_TYPE_DENY = 2
542
- RD_KAFKA_ACL_PERMISSION_TYPE_ALLOW = 3
635
+ RD_KAFKA_ACL_PERMISSION_TYPE_ANY = 1
636
+ RD_KAFKA_ACL_PERMISSION_TYPE_DENY = 2
637
+ RD_KAFKA_ACL_PERMISSION_TYPE_ALLOW = 3
543
638
 
544
639
  # Extracting error details from Acl results
545
640
  attach_function :rd_kafka_acl_result_error, [:pointer], :pointer
@@ -549,14 +644,13 @@ module Rdkafka
549
644
  attach_function :rd_kafka_event_error_string, [:pointer], :pointer
550
645
  attach_function :rd_kafka_AclBinding_error, [:pointer], :pointer
551
646
 
552
-
553
- # Extracting data from group results
554
- class NativeError < FFI::Struct # rd_kafka_error_t
647
+ # FFI struct for native error (rd_kafka_error_t)
648
+ class NativeError < FFI::Struct
555
649
  layout :code, :int32,
556
- :errstr, :pointer,
557
- :fatal, :u_int8_t,
558
- :retriable, :u_int8_t,
559
- :txn_requires_abort, :u_int8_t
650
+ :errstr, :pointer,
651
+ :fatal, :u_int8_t,
652
+ :retriable, :u_int8_t,
653
+ :txn_requires_abort, :u_int8_t
560
654
  end
561
655
 
562
656
  attach_function :rd_kafka_group_result_error, [:pointer], NativeError.by_ref # rd_kafka_group_result_t* => rd_kafka_error_t*
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Callbacks
5
+ # Base class for admin background-event result handlers.
6
+ #
7
+ # Each subclass handles one librdkafka admin result event type: it extracts the result from
8
+ # the event, looks up the matching {AbstractHandle} in the registry and resolves it (sets the
9
+ # response, builds the report, copies the broker message, then unlocks it).
10
+ #
11
+ # The event is owned by the application and destroyed by {BackgroundEventCallback} right after
12
+ # dispatch, so handlers must copy everything they need out of event-owned memory into Ruby
13
+ # objects before returning.
14
+ #
15
+ # @private
16
+ class BaseHandler
17
+ class << self
18
+ # Processes a background event for the operation this handler is responsible for.
19
+ # @param event_ptr [FFI::Pointer] pointer to the event
20
+ # @return [void]
21
+ def call(event_ptr)
22
+ raise NotImplementedError, "#{name} must implement .call"
23
+ end
24
+
25
+ private
26
+
27
+ # Copies an event-owned C string into a Ruby string
28
+ # @param ptr [FFI::Pointer] pointer to the C string
29
+ # @return [String, nil] copied string or nil when NULL
30
+ def read_event_string(ptr)
31
+ ptr.null? ? nil : ptr.read_string
32
+ end
33
+
34
+ # Resolves a handle directly from an operation-level event error and unlocks it.
35
+ #
36
+ # librdkafka signals a failure of the whole admin operation (e.g. `_TIMED_OUT` when the
37
+ # brokers are unreachable, or `_DESTROY` when the client is closed with the request in
38
+ # flight) by setting the event error and delivering an *empty* results array. The
39
+ # per-element result handlers must not index into that empty array; they call this first
40
+ # and skip results parsing when it returns true. Without this, indexing `results[0]`
41
+ # raises inside the FFI callback, the handle is never unlocked, and the caller's `wait`
42
+ # blocks until its own timeout and raises `WaitTimeoutError`, losing the real error code.
43
+ #
44
+ # @param event_ptr [FFI::Pointer] pointer to the event
45
+ # @param handle [Rdkafka::AbstractHandle] the handle to resolve
46
+ # @return [Boolean] true when the event carried an operation-level error and the handle
47
+ # was resolved here
48
+ def resolve_operation_error(event_ptr, handle)
49
+ error_code = Rdkafka::Bindings.rd_kafka_event_error(event_ptr)
50
+
51
+ return false if error_code == Rdkafka::Bindings::RD_KAFKA_RESP_ERR_NO_ERROR
52
+
53
+ handle[:response] = error_code
54
+ handle.broker_message = read_event_string(Rdkafka::Bindings.rd_kafka_event_error_string(event_ptr))
55
+ handle.unlock
56
+
57
+ true
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ module Callbacks
5
+ # Handles `RD_KAFKA_EVENT_CREATEACLS_RESULT` events
6
+ # @private
7
+ class CreateAclHandler < BaseHandler
8
+ class << self
9
+ # Resolves the create-ACL handle from its result event
10
+ # @param event_ptr [FFI::Pointer] pointer to the event
11
+ # @return [void]
12
+ def call(event_ptr)
13
+ create_acls_result = Rdkafka::Bindings.rd_kafka_event_CreateAcls_result(event_ptr)
14
+
15
+ # Get the number of acl results
16
+ pointer_to_size_t = FFI::MemoryPointer.new(:size_t)
17
+ create_acl_result_array = Rdkafka::Bindings.rd_kafka_CreateAcls_result_acls(create_acls_result, pointer_to_size_t)
18
+ create_acl_results = CreateAclResult.create_acl_results_from_array(pointer_to_size_t.read_int, create_acl_result_array)
19
+ create_acl_handle_ptr = Rdkafka::Bindings.rd_kafka_event_opaque(event_ptr)
20
+
21
+ if create_acl_handle = Rdkafka::Admin::CreateAclHandle.remove(create_acl_handle_ptr.address)
22
+ unless resolve_operation_error(event_ptr, create_acl_handle)
23
+ create_acl_handle[:response] = create_acl_results[0].result_error
24
+ create_acl_handle.result = Rdkafka::Admin::CreateAclReport.new(
25
+ rdkafka_response: create_acl_results[0].result_error,
26
+ rdkafka_response_string: create_acl_results[0].error_string
27
+ )
28
+ create_acl_handle.broker_message = create_acl_handle.result.rdkafka_response_string
29
+
30
+ create_acl_handle.unlock
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end