rdkafka 0.13.0.beta.3 → 0.13.0.beta.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9db414f84847b884bded4eb7643ead52ee664256acdde47dcf7bb5e8421b5882
4
- data.tar.gz: d696bce3413d5a591542e6bb1b7191acabd5b331e4115fb7a78b6d65320874d4
3
+ metadata.gz: 5b5ba8c133537fc31e3fb5f00d230816b92d8c8f63c8d94b39f80d5d7fb22918
4
+ data.tar.gz: df7679de1c6724585ac5c0e636b6a239ae9d6e859496a855b2596c45e07099ca
5
5
  SHA512:
6
- metadata.gz: cd209306c840710661108a357adbeec6a24cc1aa1a828959041a0810c25b19ddbb9b52442a69c389995361c96c075a9a572b532cc6ba97c70160d99db46f5d8d
7
- data.tar.gz: 5cb50b3717ab7904d4d8afb46233f0435cf519511755ee5137ccdf638775947f23db3ff40ac1efcd0562a767e23f5a65224e8d59f3a753b33206253f9db4dfc6
6
+ metadata.gz: 6d1b685394205c9beaf388e40165c028dda9ef3a48ed430e9ccb24e320facadf462215b36e82a62f82cfaf61b1d6040936c352db9bb21371cb4f855d977f9c70
7
+ data.tar.gz: 65822e65cf3ac6297fec721756cba360f68d3533e69d76ec7208d563d39f0c659a5ac34ef21f633ad0053b9db0cc1cba887d9b4ad75b1083f2ceaf564258c722
@@ -20,8 +20,8 @@ blocks:
20
20
  commands:
21
21
  - sem-version ruby $RUBY_VERSION
22
22
  - checkout
23
+ - docker-compose up -d --no-recreate
23
24
  - bundle install --path vendor/bundle
24
25
  - cd ext && bundle exec rake && cd ..
25
- - docker-compose up -d --no-recreate
26
26
  - ulimit -c unlimited
27
27
  - valgrind -v bundle exec rspec
data/CHANGELOG.md CHANGED
@@ -7,6 +7,10 @@
7
7
  * Fix documented type for DeliveryReport#error (jimmydo)
8
8
  * Bump librdkafka to 1.9.2 (thijsc)
9
9
  * Use finalizers to cleanly exit producer and admin (thijsc)
10
+ * Lock access to the native kafka client (thijsc)
11
+ - Fix potential race condition in multi-threaded producer (mensfeld)
12
+ - Fix leaking FFI resources in specs (mensfeld)
13
+ - Improve specs stability (mensfeld)
10
14
 
11
15
  # 0.12.0
12
16
  * Bumps librdkafka to 1.9.0
data/README.md CHANGED
@@ -23,6 +23,19 @@ The most important pieces of a Kafka client are implemented. We're
23
23
  working towards feature completeness, you can track that here:
24
24
  https://github.com/appsignal/rdkafka-ruby/milestone/1
25
25
 
26
+ ## Table of content
27
+
28
+ - [Installation](#installation)
29
+ - [Usage](#usage)
30
+ * [Consuming messages](#consuming-messages)
31
+ * [Producing messages](#producing-messages)
32
+ - [Higher level libraries](#higher-level-libraries)
33
+ * [Message processing frameworks](#message-processing-frameworks)
34
+ * [Message publishing libraries](#message-publishing-libraries)
35
+ - [Development](#development)
36
+ - [Example](#example)
37
+
38
+
26
39
  ## Installation
27
40
 
28
41
  This gem downloads and compiles librdkafka when it is installed. If you
@@ -77,6 +90,19 @@ Note that creating a producer consumes some resources that will not be
77
90
  released until it `#close` is explicitly called, so be sure to call
78
91
  `Config#producer` only as necessary.
79
92
 
93
+ ## Higher level libraries
94
+
95
+ Currently, there are two actively developed frameworks based on rdkafka-ruby, that provide higher level API that can be used to work with Kafka messages and one library for publishing messages.
96
+
97
+ ### Message processing frameworks
98
+
99
+ * [Karafka](https://github.com/karafka/karafka) - Ruby and Rails efficient Kafka processing framework.
100
+ * [Racecar](https://github.com/zendesk/racecar) - A simple framework for Kafka consumers in Ruby
101
+
102
+ ### Message publishing libraries
103
+
104
+ * [WaterDrop](https://github.com/karafka/waterdrop) – Standalone Karafka library for producing Kafka messages.
105
+
80
106
  ## Development
81
107
 
82
108
  A Docker Compose file is included to run Kafka and Zookeeper. To run
data/lib/rdkafka/admin.rb CHANGED
@@ -67,7 +67,9 @@ module Rdkafka
67
67
  topics_array_ptr.write_array_of_pointer(pointer_array)
68
68
 
69
69
  # Get a pointer to the queue that our request will be enqueued on
70
- queue_ptr = Rdkafka::Bindings.rd_kafka_queue_get_background(@native_kafka.inner)
70
+ queue_ptr = @native_kafka.with_inner do |inner|
71
+ Rdkafka::Bindings.rd_kafka_queue_get_background(inner)
72
+ end
71
73
  if queue_ptr.null?
72
74
  Rdkafka::Bindings.rd_kafka_NewTopic_destroy(new_topic_ptr)
73
75
  raise Rdkafka::Config::ConfigError.new("rd_kafka_queue_get_background was NULL")
@@ -78,17 +80,21 @@ module Rdkafka
78
80
  create_topic_handle[:pending] = true
79
81
  create_topic_handle[:response] = -1
80
82
  CreateTopicHandle.register(create_topic_handle)
81
- admin_options_ptr = Rdkafka::Bindings.rd_kafka_AdminOptions_new(@native_kafka.inner, Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_CREATETOPICS)
83
+ admin_options_ptr = @native_kafka.with_inner do |inner|
84
+ Rdkafka::Bindings.rd_kafka_AdminOptions_new(inner, Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_CREATETOPICS)
85
+ end
82
86
  Rdkafka::Bindings.rd_kafka_AdminOptions_set_opaque(admin_options_ptr, create_topic_handle.to_ptr)
83
87
 
84
88
  begin
85
- Rdkafka::Bindings.rd_kafka_CreateTopics(
86
- @native_kafka.inner,
87
- topics_array_ptr,
88
- 1,
89
- admin_options_ptr,
90
- queue_ptr
91
- )
89
+ @native_kafka.with_inner do |inner|
90
+ Rdkafka::Bindings.rd_kafka_CreateTopics(
91
+ inner,
92
+ topics_array_ptr,
93
+ 1,
94
+ admin_options_ptr,
95
+ queue_ptr
96
+ )
97
+ end
92
98
  rescue Exception
93
99
  CreateTopicHandle.remove(create_topic_handle.to_ptr.address)
94
100
  raise
@@ -118,7 +124,9 @@ module Rdkafka
118
124
  topics_array_ptr.write_array_of_pointer(pointer_array)
119
125
 
120
126
  # Get a pointer to the queue that our request will be enqueued on
121
- queue_ptr = Rdkafka::Bindings.rd_kafka_queue_get_background(@native_kafka.inner)
127
+ queue_ptr = @native_kafka.with_inner do |inner|
128
+ Rdkafka::Bindings.rd_kafka_queue_get_background(inner)
129
+ end
122
130
  if queue_ptr.null?
123
131
  Rdkafka::Bindings.rd_kafka_DeleteTopic_destroy(delete_topic_ptr)
124
132
  raise Rdkafka::Config::ConfigError.new("rd_kafka_queue_get_background was NULL")
@@ -129,17 +137,21 @@ module Rdkafka
129
137
  delete_topic_handle[:pending] = true
130
138
  delete_topic_handle[:response] = -1
131
139
  DeleteTopicHandle.register(delete_topic_handle)
132
- admin_options_ptr = Rdkafka::Bindings.rd_kafka_AdminOptions_new(@native_kafka.inner, Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_DELETETOPICS)
140
+ admin_options_ptr = @native_kafka.with_inner do |inner|
141
+ Rdkafka::Bindings.rd_kafka_AdminOptions_new(inner, Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_DELETETOPICS)
142
+ end
133
143
  Rdkafka::Bindings.rd_kafka_AdminOptions_set_opaque(admin_options_ptr, delete_topic_handle.to_ptr)
134
144
 
135
145
  begin
136
- Rdkafka::Bindings.rd_kafka_DeleteTopics(
137
- @native_kafka.inner,
138
- topics_array_ptr,
139
- 1,
140
- admin_options_ptr,
141
- queue_ptr
142
- )
146
+ @native_kafka.with_inner do |inner|
147
+ Rdkafka::Bindings.rd_kafka_DeleteTopics(
148
+ inner,
149
+ topics_array_ptr,
150
+ 1,
151
+ admin_options_ptr,
152
+ queue_ptr
153
+ )
154
+ end
143
155
  rescue Exception
144
156
  DeleteTopicHandle.remove(delete_topic_handle.to_ptr.address)
145
157
  raise
@@ -41,10 +41,10 @@ module Rdkafka
41
41
 
42
42
  # Metadata
43
43
 
44
- attach_function :rd_kafka_memberid, [:pointer], :string
45
- attach_function :rd_kafka_clusterid, [:pointer], :string
46
- attach_function :rd_kafka_metadata, [:pointer, :int, :pointer, :pointer, :int], :int
47
- attach_function :rd_kafka_metadata_destroy, [:pointer], :void
44
+ attach_function :rd_kafka_memberid, [:pointer], :string, blocking: true
45
+ attach_function :rd_kafka_clusterid, [:pointer], :string, blocking: true
46
+ attach_function :rd_kafka_metadata, [:pointer, :int, :pointer, :pointer, :int], :int, blocking: true
47
+ attach_function :rd_kafka_metadata_destroy, [:pointer], :void, blocking: true
48
48
 
49
49
  # Message struct
50
50
 
@@ -170,27 +170,26 @@ module Rdkafka
170
170
 
171
171
  attach_function :rd_kafka_new, [:kafka_type, :pointer, :pointer, :int], :pointer
172
172
 
173
- RD_KAFKA_DESTROY_F_IMMEDIATE = 0x4
174
- attach_function :rd_kafka_destroy_flags, [:pointer, :int], :void
173
+ attach_function :rd_kafka_destroy, [:pointer], :void
175
174
 
176
175
  # Consumer
177
176
 
178
- attach_function :rd_kafka_subscribe, [:pointer, :pointer], :int
179
- attach_function :rd_kafka_unsubscribe, [:pointer], :int
180
- attach_function :rd_kafka_subscription, [:pointer, :pointer], :int
181
- attach_function :rd_kafka_assign, [:pointer, :pointer], :int
182
- attach_function :rd_kafka_incremental_assign, [:pointer, :pointer], :int
183
- attach_function :rd_kafka_incremental_unassign, [:pointer, :pointer], :int
184
- attach_function :rd_kafka_assignment, [:pointer, :pointer], :int
185
- attach_function :rd_kafka_committed, [:pointer, :pointer, :int], :int
177
+ attach_function :rd_kafka_subscribe, [:pointer, :pointer], :int, blocking: true
178
+ attach_function :rd_kafka_unsubscribe, [:pointer], :int, blocking: true
179
+ attach_function :rd_kafka_subscription, [:pointer, :pointer], :int, blocking: true
180
+ attach_function :rd_kafka_assign, [:pointer, :pointer], :int, blocking: true
181
+ attach_function :rd_kafka_incremental_assign, [:pointer, :pointer], :int, blocking: true
182
+ attach_function :rd_kafka_incremental_unassign, [:pointer, :pointer], :int, blocking: true
183
+ attach_function :rd_kafka_assignment, [:pointer, :pointer], :int, blocking: true
184
+ attach_function :rd_kafka_committed, [:pointer, :pointer, :int], :int, blocking: true
186
185
  attach_function :rd_kafka_commit, [:pointer, :pointer, :bool], :int, blocking: true
187
- attach_function :rd_kafka_poll_set_consumer, [:pointer], :void
186
+ attach_function :rd_kafka_poll_set_consumer, [:pointer], :void, blocking: true
188
187
  attach_function :rd_kafka_consumer_poll, [:pointer, :int], :pointer, blocking: true
189
188
  attach_function :rd_kafka_consumer_close, [:pointer], :void, blocking: true
190
- attach_function :rd_kafka_offset_store, [:pointer, :int32, :int64], :int
191
- attach_function :rd_kafka_pause_partitions, [:pointer, :pointer], :int
192
- attach_function :rd_kafka_resume_partitions, [:pointer, :pointer], :int
193
- attach_function :rd_kafka_seek, [:pointer, :int32, :int64, :int], :int
189
+ attach_function :rd_kafka_offset_store, [:pointer, :int32, :int64], :int, blocking: true
190
+ attach_function :rd_kafka_pause_partitions, [:pointer, :pointer], :int, blocking: true
191
+ attach_function :rd_kafka_resume_partitions, [:pointer, :pointer], :int, blocking: true
192
+ attach_function :rd_kafka_seek, [:pointer, :int32, :int64, :int], :int, blocking: true
194
193
 
195
194
  # Headers
196
195
  attach_function :rd_kafka_header_get_all, [:pointer, :size_t, :pointer, :pointer, SizePtr], :int
@@ -199,7 +198,7 @@ module Rdkafka
199
198
  # Rebalance
200
199
 
201
200
  callback :rebalance_cb_function, [:pointer, :int, :pointer, :pointer], :void
202
- attach_function :rd_kafka_conf_set_rebalance_cb, [:pointer, :rebalance_cb_function], :void
201
+ attach_function :rd_kafka_conf_set_rebalance_cb, [:pointer, :rebalance_cb_function], :void, blocking: true
203
202
 
204
203
  RebalanceCallback = FFI::Function.new(
205
204
  :void, [:pointer, :int, :pointer, :pointer]
@@ -257,7 +256,7 @@ module Rdkafka
257
256
 
258
257
  RD_KAFKA_MSG_F_COPY = 0x2
259
258
 
260
- attach_function :rd_kafka_producev, [:pointer, :varargs], :int
259
+ attach_function :rd_kafka_producev, [:pointer, :varargs], :int, blocking: true
261
260
  callback :delivery_cb, [:pointer, :pointer, :pointer], :void
262
261
  attach_function :rd_kafka_conf_set_dr_msg_cb, [:pointer, :delivery_cb], :void
263
262
 
@@ -284,23 +283,23 @@ module Rdkafka
284
283
  RD_KAFKA_ADMIN_OP_CREATETOPICS = 1 # rd_kafka_admin_op_t
285
284
  RD_KAFKA_EVENT_CREATETOPICS_RESULT = 100 # rd_kafka_event_type_t
286
285
 
287
- attach_function :rd_kafka_CreateTopics, [:pointer, :pointer, :size_t, :pointer, :pointer], :void
288
- attach_function :rd_kafka_NewTopic_new, [:pointer, :size_t, :size_t, :pointer, :size_t], :pointer
289
- attach_function :rd_kafka_NewTopic_set_config, [:pointer, :string, :string], :int32
290
- attach_function :rd_kafka_NewTopic_destroy, [:pointer], :void
291
- attach_function :rd_kafka_event_CreateTopics_result, [:pointer], :pointer
292
- attach_function :rd_kafka_CreateTopics_result_topics, [:pointer, :pointer], :pointer
286
+ attach_function :rd_kafka_CreateTopics, [:pointer, :pointer, :size_t, :pointer, :pointer], :void, blocking: true
287
+ attach_function :rd_kafka_NewTopic_new, [:pointer, :size_t, :size_t, :pointer, :size_t], :pointer, blocking: true
288
+ attach_function :rd_kafka_NewTopic_set_config, [:pointer, :string, :string], :int32, blocking: true
289
+ attach_function :rd_kafka_NewTopic_destroy, [:pointer], :void, blocking: true
290
+ attach_function :rd_kafka_event_CreateTopics_result, [:pointer], :pointer, blocking: true
291
+ attach_function :rd_kafka_CreateTopics_result_topics, [:pointer, :pointer], :pointer, blocking: true
293
292
 
294
293
  # Delete Topics
295
294
 
296
295
  RD_KAFKA_ADMIN_OP_DELETETOPICS = 2 # rd_kafka_admin_op_t
297
296
  RD_KAFKA_EVENT_DELETETOPICS_RESULT = 101 # rd_kafka_event_type_t
298
297
 
299
- attach_function :rd_kafka_DeleteTopics, [:pointer, :pointer, :size_t, :pointer, :pointer], :int32
300
- attach_function :rd_kafka_DeleteTopic_new, [:pointer], :pointer
301
- attach_function :rd_kafka_DeleteTopic_destroy, [:pointer], :void
302
- attach_function :rd_kafka_event_DeleteTopics_result, [:pointer], :pointer
303
- attach_function :rd_kafka_DeleteTopics_result_topics, [:pointer, :pointer], :pointer
298
+ attach_function :rd_kafka_DeleteTopics, [:pointer, :pointer, :size_t, :pointer, :pointer], :int32, blocking: true
299
+ attach_function :rd_kafka_DeleteTopic_new, [:pointer], :pointer, blocking: true
300
+ attach_function :rd_kafka_DeleteTopic_destroy, [:pointer], :void, blocking: true
301
+ attach_function :rd_kafka_event_DeleteTopics_result, [:pointer], :pointer, blocking: true
302
+ attach_function :rd_kafka_DeleteTopics_result_topics, [:pointer, :pointer], :pointer, blocking: true
304
303
 
305
304
  # Background Queue and Callback
306
305
 
@@ -27,7 +27,9 @@ module Rdkafka
27
27
  def close
28
28
  return if closed?
29
29
  ObjectSpace.undefine_finalizer(self)
30
- Rdkafka::Bindings.rd_kafka_consumer_close(@native_kafka.inner)
30
+ @native_kafka.with_inner do |inner|
31
+ Rdkafka::Bindings.rd_kafka_consumer_close(inner)
32
+ end
31
33
  @native_kafka.close
32
34
  end
33
35
 
@@ -54,7 +56,9 @@ module Rdkafka
54
56
  end
55
57
 
56
58
  # Subscribe to topic partition list and check this was successful
57
- response = Rdkafka::Bindings.rd_kafka_subscribe(@native_kafka.inner, tpl)
59
+ response = @native_kafka.with_inner do |inner|
60
+ Rdkafka::Bindings.rd_kafka_subscribe(inner, tpl)
61
+ end
58
62
  if response != 0
59
63
  raise Rdkafka::RdkafkaError.new(response, "Error subscribing to '#{topics.join(', ')}'")
60
64
  end
@@ -70,7 +74,9 @@ module Rdkafka
70
74
  def unsubscribe
71
75
  closed_consumer_check(__method__)
72
76
 
73
- response = Rdkafka::Bindings.rd_kafka_unsubscribe(@native_kafka.inner)
77
+ response = @native_kafka.with_inner do |inner|
78
+ Rdkafka::Bindings.rd_kafka_unsubscribe(inner)
79
+ end
74
80
  if response != 0
75
81
  raise Rdkafka::RdkafkaError.new(response)
76
82
  end
@@ -93,7 +99,9 @@ module Rdkafka
93
99
  tpl = list.to_native_tpl
94
100
 
95
101
  begin
96
- response = Rdkafka::Bindings.rd_kafka_pause_partitions(@native_kafka.inner, tpl)
102
+ response = @native_kafka.with_inner do |inner|
103
+ Rdkafka::Bindings.rd_kafka_pause_partitions(inner, tpl)
104
+ end
97
105
 
98
106
  if response != 0
99
107
  list = TopicPartitionList.from_native_tpl(tpl)
@@ -121,7 +129,9 @@ module Rdkafka
121
129
  tpl = list.to_native_tpl
122
130
 
123
131
  begin
124
- response = Rdkafka::Bindings.rd_kafka_resume_partitions(@native_kafka.inner, tpl)
132
+ response = @native_kafka.with_inner do |inner|
133
+ Rdkafka::Bindings.rd_kafka_resume_partitions(inner, tpl)
134
+ end
125
135
  if response != 0
126
136
  raise Rdkafka::RdkafkaError.new(response, "Error resume '#{list.to_h}'")
127
137
  end
@@ -139,7 +149,9 @@ module Rdkafka
139
149
  closed_consumer_check(__method__)
140
150
 
141
151
  ptr = FFI::MemoryPointer.new(:pointer)
142
- response = Rdkafka::Bindings.rd_kafka_subscription(@native_kafka.inner, ptr)
152
+ response = @native_kafka.with_inner do |inner|
153
+ Rdkafka::Bindings.rd_kafka_subscription(inner, ptr)
154
+ end
143
155
 
144
156
  if response != 0
145
157
  raise Rdkafka::RdkafkaError.new(response)
@@ -169,7 +181,9 @@ module Rdkafka
169
181
  tpl = list.to_native_tpl
170
182
 
171
183
  begin
172
- response = Rdkafka::Bindings.rd_kafka_assign(@native_kafka.inner, tpl)
184
+ response = @native_kafka.with_inner do |inner|
185
+ Rdkafka::Bindings.rd_kafka_assign(inner, tpl)
186
+ end
173
187
  if response != 0
174
188
  raise Rdkafka::RdkafkaError.new(response, "Error assigning '#{list.to_h}'")
175
189
  end
@@ -187,7 +201,9 @@ module Rdkafka
187
201
  closed_consumer_check(__method__)
188
202
 
189
203
  ptr = FFI::MemoryPointer.new(:pointer)
190
- response = Rdkafka::Bindings.rd_kafka_assignment(@native_kafka.inner, ptr)
204
+ response = @native_kafka.with_inner do |inner|
205
+ Rdkafka::Bindings.rd_kafka_assignment(inner, ptr)
206
+ end
191
207
  if response != 0
192
208
  raise Rdkafka::RdkafkaError.new(response)
193
209
  end
@@ -226,7 +242,9 @@ module Rdkafka
226
242
  tpl = list.to_native_tpl
227
243
 
228
244
  begin
229
- response = Rdkafka::Bindings.rd_kafka_committed(@native_kafka.inner, tpl, timeout_ms)
245
+ response = @native_kafka.with_inner do |inner|
246
+ Rdkafka::Bindings.rd_kafka_committed(inner, tpl, timeout_ms)
247
+ end
230
248
  if response != 0
231
249
  raise Rdkafka::RdkafkaError.new(response)
232
250
  end
@@ -251,14 +269,16 @@ module Rdkafka
251
269
  low = FFI::MemoryPointer.new(:int64, 1)
252
270
  high = FFI::MemoryPointer.new(:int64, 1)
253
271
 
254
- response = Rdkafka::Bindings.rd_kafka_query_watermark_offsets(
255
- @native_kafka.inner,
256
- topic,
257
- partition,
258
- low,
259
- high,
260
- timeout_ms,
261
- )
272
+ response = @native_kafka.with_inner do |inner|
273
+ Rdkafka::Bindings.rd_kafka_query_watermark_offsets(
274
+ inner,
275
+ topic,
276
+ partition,
277
+ low,
278
+ high,
279
+ timeout_ms,
280
+ )
281
+ end
262
282
  if response != 0
263
283
  raise Rdkafka::RdkafkaError.new(response, "Error querying watermark offsets for partition #{partition} of #{topic}")
264
284
  end
@@ -306,7 +326,9 @@ module Rdkafka
306
326
  # @return [String, nil]
307
327
  def cluster_id
308
328
  closed_consumer_check(__method__)
309
- Rdkafka::Bindings.rd_kafka_clusterid(@native_kafka.inner)
329
+ @native_kafka.with_inner do |inner|
330
+ Rdkafka::Bindings.rd_kafka_clusterid(inner)
331
+ end
310
332
  end
311
333
 
312
334
  # Returns this client's broker-assigned group member id
@@ -316,7 +338,9 @@ module Rdkafka
316
338
  # @return [String, nil]
317
339
  def member_id
318
340
  closed_consumer_check(__method__)
319
- Rdkafka::Bindings.rd_kafka_memberid(@native_kafka.inner)
341
+ @native_kafka.with_inner do |inner|
342
+ Rdkafka::Bindings.rd_kafka_memberid(inner)
343
+ end
320
344
  end
321
345
 
322
346
  # Store offset of a message to be used in the next commit of this consumer
@@ -333,11 +357,13 @@ module Rdkafka
333
357
 
334
358
  # rd_kafka_offset_store is one of the few calls that does not support
335
359
  # a string as the topic, so create a native topic for it.
336
- native_topic = Rdkafka::Bindings.rd_kafka_topic_new(
337
- @native_kafka.inner,
338
- message.topic,
339
- nil
340
- )
360
+ native_topic = @native_kafka.with_inner do |inner|
361
+ Rdkafka::Bindings.rd_kafka_topic_new(
362
+ inner,
363
+ message.topic,
364
+ nil
365
+ )
366
+ end
341
367
  response = Rdkafka::Bindings.rd_kafka_offset_store(
342
368
  native_topic,
343
369
  message.partition,
@@ -365,11 +391,13 @@ module Rdkafka
365
391
 
366
392
  # rd_kafka_offset_store is one of the few calls that does not support
367
393
  # a string as the topic, so create a native topic for it.
368
- native_topic = Rdkafka::Bindings.rd_kafka_topic_new(
369
- @native_kafka.inner,
370
- message.topic,
371
- nil
372
- )
394
+ native_topic = @native_kafka.with_inner do |inner|
395
+ Rdkafka::Bindings.rd_kafka_topic_new(
396
+ inner,
397
+ message.topic,
398
+ nil
399
+ )
400
+ end
373
401
  response = Rdkafka::Bindings.rd_kafka_seek(
374
402
  native_topic,
375
403
  message.partition,
@@ -410,7 +438,9 @@ module Rdkafka
410
438
  tpl = list ? list.to_native_tpl : nil
411
439
 
412
440
  begin
413
- response = Rdkafka::Bindings.rd_kafka_commit(@native_kafka.inner, tpl, async)
441
+ response = @native_kafka.with_inner do |inner|
442
+ Rdkafka::Bindings.rd_kafka_commit(inner, tpl, async)
443
+ end
414
444
  if response != 0
415
445
  raise Rdkafka::RdkafkaError.new(response)
416
446
  end
@@ -429,7 +459,9 @@ module Rdkafka
429
459
  def poll(timeout_ms)
430
460
  closed_consumer_check(__method__)
431
461
 
432
- message_ptr = Rdkafka::Bindings.rd_kafka_consumer_poll(@native_kafka.inner, timeout_ms)
462
+ message_ptr = @native_kafka.with_inner do |inner|
463
+ Rdkafka::Bindings.rd_kafka_consumer_poll(inner, timeout_ms)
464
+ end
433
465
  if message_ptr.null?
434
466
  nil
435
467
  else
@@ -6,19 +6,27 @@ module Rdkafka
6
6
  class NativeKafka
7
7
  def initialize(inner, run_polling_thread:)
8
8
  @inner = inner
9
+ # Lock around external access
10
+ @access_mutex = Mutex.new
11
+ # Lock around internal polling
12
+ @poll_mutex = Mutex.new
9
13
 
10
14
  if run_polling_thread
11
15
  # Start thread to poll client for delivery callbacks,
12
16
  # not used in consumer.
13
17
  @polling_thread = Thread.new do
14
18
  loop do
15
- Rdkafka::Bindings.rd_kafka_poll(inner, 250)
19
+ @poll_mutex.synchronize do
20
+ Rdkafka::Bindings.rd_kafka_poll(inner, 100)
21
+ end
22
+
16
23
  # Exit thread if closing and the poll queue is empty
17
24
  if Thread.current[:closing] && Rdkafka::Bindings.rd_kafka_outq_len(inner) == 0
18
25
  break
19
26
  end
20
27
  end
21
28
  end
29
+
22
30
  @polling_thread.abort_on_exception = true
23
31
  @polling_thread[:closing] = false
24
32
  end
@@ -26,8 +34,12 @@ module Rdkafka
26
34
  @closing = false
27
35
  end
28
36
 
29
- def inner
30
- @inner
37
+ def with_inner
38
+ return if @inner.nil?
39
+
40
+ @access_mutex.synchronize do
41
+ yield @inner
42
+ end
31
43
  end
32
44
 
33
45
  def finalizer
@@ -41,21 +53,30 @@ module Rdkafka
41
53
  def close(object_id=nil)
42
54
  return if closed?
43
55
 
56
+ @access_mutex.lock
57
+
44
58
  # Indicate to the outside world that we are closing
45
59
  @closing = true
46
60
 
47
61
  if @polling_thread
48
62
  # Indicate to polling thread that we're closing
49
63
  @polling_thread[:closing] = true
50
- # Wait for the polling thread to finish up
64
+
65
+ # Wait for the polling thread to finish up,
66
+ # this can be aborted in practice if this
67
+ # code runs from a finalizer.
51
68
  @polling_thread.join
52
69
  end
53
70
 
54
- # Destroy the client
55
- Rdkafka::Bindings.rd_kafka_destroy_flags(
56
- @inner,
57
- Rdkafka::Bindings::RD_KAFKA_DESTROY_F_IMMEDIATE
58
- )
71
+ # Destroy the client after locking both mutexes
72
+ @poll_mutex.lock
73
+
74
+ # This check prevents a race condition, where we would enter the close in two threads
75
+ # and after unlocking the primary one that hold the lock but finished, ours would be unlocked
76
+ # and would continue to run, trying to destroy inner twice
77
+ return unless @inner
78
+
79
+ Rdkafka::Bindings.rd_kafka_destroy(@inner)
59
80
  @inner = nil
60
81
  end
61
82
  end
@@ -52,9 +52,14 @@ module Rdkafka
52
52
 
53
53
  # Wait until all outstanding producer requests are completed, with the given timeout
54
54
  # in seconds. Call this before closing a producer to ensure delivery of all messages.
55
+ #
56
+ # @param timeout_ms [Integer] how long should we wait for flush of all messages
55
57
  def flush(timeout_ms=5_000)
56
58
  closed_producer_check(__method__)
57
- Rdkafka::Bindings.rd_kafka_flush(@native_kafka.inner, timeout_ms)
59
+
60
+ @native_kafka.with_inner do |inner|
61
+ Rdkafka::Bindings.rd_kafka_flush(inner, timeout_ms)
62
+ end
58
63
  end
59
64
 
60
65
  # Partition count for a given topic.
@@ -65,7 +70,9 @@ module Rdkafka
65
70
  # @return partition count [Integer,nil]
66
71
  def partition_count(topic)
67
72
  closed_producer_check(__method__)
68
- Rdkafka::Metadata.new(@native_kafka.inner, topic).topics&.first[:partition_count]
73
+ @native_kafka.with_inner do |inner|
74
+ Rdkafka::Metadata.new(inner, topic).topics&.first[:partition_count]
75
+ end
69
76
  end
70
77
 
71
78
  # Produces a message to a Kafka topic. The message is added to rdkafka's queue, call {DeliveryHandle#wait wait} on the returned delivery handle to make sure it is delivered.
@@ -156,10 +163,12 @@ module Rdkafka
156
163
  args << :int << Rdkafka::Bindings::RD_KAFKA_VTYPE_END
157
164
 
158
165
  # Produce the message
159
- response = Rdkafka::Bindings.rd_kafka_producev(
160
- @native_kafka.inner,
161
- *args
162
- )
166
+ response = @native_kafka.with_inner do |inner|
167
+ Rdkafka::Bindings.rd_kafka_producev(
168
+ inner,
169
+ *args
170
+ )
171
+ end
163
172
 
164
173
  # Raise error if the produce call was not successful
165
174
  if response != 0
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rdkafka
4
- VERSION = "0.13.0.beta.3"
4
+ VERSION = "0.13.0.beta.5"
5
5
  LIBRDKAFKA_VERSION = "1.9.2"
6
6
  LIBRDKAFKA_SOURCE_SHA256 = "3fba157a9f80a0889c982acdd44608be8a46142270a389008b22d921be1198ad"
7
7
  end
@@ -151,22 +151,23 @@ describe Rdkafka::Config do
151
151
  end
152
152
 
153
153
  it "allows string partitioner key" do
154
- expect(Rdkafka::Producer).to receive(:new).with(kind_of(Rdkafka::NativeKafka), "murmur2")
154
+ expect(Rdkafka::Producer).to receive(:new).with(kind_of(Rdkafka::NativeKafka), "murmur2").and_call_original
155
155
  config = Rdkafka::Config.new("partitioner" => "murmur2")
156
- config.producer
156
+ config.producer.close
157
157
  end
158
158
 
159
159
  it "allows symbol partitioner key" do
160
- expect(Rdkafka::Producer).to receive(:new).with(kind_of(Rdkafka::NativeKafka), "murmur2")
160
+ expect(Rdkafka::Producer).to receive(:new).with(kind_of(Rdkafka::NativeKafka), "murmur2").and_call_original
161
161
  config = Rdkafka::Config.new(:partitioner => "murmur2")
162
- config.producer
162
+ config.producer.close
163
163
  end
164
164
 
165
165
  it "should allow configuring zstd compression" do
166
166
  config = Rdkafka::Config.new('compression.codec' => 'zstd')
167
167
  begin
168
- expect(config.producer).to be_a Rdkafka::Producer
169
- config.producer.close
168
+ producer = config.producer
169
+ expect(producer).to be_a Rdkafka::Producer
170
+ producer.close
170
171
  rescue Rdkafka::Config::ConfigError => ex
171
172
  pending "Zstd compression not supported on this machine"
172
173
  raise ex
@@ -28,7 +28,7 @@ describe Rdkafka::Consumer::Message do
28
28
  end
29
29
 
30
30
  after(:each) do
31
- Rdkafka::Bindings.rd_kafka_destroy_flags(native_client, Rdkafka::Bindings::RD_KAFKA_DESTROY_F_IMMEDIATE)
31
+ Rdkafka::Bindings.rd_kafka_destroy(native_client)
32
32
  end
33
33
 
34
34
  subject { Rdkafka::Consumer::Message.new(native_message) }
@@ -55,7 +55,7 @@ describe Rdkafka::Consumer do
55
55
 
56
56
  describe "#pause and #resume" do
57
57
  context "subscription" do
58
- let(:timeout) { 1000 }
58
+ let(:timeout) { 2000 }
59
59
 
60
60
  before { consumer.subscribe("consume_test_topic") }
61
61
  after { consumer.unsubscribe }
@@ -738,7 +738,8 @@ describe Rdkafka::Consumer do
738
738
  #
739
739
  # This is, in effect, an integration test and the subsequent specs are
740
740
  # unit tests.
741
- create_topic_handle = rdkafka_config.admin.create_topic(topic_name, 1, 1)
741
+ admin = rdkafka_config.admin
742
+ create_topic_handle = admin.create_topic(topic_name, 1, 1)
742
743
  create_topic_handle.wait(max_wait_timeout: 15.0)
743
744
  consumer.subscribe(topic_name)
744
745
  produce_n 42
@@ -751,6 +752,7 @@ describe Rdkafka::Consumer do
751
752
  expect(all_yields.flatten.size).to eq 42
752
753
  expect(all_yields.size).to be > 4
753
754
  expect(all_yields.flatten.map(&:key)).to eq (0..41).map { |x| x.to_s }
755
+ admin.close
754
756
  end
755
757
 
756
758
  it "should batch poll results and yield arrays of messages" do
@@ -793,13 +795,15 @@ describe Rdkafka::Consumer do
793
795
  end
794
796
 
795
797
  it "should yield [] if nothing is received before the timeout" do
796
- create_topic_handle = rdkafka_config.admin.create_topic(topic_name, 1, 1)
798
+ admin = rdkafka_config.admin
799
+ create_topic_handle = admin.create_topic(topic_name, 1, 1)
797
800
  create_topic_handle.wait(max_wait_timeout: 15.0)
798
801
  consumer.subscribe(topic_name)
799
802
  consumer.each_batch do |batch|
800
803
  expect(batch).to eq([])
801
804
  break
802
805
  end
806
+ admin.close
803
807
  end
804
808
 
805
809
  it "should yield batchs of max_items in size if messages are already fetched" do
@@ -876,6 +880,7 @@ describe Rdkafka::Consumer do
876
880
  expect(batches_yielded.first.size).to eq 2
877
881
  expect(exceptions_yielded.flatten.size).to eq 1
878
882
  expect(exceptions_yielded.flatten.first).to be_instance_of(Rdkafka::RdkafkaError)
883
+ consumer.close
879
884
  end
880
885
  end
881
886
 
@@ -917,6 +922,7 @@ describe Rdkafka::Consumer do
917
922
  expect(each_batch_iterations).to eq 0
918
923
  expect(batches_yielded.size).to eq 0
919
924
  expect(exceptions_yielded.size).to eq 0
925
+ consumer.close
920
926
  end
921
927
  end
922
928
  end
@@ -10,7 +10,7 @@ describe Rdkafka::Metadata do
10
10
 
11
11
  after do
12
12
  Rdkafka::Bindings.rd_kafka_consumer_close(native_kafka)
13
- Rdkafka::Bindings.rd_kafka_destroy_flags(native_kafka, Rdkafka::Bindings::RD_KAFKA_DESTROY_F_IMMEDIATE)
13
+ Rdkafka::Bindings.rd_kafka_destroy(native_kafka)
14
14
  end
15
15
 
16
16
  context "passing in a topic name" do
@@ -11,9 +11,6 @@ describe Rdkafka::NativeKafka do
11
11
  subject(:client) { described_class.new(native, run_polling_thread: true) }
12
12
 
13
13
  before do
14
- allow(Rdkafka::Bindings).to receive(:rd_kafka_poll).with(instance_of(FFI::Pointer), 250).and_call_original
15
- allow(Rdkafka::Bindings).to receive(:rd_kafka_outq_len).with(instance_of(FFI::Pointer)).and_return(0).and_call_original
16
- allow(Rdkafka::Bindings).to receive(:rd_kafka_destroy_flags)
17
14
  allow(Thread).to receive(:new).and_return(thread)
18
15
 
19
16
  allow(thread).to receive(:[]=).with(:closing, anything)
@@ -21,6 +18,8 @@ describe Rdkafka::NativeKafka do
21
18
  allow(thread).to receive(:abort_on_exception=).with(anything)
22
19
  end
23
20
 
21
+ after { client.close }
22
+
24
23
  context "defaults" do
25
24
  it "sets the thread to abort on exception" do
26
25
  expect(thread).to receive(:abort_on_exception=).with(true)
@@ -41,42 +40,12 @@ describe Rdkafka::NativeKafka do
41
40
 
42
41
  client
43
42
  end
44
-
45
- it "polls the native with default 250ms timeout" do
46
- polling_loop_expects do
47
- expect(Rdkafka::Bindings).to receive(:rd_kafka_poll).with(instance_of(FFI::Pointer), 250).at_least(:once)
48
- end
49
- end
50
-
51
- it "check the out queue of native client" do
52
- polling_loop_expects do
53
- expect(Rdkafka::Bindings).to receive(:rd_kafka_outq_len).with(native).at_least(:once)
54
- end
55
- end
56
-
57
- context "if not enabled" do
58
- subject(:client) { described_class.new(native, run_polling_thread: false) }
59
-
60
- it "is not created" do
61
- expect(Thread).not_to receive(:new)
62
-
63
- client
64
- end
65
- end
66
- end
67
-
68
- def polling_loop_expects(&block)
69
- Thread.current[:closing] = true # this forces the loop break with line #12
70
-
71
- allow(Thread).to receive(:new).and_yield do |_|
72
- block.call
73
- end.and_return(thread)
74
-
75
- client
76
43
  end
77
44
 
78
- it "exposes inner client" do
79
- expect(client.inner).to eq(native)
45
+ it "exposes the inner client" do
46
+ client.with_inner do |inner|
47
+ expect(inner).to eq(native)
48
+ end
80
49
  end
81
50
 
82
51
  context "when client was not yet closed (`nil`)" do
@@ -86,7 +55,7 @@ describe Rdkafka::NativeKafka do
86
55
 
87
56
  context "and attempt to close" do
88
57
  it "calls the `destroy` binding" do
89
- expect(Rdkafka::Bindings).to receive(:rd_kafka_destroy_flags).with(native, Rdkafka::Bindings::RD_KAFKA_DESTROY_F_IMMEDIATE)
58
+ expect(Rdkafka::Bindings).to receive(:rd_kafka_destroy).with(native).and_call_original
90
59
 
91
60
  client.close
92
61
  end
@@ -106,7 +75,6 @@ describe Rdkafka::NativeKafka do
106
75
  it "closes and unassign the native client" do
107
76
  client.close
108
77
 
109
- expect(client.inner).to eq(nil)
110
78
  expect(client.closed?).to eq(true)
111
79
  end
112
80
  end
@@ -141,7 +109,6 @@ describe Rdkafka::NativeKafka do
141
109
  it "does not close and unassign the native client again" do
142
110
  client.close
143
111
 
144
- expect(client.inner).to eq(nil)
145
112
  expect(client.closed?).to eq(true)
146
113
  end
147
114
  end
data/spec/spec_helper.rb CHANGED
@@ -73,7 +73,7 @@ def new_native_topic(topic_name="topic_name", native_client: )
73
73
  end
74
74
 
75
75
  def wait_for_message(topic:, delivery_report:, timeout_in_seconds: 30, consumer: nil)
76
- new_consumer = !!consumer
76
+ new_consumer = consumer.nil?
77
77
  consumer ||= rdkafka_consumer_config.consumer
78
78
  consumer.subscribe(topic)
79
79
  timeout = Time.now.to_i + timeout_in_seconds
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdkafka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0.beta.3
4
+ version: 0.13.0.beta.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thijs Cadier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-25 00:00:00.000000000 Z
11
+ date: 2023-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -139,8 +139,7 @@ dependencies:
139
139
  description: Modern Kafka client library for Ruby based on librdkafka
140
140
  email:
141
141
  - thijs@appsignal.com
142
- executables:
143
- - console
142
+ executables: []
144
143
  extensions:
145
144
  - ext/Rakefile
146
145
  extra_rdoc_files: []
@@ -155,7 +154,6 @@ files:
155
154
  - LICENSE
156
155
  - README.md
157
156
  - Rakefile
158
- - bin/console
159
157
  - docker-compose.yml
160
158
  - ext/README.md
161
159
  - ext/Rakefile
@@ -222,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
222
220
  - !ruby/object:Gem::Version
223
221
  version: 1.3.1
224
222
  requirements: []
225
- rubygems_version: 3.3.13
223
+ rubygems_version: 3.4.1
226
224
  signing_key:
227
225
  specification_version: 4
228
226
  summary: The rdkafka gem is a modern Kafka client library for Ruby based on librdkafka.
data/bin/console DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # frozen_string_literal: true
4
-
5
- ENV["IRBRC"] = File.join(File.dirname(__FILE__), ".irbrc")
6
-
7
- require "bundler/setup"
8
- require "rdkafka"
9
-
10
- require "irb"
11
- IRB.start(__FILE__)