rdkafka 0.12.0 → 0.15.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +57 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +155 -93
- data/Gemfile +2 -0
- data/{LICENSE → MIT-LICENSE} +2 -1
- data/README.md +76 -29
- data/Rakefile +2 -0
- data/certs/cert_chain.pem +26 -0
- data/docker-compose.yml +18 -15
- data/ext/README.md +1 -1
- data/ext/Rakefile +46 -27
- data/lib/rdkafka/abstract_handle.rb +41 -25
- data/lib/rdkafka/admin/acl_binding_result.rb +51 -0
- data/lib/rdkafka/admin/create_acl_handle.rb +28 -0
- data/lib/rdkafka/admin/create_acl_report.rb +24 -0
- data/lib/rdkafka/admin/create_partitions_handle.rb +27 -0
- data/lib/rdkafka/admin/create_partitions_report.rb +6 -0
- data/lib/rdkafka/admin/create_topic_handle.rb +2 -0
- data/lib/rdkafka/admin/create_topic_report.rb +2 -0
- data/lib/rdkafka/admin/delete_acl_handle.rb +30 -0
- data/lib/rdkafka/admin/delete_acl_report.rb +23 -0
- data/lib/rdkafka/admin/delete_groups_handle.rb +28 -0
- data/lib/rdkafka/admin/delete_groups_report.rb +24 -0
- data/lib/rdkafka/admin/delete_topic_handle.rb +2 -0
- data/lib/rdkafka/admin/delete_topic_report.rb +2 -0
- data/lib/rdkafka/admin/describe_acl_handle.rb +30 -0
- data/lib/rdkafka/admin/describe_acl_report.rb +23 -0
- data/lib/rdkafka/admin.rb +494 -35
- data/lib/rdkafka/bindings.rb +180 -41
- data/lib/rdkafka/callbacks.rb +202 -1
- data/lib/rdkafka/config.rb +62 -25
- data/lib/rdkafka/consumer/headers.rb +24 -9
- data/lib/rdkafka/consumer/message.rb +3 -1
- data/lib/rdkafka/consumer/partition.rb +2 -0
- data/lib/rdkafka/consumer/topic_partition_list.rb +13 -8
- data/lib/rdkafka/consumer.rb +243 -111
- data/lib/rdkafka/error.rb +15 -0
- data/lib/rdkafka/helpers/time.rb +14 -0
- data/lib/rdkafka/metadata.rb +25 -2
- data/lib/rdkafka/native_kafka.rb +120 -0
- data/lib/rdkafka/producer/delivery_handle.rb +16 -2
- data/lib/rdkafka/producer/delivery_report.rb +22 -2
- data/lib/rdkafka/producer.rb +151 -21
- data/lib/rdkafka/version.rb +5 -3
- data/lib/rdkafka.rb +24 -2
- data/rdkafka.gemspec +21 -5
- data/renovate.json +6 -0
- data/spec/rdkafka/abstract_handle_spec.rb +1 -1
- data/spec/rdkafka/admin/create_acl_handle_spec.rb +56 -0
- data/spec/rdkafka/admin/create_acl_report_spec.rb +18 -0
- data/spec/rdkafka/admin/create_topic_handle_spec.rb +1 -1
- data/spec/rdkafka/admin/create_topic_report_spec.rb +1 -1
- data/spec/rdkafka/admin/delete_acl_handle_spec.rb +85 -0
- data/spec/rdkafka/admin/delete_acl_report_spec.rb +72 -0
- data/spec/rdkafka/admin/delete_topic_handle_spec.rb +1 -1
- data/spec/rdkafka/admin/delete_topic_report_spec.rb +1 -1
- data/spec/rdkafka/admin/describe_acl_handle_spec.rb +85 -0
- data/spec/rdkafka/admin/describe_acl_report_spec.rb +73 -0
- data/spec/rdkafka/admin_spec.rb +209 -5
- data/spec/rdkafka/bindings_spec.rb +2 -1
- data/spec/rdkafka/callbacks_spec.rb +1 -1
- data/spec/rdkafka/config_spec.rb +24 -3
- data/spec/rdkafka/consumer/headers_spec.rb +60 -0
- data/spec/rdkafka/consumer/message_spec.rb +1 -1
- data/spec/rdkafka/consumer/partition_spec.rb +1 -1
- data/spec/rdkafka/consumer/topic_partition_list_spec.rb +20 -1
- data/spec/rdkafka/consumer_spec.rb +352 -61
- data/spec/rdkafka/error_spec.rb +1 -1
- data/spec/rdkafka/metadata_spec.rb +4 -3
- data/spec/rdkafka/{producer/client_spec.rb → native_kafka_spec.rb} +13 -35
- data/spec/rdkafka/producer/delivery_handle_spec.rb +4 -1
- data/spec/rdkafka/producer/delivery_report_spec.rb +11 -3
- data/spec/rdkafka/producer_spec.rb +234 -22
- data/spec/spec_helper.rb +20 -2
- data.tar.gz.sig +0 -0
- metadata +81 -17
- metadata.gz.sig +0 -0
- data/.semaphore/semaphore.yml +0 -23
- data/bin/console +0 -11
- data/lib/rdkafka/producer/client.rb +0 -47
data/spec/rdkafka/error_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "securerandom"
|
3
4
|
|
4
5
|
describe Rdkafka::Metadata do
|
@@ -29,7 +30,7 @@ describe Rdkafka::Metadata do
|
|
29
30
|
it "#brokers returns our single broker" do
|
30
31
|
expect(subject.brokers.length).to eq(1)
|
31
32
|
expect(subject.brokers[0][:broker_id]).to eq(1)
|
32
|
-
expect(subject.brokers[0][:broker_name]).to eq("
|
33
|
+
expect(subject.brokers[0][:broker_name]).to eq("127.0.0.1")
|
33
34
|
expect(subject.brokers[0][:broker_port]).to eq(9092)
|
34
35
|
end
|
35
36
|
|
@@ -52,7 +53,7 @@ describe Rdkafka::Metadata do
|
|
52
53
|
it "#brokers returns our single broker" do
|
53
54
|
expect(subject.brokers.length).to eq(1)
|
54
55
|
expect(subject.brokers[0][:broker_id]).to eq(1)
|
55
|
-
expect(subject.brokers[0][:broker_name]).to eq("
|
56
|
+
expect(subject.brokers[0][:broker_name]).to eq("127.0.0.1")
|
56
57
|
expect(subject.brokers[0][:broker_port]).to eq(9092)
|
57
58
|
end
|
58
59
|
|
@@ -1,17 +1,15 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
describe Rdkafka::
|
3
|
+
describe Rdkafka::NativeKafka do
|
4
4
|
let(:config) { rdkafka_producer_config }
|
5
5
|
let(:native) { config.send(:native_kafka, config.send(:native_config), :rd_kafka_producer) }
|
6
6
|
let(:closing) { false }
|
7
7
|
let(:thread) { double(Thread) }
|
8
|
+
let(:opaque) { Rdkafka::Opaque.new }
|
8
9
|
|
9
|
-
subject(:client) { described_class.new(native) }
|
10
|
+
subject(:client) { described_class.new(native, run_polling_thread: true, opaque: opaque) }
|
10
11
|
|
11
12
|
before do
|
12
|
-
allow(Rdkafka::Bindings).to receive(:rd_kafka_poll).with(instance_of(FFI::Pointer), 250).and_call_original
|
13
|
-
allow(Rdkafka::Bindings).to receive(:rd_kafka_outq_len).with(instance_of(FFI::Pointer)).and_return(0).and_call_original
|
14
|
-
allow(Rdkafka::Bindings).to receive(:rd_kafka_destroy)
|
15
13
|
allow(Thread).to receive(:new).and_return(thread)
|
16
14
|
|
17
15
|
allow(thread).to receive(:[]=).with(:closing, anything)
|
@@ -19,6 +17,8 @@ describe Rdkafka::Producer::Client do
|
|
19
17
|
allow(thread).to receive(:abort_on_exception=).with(anything)
|
20
18
|
end
|
21
19
|
|
20
|
+
after { client.close }
|
21
|
+
|
22
22
|
context "defaults" do
|
23
23
|
it "sets the thread to abort on exception" do
|
24
24
|
expect(thread).to receive(:abort_on_exception=).with(true)
|
@@ -39,32 +39,12 @@ describe Rdkafka::Producer::Client do
|
|
39
39
|
|
40
40
|
client
|
41
41
|
end
|
42
|
-
|
43
|
-
it "polls the native with default 250ms timeout" do
|
44
|
-
polling_loop_expects do
|
45
|
-
expect(Rdkafka::Bindings).to receive(:rd_kafka_poll).with(instance_of(FFI::Pointer), 250).at_least(:once)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
it "check the out queue of native client" do
|
50
|
-
polling_loop_expects do
|
51
|
-
expect(Rdkafka::Bindings).to receive(:rd_kafka_outq_len).with(native).at_least(:once)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def polling_loop_expects(&block)
|
57
|
-
Thread.current[:closing] = true # this forces the loop break with line #12
|
58
|
-
|
59
|
-
allow(Thread).to receive(:new).and_yield do |_|
|
60
|
-
block.call
|
61
|
-
end.and_return(thread)
|
62
|
-
|
63
|
-
client
|
64
42
|
end
|
65
43
|
|
66
|
-
it "exposes
|
67
|
-
|
44
|
+
it "exposes the inner client" do
|
45
|
+
client.with_inner do |inner|
|
46
|
+
expect(inner).to eq(native)
|
47
|
+
end
|
68
48
|
end
|
69
49
|
|
70
50
|
context "when client was not yet closed (`nil`)" do
|
@@ -74,7 +54,7 @@ describe Rdkafka::Producer::Client do
|
|
74
54
|
|
75
55
|
context "and attempt to close" do
|
76
56
|
it "calls the `destroy` binding" do
|
77
|
-
expect(Rdkafka::Bindings).to receive(:rd_kafka_destroy).with(native)
|
57
|
+
expect(Rdkafka::Bindings).to receive(:rd_kafka_destroy).with(native).and_call_original
|
78
58
|
|
79
59
|
client.close
|
80
60
|
end
|
@@ -94,7 +74,6 @@ describe Rdkafka::Producer::Client do
|
|
94
74
|
it "closes and unassign the native client" do
|
95
75
|
client.close
|
96
76
|
|
97
|
-
expect(client.native).to eq(nil)
|
98
77
|
expect(client.closed?).to eq(true)
|
99
78
|
end
|
100
79
|
end
|
@@ -109,7 +88,7 @@ describe Rdkafka::Producer::Client do
|
|
109
88
|
|
110
89
|
context "and attempt to close again" do
|
111
90
|
it "does not call the `destroy` binding" do
|
112
|
-
expect(Rdkafka::Bindings).not_to receive(:
|
91
|
+
expect(Rdkafka::Bindings).not_to receive(:rd_kafka_destroy_flags)
|
113
92
|
|
114
93
|
client.close
|
115
94
|
end
|
@@ -129,13 +108,12 @@ describe Rdkafka::Producer::Client do
|
|
129
108
|
it "does not close and unassign the native client again" do
|
130
109
|
client.close
|
131
110
|
|
132
|
-
expect(client.native).to eq(nil)
|
133
111
|
expect(client.closed?).to eq(true)
|
134
112
|
end
|
135
113
|
end
|
136
114
|
end
|
137
115
|
|
138
|
-
it "
|
116
|
+
it "provides a finalizer that closes the native kafka client" do
|
139
117
|
expect(client.closed?).to eq(false)
|
140
118
|
|
141
119
|
client.finalizer.call("some-ignored-object-id")
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
describe Rdkafka::Producer::DeliveryHandle do
|
4
4
|
let(:response) { 0 }
|
@@ -9,6 +9,7 @@ describe Rdkafka::Producer::DeliveryHandle do
|
|
9
9
|
handle[:response] = response
|
10
10
|
handle[:partition] = 2
|
11
11
|
handle[:offset] = 100
|
12
|
+
handle[:topic_name] = FFI::MemoryPointer.from_string("produce_test_topic")
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
@@ -29,6 +30,7 @@ describe Rdkafka::Producer::DeliveryHandle do
|
|
29
30
|
|
30
31
|
expect(report.partition).to eq(2)
|
31
32
|
expect(report.offset).to eq(100)
|
33
|
+
expect(report.topic_name).to eq("produce_test_topic")
|
32
34
|
end
|
33
35
|
|
34
36
|
it "should wait without a timeout" do
|
@@ -36,6 +38,7 @@ describe Rdkafka::Producer::DeliveryHandle do
|
|
36
38
|
|
37
39
|
expect(report.partition).to eq(2)
|
38
40
|
expect(report.offset).to eq(100)
|
41
|
+
expect(report.topic_name).to eq("produce_test_topic")
|
39
42
|
end
|
40
43
|
end
|
41
44
|
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
describe Rdkafka::Producer::DeliveryReport do
|
4
|
-
subject { Rdkafka::Producer::DeliveryReport.new(2, 100, "
|
4
|
+
subject { Rdkafka::Producer::DeliveryReport.new(2, 100, "topic", -1) }
|
5
5
|
|
6
6
|
it "should get the partition" do
|
7
7
|
expect(subject.partition).to eq 2
|
@@ -11,7 +11,15 @@ describe Rdkafka::Producer::DeliveryReport do
|
|
11
11
|
expect(subject.offset).to eq 100
|
12
12
|
end
|
13
13
|
|
14
|
+
it "should get the topic_name" do
|
15
|
+
expect(subject.topic_name).to eq "topic"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should get the same topic name under topic alias" do
|
19
|
+
expect(subject.topic).to eq "topic"
|
20
|
+
end
|
21
|
+
|
14
22
|
it "should get the error" do
|
15
|
-
expect(subject.error).to eq
|
23
|
+
expect(subject.error).to eq -1
|
16
24
|
end
|
17
25
|
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "zlib"
|
3
4
|
|
4
5
|
describe Rdkafka::Producer do
|
@@ -7,11 +8,16 @@ describe Rdkafka::Producer do
|
|
7
8
|
|
8
9
|
after do
|
9
10
|
# Registry should always end up being empty
|
10
|
-
|
11
|
+
registry = Rdkafka::Producer::DeliveryHandle::REGISTRY
|
12
|
+
expect(registry).to be_empty, registry.inspect
|
11
13
|
producer.close
|
12
14
|
consumer.close
|
13
15
|
end
|
14
16
|
|
17
|
+
describe '#name' do
|
18
|
+
it { expect(producer.name).to include('rdkafka#producer-') }
|
19
|
+
end
|
20
|
+
|
15
21
|
context "delivery callback" do
|
16
22
|
context "with a proc/lambda" do
|
17
23
|
it "should set the callback" do
|
@@ -28,8 +34,10 @@ describe Rdkafka::Producer do
|
|
28
34
|
|
29
35
|
producer.delivery_callback = lambda do |report|
|
30
36
|
expect(report).not_to be_nil
|
37
|
+
expect(report.label).to eq "label"
|
31
38
|
expect(report.partition).to eq 1
|
32
39
|
expect(report.offset).to be >= 0
|
40
|
+
expect(report.topic_name).to eq "produce_test_topic"
|
33
41
|
@callback_called = true
|
34
42
|
end
|
35
43
|
|
@@ -37,9 +45,12 @@ describe Rdkafka::Producer do
|
|
37
45
|
handle = producer.produce(
|
38
46
|
topic: "produce_test_topic",
|
39
47
|
payload: "payload",
|
40
|
-
key: "key"
|
48
|
+
key: "key",
|
49
|
+
label: "label"
|
41
50
|
)
|
42
51
|
|
52
|
+
expect(handle.label).to eq "label"
|
53
|
+
|
43
54
|
# Wait for it to be delivered
|
44
55
|
handle.wait(max_wait_timeout: 15)
|
45
56
|
|
@@ -113,6 +124,7 @@ describe Rdkafka::Producer do
|
|
113
124
|
expect(called_report.first).not_to be_nil
|
114
125
|
expect(called_report.first.partition).to eq 1
|
115
126
|
expect(called_report.first.offset).to be >= 0
|
127
|
+
expect(called_report.first.topic_name).to eq "produce_test_topic"
|
116
128
|
end
|
117
129
|
|
118
130
|
it "should provide handle" do
|
@@ -167,11 +179,13 @@ describe Rdkafka::Producer do
|
|
167
179
|
handle = producer.produce(
|
168
180
|
topic: "produce_test_topic",
|
169
181
|
payload: "payload",
|
170
|
-
key: "key"
|
182
|
+
key: "key",
|
183
|
+
label: "label"
|
171
184
|
)
|
172
185
|
|
173
186
|
# Should be pending at first
|
174
187
|
expect(handle.pending?).to be true
|
188
|
+
expect(handle.label).to eq "label"
|
175
189
|
|
176
190
|
# Check delivery handle and report
|
177
191
|
report = handle.wait(max_wait_timeout: 5)
|
@@ -179,11 +193,13 @@ describe Rdkafka::Producer do
|
|
179
193
|
expect(report).not_to be_nil
|
180
194
|
expect(report.partition).to eq 1
|
181
195
|
expect(report.offset).to be >= 0
|
196
|
+
expect(report.label).to eq "label"
|
182
197
|
|
183
|
-
#
|
198
|
+
# Flush and close producer
|
199
|
+
producer.flush
|
184
200
|
producer.close
|
185
201
|
|
186
|
-
# Consume message and verify
|
202
|
+
# Consume message and verify its content
|
187
203
|
message = wait_for_message(
|
188
204
|
topic: "produce_test_topic",
|
189
205
|
delivery_report: report,
|
@@ -207,7 +223,7 @@ describe Rdkafka::Producer do
|
|
207
223
|
)
|
208
224
|
report = handle.wait(max_wait_timeout: 5)
|
209
225
|
|
210
|
-
# Consume message and verify
|
226
|
+
# Consume message and verify its content
|
211
227
|
message = wait_for_message(
|
212
228
|
topic: "produce_test_topic",
|
213
229
|
delivery_report: report,
|
@@ -251,6 +267,28 @@ describe Rdkafka::Producer do
|
|
251
267
|
expect(messages[2].key).to eq key
|
252
268
|
end
|
253
269
|
|
270
|
+
it "should produce a message with empty string without crashing" do
|
271
|
+
messages = [{key: 'a', partition_key: ''}]
|
272
|
+
|
273
|
+
messages = messages.map do |m|
|
274
|
+
handle = producer.produce(
|
275
|
+
topic: "partitioner_test_topic",
|
276
|
+
payload: "payload partition",
|
277
|
+
key: m[:key],
|
278
|
+
partition_key: m[:partition_key]
|
279
|
+
)
|
280
|
+
report = handle.wait(max_wait_timeout: 5)
|
281
|
+
|
282
|
+
wait_for_message(
|
283
|
+
topic: "partitioner_test_topic",
|
284
|
+
delivery_report: report,
|
285
|
+
)
|
286
|
+
end
|
287
|
+
|
288
|
+
expect(messages[0].partition).to eq 0
|
289
|
+
expect(messages[0].key).to eq 'a'
|
290
|
+
end
|
291
|
+
|
254
292
|
it "should produce a message with utf-8 encoding" do
|
255
293
|
handle = producer.produce(
|
256
294
|
topic: "produce_test_topic",
|
@@ -259,7 +297,7 @@ describe Rdkafka::Producer do
|
|
259
297
|
)
|
260
298
|
report = handle.wait(max_wait_timeout: 5)
|
261
299
|
|
262
|
-
# Consume message and verify
|
300
|
+
# Consume message and verify its content
|
263
301
|
message = wait_for_message(
|
264
302
|
topic: "produce_test_topic",
|
265
303
|
delivery_report: report,
|
@@ -292,7 +330,7 @@ describe Rdkafka::Producer do
|
|
292
330
|
)
|
293
331
|
report = handle.wait(max_wait_timeout: 5)
|
294
332
|
|
295
|
-
# Consume message and verify
|
333
|
+
# Consume message and verify its content
|
296
334
|
message = wait_for_message(
|
297
335
|
topic: "produce_test_topic",
|
298
336
|
delivery_report: report,
|
@@ -313,7 +351,7 @@ describe Rdkafka::Producer do
|
|
313
351
|
)
|
314
352
|
report = handle.wait(max_wait_timeout: 5)
|
315
353
|
|
316
|
-
# Consume message and verify
|
354
|
+
# Consume message and verify its content
|
317
355
|
message = wait_for_message(
|
318
356
|
topic: "produce_test_topic",
|
319
357
|
delivery_report: report,
|
@@ -333,7 +371,7 @@ describe Rdkafka::Producer do
|
|
333
371
|
)
|
334
372
|
report = handle.wait(max_wait_timeout: 5)
|
335
373
|
|
336
|
-
# Consume message and verify
|
374
|
+
# Consume message and verify its content
|
337
375
|
message = wait_for_message(
|
338
376
|
topic: "produce_test_topic",
|
339
377
|
delivery_report: report,
|
@@ -351,7 +389,7 @@ describe Rdkafka::Producer do
|
|
351
389
|
)
|
352
390
|
report = handle.wait(max_wait_timeout: 5)
|
353
391
|
|
354
|
-
# Consume message and verify
|
392
|
+
# Consume message and verify its content
|
355
393
|
message = wait_for_message(
|
356
394
|
topic: "produce_test_topic",
|
357
395
|
delivery_report: report,
|
@@ -371,7 +409,7 @@ describe Rdkafka::Producer do
|
|
371
409
|
)
|
372
410
|
report = handle.wait(max_wait_timeout: 5)
|
373
411
|
|
374
|
-
# Consume message and verify
|
412
|
+
# Consume message and verify its content
|
375
413
|
message = wait_for_message(
|
376
414
|
topic: "produce_test_topic",
|
377
415
|
delivery_report: report,
|
@@ -380,9 +418,9 @@ describe Rdkafka::Producer do
|
|
380
418
|
|
381
419
|
expect(message.payload).to eq "payload headers"
|
382
420
|
expect(message.key).to eq "key headers"
|
383
|
-
expect(message.headers[
|
384
|
-
expect(message.headers[
|
385
|
-
expect(message.headers[
|
421
|
+
expect(message.headers["foo"]).to eq "bar"
|
422
|
+
expect(message.headers["baz"]).to eq "foobar"
|
423
|
+
expect(message.headers["foobar"]).to be_nil
|
386
424
|
end
|
387
425
|
|
388
426
|
it "should produce a message with empty headers" do
|
@@ -394,7 +432,7 @@ describe Rdkafka::Producer do
|
|
394
432
|
)
|
395
433
|
report = handle.wait(max_wait_timeout: 5)
|
396
434
|
|
397
|
-
# Consume message and verify
|
435
|
+
# Consume message and verify its content
|
398
436
|
message = wait_for_message(
|
399
437
|
topic: "produce_test_topic",
|
400
438
|
delivery_report: report,
|
@@ -432,10 +470,10 @@ describe Rdkafka::Producer do
|
|
432
470
|
# wait for and check the message in the main process.
|
433
471
|
reader, writer = IO.pipe
|
434
472
|
|
435
|
-
fork do
|
473
|
+
pid = fork do
|
436
474
|
reader.close
|
437
475
|
|
438
|
-
#
|
476
|
+
# Avoid sharing the client between processes.
|
439
477
|
producer = rdkafka_producer_config.producer
|
440
478
|
|
441
479
|
handle = producer.produce(
|
@@ -448,24 +486,28 @@ describe Rdkafka::Producer do
|
|
448
486
|
|
449
487
|
report_json = JSON.generate(
|
450
488
|
"partition" => report.partition,
|
451
|
-
"offset" => report.offset
|
489
|
+
"offset" => report.offset,
|
490
|
+
"topic_name" => report.topic_name
|
452
491
|
)
|
453
492
|
|
454
493
|
writer.write(report_json)
|
455
494
|
writer.close
|
495
|
+
producer.flush
|
456
496
|
producer.close
|
457
497
|
end
|
498
|
+
Process.wait(pid)
|
458
499
|
|
459
500
|
writer.close
|
460
501
|
report_hash = JSON.parse(reader.read)
|
461
502
|
report = Rdkafka::Producer::DeliveryReport.new(
|
462
503
|
report_hash["partition"],
|
463
|
-
report_hash["offset"]
|
504
|
+
report_hash["offset"],
|
505
|
+
report_hash["topic_name"]
|
464
506
|
)
|
465
507
|
|
466
508
|
reader.close
|
467
509
|
|
468
|
-
# Consume message and verify
|
510
|
+
# Consume message and verify its content
|
469
511
|
message = wait_for_message(
|
470
512
|
topic: "produce_test_topic",
|
471
513
|
delivery_report: report,
|
@@ -522,4 +564,174 @@ describe Rdkafka::Producer do
|
|
522
564
|
end
|
523
565
|
end
|
524
566
|
end
|
567
|
+
|
568
|
+
context "when not being able to deliver the message" do
|
569
|
+
let(:producer) do
|
570
|
+
rdkafka_producer_config(
|
571
|
+
"bootstrap.servers": "localhost:9093",
|
572
|
+
"message.timeout.ms": 100
|
573
|
+
).producer
|
574
|
+
end
|
575
|
+
|
576
|
+
it "should contain the error in the response when not deliverable" do
|
577
|
+
handler = producer.produce(topic: 'produce_test_topic', payload: nil, label: 'na')
|
578
|
+
# Wait for the async callbacks and delivery registry to update
|
579
|
+
sleep(2)
|
580
|
+
expect(handler.create_result.error).to be_a(Rdkafka::RdkafkaError)
|
581
|
+
expect(handler.create_result.label).to eq('na')
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
describe '#partition_count' do
|
586
|
+
it { expect(producer.partition_count('consume_test_topic')).to eq(3) }
|
587
|
+
|
588
|
+
context 'when the partition count value is already cached' do
|
589
|
+
before do
|
590
|
+
producer.partition_count('consume_test_topic')
|
591
|
+
allow(::Rdkafka::Metadata).to receive(:new).and_call_original
|
592
|
+
end
|
593
|
+
|
594
|
+
it 'expect not to query it again' do
|
595
|
+
producer.partition_count('consume_test_topic')
|
596
|
+
expect(::Rdkafka::Metadata).not_to have_received(:new)
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
context 'when the partition count value was cached but time expired' do
|
601
|
+
before do
|
602
|
+
allow(::Process).to receive(:clock_gettime).and_return(0, 30.02)
|
603
|
+
producer.partition_count('consume_test_topic')
|
604
|
+
allow(::Rdkafka::Metadata).to receive(:new).and_call_original
|
605
|
+
end
|
606
|
+
|
607
|
+
it 'expect not to query it again' do
|
608
|
+
producer.partition_count('consume_test_topic')
|
609
|
+
expect(::Rdkafka::Metadata).to have_received(:new)
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
context 'when the partition count value was cached and time did not expire' do
|
614
|
+
before do
|
615
|
+
allow(::Process).to receive(:clock_gettime).and_return(0, 29.001)
|
616
|
+
producer.partition_count('consume_test_topic')
|
617
|
+
allow(::Rdkafka::Metadata).to receive(:new).and_call_original
|
618
|
+
end
|
619
|
+
|
620
|
+
it 'expect not to query it again' do
|
621
|
+
producer.partition_count('consume_test_topic')
|
622
|
+
expect(::Rdkafka::Metadata).not_to have_received(:new)
|
623
|
+
end
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
describe '#flush' do
|
628
|
+
it "should return flush when it can flush all outstanding messages or when no messages" do
|
629
|
+
producer.produce(
|
630
|
+
topic: "produce_test_topic",
|
631
|
+
payload: "payload headers",
|
632
|
+
key: "key headers",
|
633
|
+
headers: {}
|
634
|
+
)
|
635
|
+
|
636
|
+
expect(producer.flush(5_000)).to eq(true)
|
637
|
+
end
|
638
|
+
|
639
|
+
context 'when it cannot flush due to a timeout' do
|
640
|
+
let(:producer) do
|
641
|
+
rdkafka_producer_config(
|
642
|
+
"bootstrap.servers": "localhost:9093",
|
643
|
+
"message.timeout.ms": 2_000
|
644
|
+
).producer
|
645
|
+
end
|
646
|
+
|
647
|
+
after do
|
648
|
+
# Allow rdkafka to evict message preventing memory-leak
|
649
|
+
sleep(2)
|
650
|
+
end
|
651
|
+
|
652
|
+
it "should return false on flush when cannot deliver and beyond timeout" do
|
653
|
+
producer.produce(
|
654
|
+
topic: "produce_test_topic",
|
655
|
+
payload: "payload headers",
|
656
|
+
key: "key headers",
|
657
|
+
headers: {}
|
658
|
+
)
|
659
|
+
|
660
|
+
expect(producer.flush(1_000)).to eq(false)
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
context 'when there is a different error' do
|
665
|
+
before { allow(Rdkafka::Bindings).to receive(:rd_kafka_flush).and_return(-199) }
|
666
|
+
|
667
|
+
it 'should raise it' do
|
668
|
+
expect { producer.flush }.to raise_error(Rdkafka::RdkafkaError)
|
669
|
+
end
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
describe '#purge' do
|
674
|
+
context 'when no outgoing messages' do
|
675
|
+
it { expect(producer.purge).to eq(true) }
|
676
|
+
end
|
677
|
+
|
678
|
+
context 'when librdkafka purge returns an error' do
|
679
|
+
before { expect(Rdkafka::Bindings).to receive(:rd_kafka_purge).and_return(-153) }
|
680
|
+
|
681
|
+
it 'expect to raise an error' do
|
682
|
+
expect { producer.purge }.to raise_error(Rdkafka::RdkafkaError, /retry/)
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
context 'when there are outgoing things in the queue' do
|
687
|
+
let(:producer) do
|
688
|
+
rdkafka_producer_config(
|
689
|
+
"bootstrap.servers": "localhost:9093",
|
690
|
+
"message.timeout.ms": 2_000
|
691
|
+
).producer
|
692
|
+
end
|
693
|
+
|
694
|
+
it "should should purge and move forward" do
|
695
|
+
producer.produce(
|
696
|
+
topic: "produce_test_topic",
|
697
|
+
payload: "payload headers"
|
698
|
+
)
|
699
|
+
|
700
|
+
expect(producer.purge).to eq(true)
|
701
|
+
expect(producer.flush(1_000)).to eq(true)
|
702
|
+
end
|
703
|
+
|
704
|
+
it "should materialize the delivery handles" do
|
705
|
+
handle = producer.produce(
|
706
|
+
topic: "produce_test_topic",
|
707
|
+
payload: "payload headers"
|
708
|
+
)
|
709
|
+
|
710
|
+
expect(producer.purge).to eq(true)
|
711
|
+
|
712
|
+
expect { handle.wait }.to raise_error(Rdkafka::RdkafkaError, /purge_queue/)
|
713
|
+
end
|
714
|
+
|
715
|
+
context "when using delivery_callback" do
|
716
|
+
let(:delivery_reports) { [] }
|
717
|
+
|
718
|
+
let(:delivery_callback) do
|
719
|
+
->(delivery_report) { delivery_reports << delivery_report }
|
720
|
+
end
|
721
|
+
|
722
|
+
before { producer.delivery_callback = delivery_callback }
|
723
|
+
|
724
|
+
it "should run the callback" do
|
725
|
+
handle = producer.produce(
|
726
|
+
topic: "produce_test_topic",
|
727
|
+
payload: "payload headers"
|
728
|
+
)
|
729
|
+
|
730
|
+
expect(producer.purge).to eq(true)
|
731
|
+
# queue purge
|
732
|
+
expect(delivery_reports[0].error).to eq(-152)
|
733
|
+
end
|
734
|
+
end
|
735
|
+
end
|
736
|
+
end
|
525
737
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
unless ENV["CI"] == "true"
|
2
4
|
require "simplecov"
|
3
5
|
SimpleCov.start do
|
@@ -9,6 +11,7 @@ require "pry"
|
|
9
11
|
require "rspec"
|
10
12
|
require "rdkafka"
|
11
13
|
require "timeout"
|
14
|
+
require "securerandom"
|
12
15
|
|
13
16
|
def rdkafka_base_config
|
14
17
|
{
|
@@ -33,7 +36,7 @@ def rdkafka_consumer_config(config_overrides={})
|
|
33
36
|
# Add consumer specific fields to it
|
34
37
|
config[:"auto.offset.reset"] = "earliest"
|
35
38
|
config[:"enable.partition.eof"] = false
|
36
|
-
config[:"group.id"] = "ruby-test-#{
|
39
|
+
config[:"group.id"] = "ruby-test-#{SecureRandom.uuid}"
|
37
40
|
# Enable debug mode if required
|
38
41
|
if ENV["DEBUG_CONSUMER"]
|
39
42
|
config[:debug] = "cgrp,topic,fetch"
|
@@ -71,7 +74,7 @@ def new_native_topic(topic_name="topic_name", native_client: )
|
|
71
74
|
end
|
72
75
|
|
73
76
|
def wait_for_message(topic:, delivery_report:, timeout_in_seconds: 30, consumer: nil)
|
74
|
-
new_consumer =
|
77
|
+
new_consumer = consumer.nil?
|
75
78
|
consumer ||= rdkafka_consumer_config.consumer
|
76
79
|
consumer.subscribe(topic)
|
77
80
|
timeout = Time.now.to_i + timeout_in_seconds
|
@@ -104,6 +107,20 @@ def wait_for_unassignment(consumer)
|
|
104
107
|
end
|
105
108
|
end
|
106
109
|
|
110
|
+
def notify_listener(listener, &block)
|
111
|
+
# 1. subscribe and poll
|
112
|
+
consumer.subscribe("consume_test_topic")
|
113
|
+
wait_for_assignment(consumer)
|
114
|
+
consumer.poll(100)
|
115
|
+
|
116
|
+
block.call if block
|
117
|
+
|
118
|
+
# 2. unsubscribe
|
119
|
+
consumer.unsubscribe
|
120
|
+
wait_for_unassignment(consumer)
|
121
|
+
consumer.close
|
122
|
+
end
|
123
|
+
|
107
124
|
RSpec.configure do |config|
|
108
125
|
config.filter_run focus: true
|
109
126
|
config.run_all_when_everything_filtered = true
|
@@ -118,6 +135,7 @@ RSpec.configure do |config|
|
|
118
135
|
rake_test_topic: 3,
|
119
136
|
watermarks_test_topic: 3,
|
120
137
|
partitioner_test_topic: 25,
|
138
|
+
example_topic: 1
|
121
139
|
}.each do |topic, partitions|
|
122
140
|
create_topic_handle = admin.create_topic(topic.to_s, partitions, 1)
|
123
141
|
begin
|
data.tar.gz.sig
ADDED
Binary file
|