karafka-rdkafka 0.20.0.rc2 → 0.20.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci_linux_x86_64_gnu.yml +249 -0
  3. data/.github/workflows/ci_linux_x86_64_musl.yml +205 -0
  4. data/.github/workflows/ci_macos_arm64.yml +306 -0
  5. data/.github/workflows/push_linux_x86_64_gnu.yml +64 -0
  6. data/.github/workflows/push_linux_x86_64_musl.yml +77 -0
  7. data/.github/workflows/push_macos_arm64.yml +54 -0
  8. data/.github/workflows/push_ruby.yml +37 -0
  9. data/.gitignore +1 -0
  10. data/.ruby-version +1 -1
  11. data/CHANGELOG.md +25 -4
  12. data/README.md +2 -3
  13. data/Rakefile +0 -2
  14. data/dist/{librdkafka-2.10.0.tar.gz → librdkafka-2.8.0.tar.gz} +0 -0
  15. data/docker-compose.yml +1 -1
  16. data/ext/Rakefile +1 -1
  17. data/ext/build_common.sh +361 -0
  18. data/ext/build_linux_x86_64_gnu.sh +306 -0
  19. data/ext/build_linux_x86_64_musl.sh +763 -0
  20. data/ext/build_macos_arm64.sh +550 -0
  21. data/karafka-rdkafka.gemspec +51 -10
  22. data/lib/rdkafka/bindings.rb +32 -6
  23. data/lib/rdkafka/config.rb +4 -1
  24. data/lib/rdkafka/error.rb +8 -1
  25. data/lib/rdkafka/native_kafka.rb +4 -0
  26. data/lib/rdkafka/producer/partitions_count_cache.rb +216 -0
  27. data/lib/rdkafka/producer.rb +51 -34
  28. data/lib/rdkafka/version.rb +3 -3
  29. data/lib/rdkafka.rb +1 -0
  30. data/renovate.json +74 -0
  31. data/spec/rdkafka/admin_spec.rb +217 -3
  32. data/spec/rdkafka/bindings_spec.rb +0 -25
  33. data/spec/rdkafka/config_spec.rb +1 -1
  34. data/spec/rdkafka/consumer_spec.rb +35 -17
  35. data/spec/rdkafka/metadata_spec.rb +2 -2
  36. data/spec/rdkafka/producer/partitions_count_cache_spec.rb +359 -0
  37. data/spec/rdkafka/producer_spec.rb +493 -8
  38. data/spec/spec_helper.rb +32 -7
  39. metadata +37 -95
  40. checksums.yaml.gz.sig +0 -0
  41. data/.github/workflows/ci.yml +0 -99
  42. data/Guardfile +0 -19
  43. data/certs/cert.pem +0 -26
  44. data.tar.gz.sig +0 -0
  45. metadata.gz.sig +0 -3
@@ -53,7 +53,7 @@ describe Rdkafka::Producer do
53
53
  let(:producer) do
54
54
  rdkafka_producer_config(
55
55
  'message.timeout.ms': 1_000_000,
56
- :"bootstrap.servers" => "localhost:9094",
56
+ :"bootstrap.servers" => "127.0.0.1:9094",
57
57
  ).producer
58
58
  end
59
59
 
@@ -263,6 +263,8 @@ describe Rdkafka::Producer do
263
263
  expect(message.partition).to eq 1
264
264
  expect(message.payload).to eq "payload"
265
265
  expect(message.key).to eq "key"
266
+ # Since api.version.request is on by default we will get
267
+ # the message creation timestamp if it's not set.
266
268
  expect(message.timestamp).to be_within(10).of(Time.now)
267
269
  end
268
270
 
@@ -338,7 +340,7 @@ describe Rdkafka::Producer do
338
340
  )
339
341
  end
340
342
 
341
- expect(messages[0].partition).to eq 0
343
+ expect(messages[0].partition).to be >= 0
342
344
  expect(messages[0].key).to eq 'a'
343
345
  end
344
346
 
@@ -362,6 +364,48 @@ describe Rdkafka::Producer do
362
364
  expect(message.key).to eq "key utf8"
363
365
  end
364
366
 
367
+ it "should produce a message to a non-existing topic with key and partition key" do
368
+ new_topic = "it-#{SecureRandom.uuid}"
369
+
370
+ handle = producer.produce(
371
+ # Needs to be a new topic each time
372
+ topic: new_topic,
373
+ payload: "payload",
374
+ key: "key",
375
+ partition_key: "partition_key",
376
+ label: "label"
377
+ )
378
+
379
+ # Should be pending at first
380
+ expect(handle.pending?).to be true
381
+ expect(handle.label).to eq "label"
382
+
383
+ # Check delivery handle and report
384
+ report = handle.wait(max_wait_timeout: 5)
385
+ expect(handle.pending?).to be false
386
+ expect(report).not_to be_nil
387
+ expect(report.partition).to eq 0
388
+ expect(report.offset).to be >= 0
389
+ expect(report.label).to eq "label"
390
+
391
+ # Flush and close producer
392
+ producer.flush
393
+ producer.close
394
+
395
+ # Consume message and verify its content
396
+ message = wait_for_message(
397
+ topic: new_topic,
398
+ delivery_report: report,
399
+ consumer: consumer
400
+ )
401
+ expect(message.partition).to eq 0
402
+ expect(message.payload).to eq "payload"
403
+ expect(message.key).to eq "key"
404
+ # Since api.version.request is on by default we will get
405
+ # the message creation timestamp if it's not set.
406
+ expect(message.timestamp).to be_within(10).of(Time.now)
407
+ end
408
+
365
409
  context "timestamp" do
366
410
  it "should raise a type error if not nil, integer or time" do
367
411
  expect {
@@ -621,7 +665,7 @@ describe Rdkafka::Producer do
621
665
  context "when not being able to deliver the message" do
622
666
  let(:producer) do
623
667
  rdkafka_producer_config(
624
- "bootstrap.servers": "localhost:9093",
668
+ "bootstrap.servers": "127.0.0.1:9093",
625
669
  "message.timeout.ms": 100
626
670
  ).producer
627
671
  end
@@ -635,6 +679,25 @@ describe Rdkafka::Producer do
635
679
  end
636
680
  end
637
681
 
682
+ context "when topic does not exist and allow.auto.create.topics is false" do
683
+ let(:producer) do
684
+ rdkafka_producer_config(
685
+ "bootstrap.servers": "127.0.0.1:9092",
686
+ "message.timeout.ms": 100,
687
+ "allow.auto.create.topics": false
688
+ ).producer
689
+ end
690
+
691
+ it "should contain the error in the response when not deliverable" do
692
+ handler = producer.produce(topic: "it-#{SecureRandom.uuid}", payload: nil, label: 'na')
693
+ # Wait for the async callbacks and delivery registry to update
694
+ sleep(2)
695
+ expect(handler.create_result.error).to be_a(Rdkafka::RdkafkaError)
696
+ expect(handler.create_result.error.code).to eq(:msg_timed_out)
697
+ expect(handler.create_result.label).to eq('na')
698
+ end
699
+ end
700
+
638
701
  describe '#partition_count' do
639
702
  it { expect(producer.partition_count('example_topic')).to eq(1) }
640
703
 
@@ -652,12 +715,11 @@ describe Rdkafka::Producer do
652
715
 
653
716
  context 'when the partition count value was cached but time expired' do
654
717
  before do
655
- allow(::Process).to receive(:clock_gettime).and_return(0, 30.02)
656
- producer.partition_count('example_topic')
718
+ ::Rdkafka::Producer.partitions_count_cache = Rdkafka::Producer::PartitionsCountCache.new
657
719
  allow(::Rdkafka::Metadata).to receive(:new).and_call_original
658
720
  end
659
721
 
660
- it 'expect not to query it again' do
722
+ it 'expect to query it again' do
661
723
  producer.partition_count('example_topic')
662
724
  expect(::Rdkafka::Metadata).to have_received(:new)
663
725
  end
@@ -719,7 +781,7 @@ describe Rdkafka::Producer do
719
781
  context 'when it cannot flush due to a timeout' do
720
782
  let(:producer) do
721
783
  rdkafka_producer_config(
722
- "bootstrap.servers": "localhost:9093",
784
+ "bootstrap.servers": "127.0.0.1:9093",
723
785
  "message.timeout.ms": 2_000
724
786
  ).producer
725
787
  end
@@ -766,7 +828,7 @@ describe Rdkafka::Producer do
766
828
  context 'when there are outgoing things in the queue' do
767
829
  let(:producer) do
768
830
  rdkafka_producer_config(
769
- "bootstrap.servers": "localhost:9093",
831
+ "bootstrap.servers": "127.0.0.1:9093",
770
832
  "message.timeout.ms": 2_000
771
833
  ).producer
772
834
  end
@@ -1040,4 +1102,427 @@ describe Rdkafka::Producer do
1040
1102
  expect(message.headers['version']).to eq('2.1.3')
1041
1103
  end
1042
1104
  end
1105
+
1106
+ describe 'with active statistics callback' do
1107
+ let(:producer) do
1108
+ rdkafka_producer_config('statistics.interval.ms': 1_000).producer
1109
+ end
1110
+
1111
+ let(:count_cache_hash) { described_class.partitions_count_cache.to_h }
1112
+ let(:pre_statistics_ttl) { count_cache_hash.fetch('produce_test_topic', [])[0] }
1113
+ let(:post_statistics_ttl) { count_cache_hash.fetch('produce_test_topic', [])[0] }
1114
+
1115
+ context "when using partition key" do
1116
+ before do
1117
+ Rdkafka::Config.statistics_callback = ->(*) {}
1118
+
1119
+ # This call will make a blocking request to the metadata cache
1120
+ producer.produce(
1121
+ topic: "produce_test_topic",
1122
+ payload: "payload headers",
1123
+ partition_key: "test"
1124
+ ).wait
1125
+
1126
+ pre_statistics_ttl
1127
+
1128
+ # We wait to make sure that statistics are triggered and that there is a refresh
1129
+ sleep(1.5)
1130
+
1131
+ post_statistics_ttl
1132
+ end
1133
+
1134
+ it 'expect to update ttl on the partitions count cache via statistics' do
1135
+ expect(pre_statistics_ttl).to be < post_statistics_ttl
1136
+ end
1137
+ end
1138
+
1139
+ context "when not using partition key" do
1140
+ before do
1141
+ Rdkafka::Config.statistics_callback = ->(*) {}
1142
+
1143
+ # This call will make a blocking request to the metadata cache
1144
+ producer.produce(
1145
+ topic: "produce_test_topic",
1146
+ payload: "payload headers"
1147
+ ).wait
1148
+
1149
+ pre_statistics_ttl
1150
+
1151
+ # We wait to make sure that statistics are triggered and that there is a refresh
1152
+ sleep(1.5)
1153
+
1154
+ # This will anyhow be populated from statistic
1155
+ post_statistics_ttl
1156
+ end
1157
+
1158
+ it 'expect not to update ttl on the partitions count cache via blocking but via use stats' do
1159
+ expect(pre_statistics_ttl).to be_nil
1160
+ expect(post_statistics_ttl).not_to be_nil
1161
+ end
1162
+ end
1163
+ end
1164
+
1165
+ describe 'without active statistics callback' do
1166
+ let(:producer) do
1167
+ rdkafka_producer_config('statistics.interval.ms': 1_000).producer
1168
+ end
1169
+
1170
+ let(:count_cache_hash) { described_class.partitions_count_cache.to_h }
1171
+ let(:pre_statistics_ttl) { count_cache_hash.fetch('produce_test_topic', [])[0] }
1172
+ let(:post_statistics_ttl) { count_cache_hash.fetch('produce_test_topic', [])[0] }
1173
+
1174
+ context "when using partition key" do
1175
+ before do
1176
+ # This call will make a blocking request to the metadata cache
1177
+ producer.produce(
1178
+ topic: "produce_test_topic",
1179
+ payload: "payload headers",
1180
+ partition_key: "test"
1181
+ ).wait
1182
+
1183
+ pre_statistics_ttl
1184
+
1185
+ # We wait to make sure that statistics are triggered and that there is a refresh
1186
+ sleep(1.5)
1187
+
1188
+ post_statistics_ttl
1189
+ end
1190
+
1191
+ it 'expect not to update ttl on the partitions count cache via statistics' do
1192
+ expect(pre_statistics_ttl).to eq post_statistics_ttl
1193
+ end
1194
+ end
1195
+
1196
+ context "when not using partition key" do
1197
+ before do
1198
+ # This call will make a blocking request to the metadata cache
1199
+ producer.produce(
1200
+ topic: "produce_test_topic",
1201
+ payload: "payload headers"
1202
+ ).wait
1203
+
1204
+ pre_statistics_ttl
1205
+
1206
+ # We wait to make sure that statistics are triggered and that there is a refresh
1207
+ sleep(1.5)
1208
+
1209
+ # This should not be populated because stats are not in use
1210
+ post_statistics_ttl
1211
+ end
1212
+
1213
+ it 'expect not to update ttl on the partitions count cache via anything' do
1214
+ expect(pre_statistics_ttl).to be_nil
1215
+ expect(post_statistics_ttl).to be_nil
1216
+ end
1217
+ end
1218
+ end
1219
+
1220
+ describe 'with other fiber closing' do
1221
+ context 'when we create many fibers and close producer in some of them' do
1222
+ it 'expect not to crash ruby' do
1223
+ 10.times do |i|
1224
+ producer = rdkafka_producer_config.producer
1225
+
1226
+ Fiber.new do
1227
+ GC.start
1228
+ producer.close
1229
+ end.resume
1230
+ end
1231
+ end
1232
+ end
1233
+ end
1234
+
1235
+ let(:producer) { rdkafka_producer_config.producer }
1236
+ let(:all_partitioners) { %w(random consistent consistent_random murmur2 murmur2_random fnv1a fnv1a_random) }
1237
+
1238
+ describe "partitioner behavior through producer API" do
1239
+ context "testing all partitioners with same key" do
1240
+ it "should not return partition 0 for all partitioners" do
1241
+ test_key = "test-key-123"
1242
+ results = {}
1243
+
1244
+ all_partitioners.each do |partitioner|
1245
+ handle = producer.produce(
1246
+ topic: "partitioner_test_topic",
1247
+ payload: "test payload",
1248
+ partition_key: test_key,
1249
+ partitioner: partitioner
1250
+ )
1251
+
1252
+ report = handle.wait(max_wait_timeout: 5)
1253
+ results[partitioner] = report.partition
1254
+ end
1255
+
1256
+ # Should not all be the same partition (especially not all 0)
1257
+ unique_partitions = results.values.uniq
1258
+ expect(unique_partitions.size).to be > 1
1259
+ end
1260
+ end
1261
+
1262
+ context "empty string partition key" do
1263
+ it "should produce message with empty partition key without crashing and go to partition 0 for all partitioners" do
1264
+ all_partitioners.each do |partitioner|
1265
+ handle = producer.produce(
1266
+ topic: "partitioner_test_topic",
1267
+ payload: "test payload",
1268
+ key: "test-key",
1269
+ partition_key: "",
1270
+ partitioner: partitioner
1271
+ )
1272
+
1273
+ report = handle.wait(max_wait_timeout: 5)
1274
+ expect(report.partition).to be >= 0
1275
+ end
1276
+ end
1277
+ end
1278
+
1279
+ context "nil partition key" do
1280
+ it "should handle nil partition key gracefully" do
1281
+ handle = producer.produce(
1282
+ topic: "partitioner_test_topic",
1283
+ payload: "test payload",
1284
+ key: "test-key",
1285
+ partition_key: nil
1286
+ )
1287
+
1288
+ report = handle.wait(max_wait_timeout: 5)
1289
+ expect(report.partition).to be >= 0
1290
+ expect(report.partition).to be < producer.partition_count("partitioner_test_topic")
1291
+ end
1292
+ end
1293
+
1294
+ context "various key types and lengths with different partitioners" do
1295
+ it "should handle very short keys with all partitioners" do
1296
+ all_partitioners.each do |partitioner|
1297
+ handle = producer.produce(
1298
+ topic: "partitioner_test_topic",
1299
+ payload: "test payload",
1300
+ partition_key: "a",
1301
+ partitioner: partitioner
1302
+ )
1303
+
1304
+ report = handle.wait(max_wait_timeout: 5)
1305
+ expect(report.partition).to be >= 0
1306
+ expect(report.partition).to be < producer.partition_count("partitioner_test_topic")
1307
+ end
1308
+ end
1309
+
1310
+ it "should handle very long keys with all partitioners" do
1311
+ long_key = "a" * 1000
1312
+
1313
+ all_partitioners.each do |partitioner|
1314
+ handle = producer.produce(
1315
+ topic: "partitioner_test_topic",
1316
+ payload: "test payload",
1317
+ partition_key: long_key,
1318
+ partitioner: partitioner
1319
+ )
1320
+
1321
+ report = handle.wait(max_wait_timeout: 5)
1322
+ expect(report.partition).to be >= 0
1323
+ expect(report.partition).to be < producer.partition_count("partitioner_test_topic")
1324
+ end
1325
+ end
1326
+
1327
+ it "should handle unicode keys with all partitioners" do
1328
+ unicode_key = "测试键值🚀"
1329
+
1330
+ all_partitioners.each do |partitioner|
1331
+ handle = producer.produce(
1332
+ topic: "partitioner_test_topic",
1333
+ payload: "test payload",
1334
+ partition_key: unicode_key,
1335
+ partitioner: partitioner
1336
+ )
1337
+
1338
+ report = handle.wait(max_wait_timeout: 5)
1339
+ expect(report.partition).to be >= 0
1340
+ expect(report.partition).to be < producer.partition_count("partitioner_test_topic")
1341
+ end
1342
+ end
1343
+ end
1344
+
1345
+ context "consistency testing for deterministic partitioners" do
1346
+ %w(consistent murmur2 fnv1a).each do |partitioner|
1347
+ it "should consistently route same partition key to same partition with #{partitioner}" do
1348
+ partition_key = "consistent-test-key"
1349
+
1350
+ # Produce multiple messages with same partition key
1351
+ reports = 5.times.map do
1352
+ handle = producer.produce(
1353
+ topic: "partitioner_test_topic",
1354
+ payload: "test payload #{Time.now.to_f}",
1355
+ partition_key: partition_key,
1356
+ partitioner: partitioner
1357
+ )
1358
+ handle.wait(max_wait_timeout: 5)
1359
+ end
1360
+
1361
+ # All should go to same partition
1362
+ partitions = reports.map(&:partition).uniq
1363
+ expect(partitions.size).to eq(1)
1364
+ end
1365
+ end
1366
+ end
1367
+
1368
+ context "randomness testing for random partitioners" do
1369
+ %w(random consistent_random murmur2_random fnv1a_random).each do |partitioner|
1370
+ it "should potentially distribute across partitions with #{partitioner}" do
1371
+ # Note: random partitioners might still return same value by chance
1372
+ partition_key = "random-test-key"
1373
+
1374
+ reports = 10.times.map do
1375
+ handle = producer.produce(
1376
+ topic: "partitioner_test_topic",
1377
+ payload: "test payload #{Time.now.to_f}",
1378
+ partition_key: partition_key,
1379
+ partitioner: partitioner
1380
+ )
1381
+ handle.wait(max_wait_timeout: 5)
1382
+ end
1383
+
1384
+ partitions = reports.map(&:partition)
1385
+
1386
+ # Just ensure they're valid partitions
1387
+ partitions.each do |partition|
1388
+ expect(partition).to be >= 0
1389
+ expect(partition).to be < producer.partition_count("partitioner_test_topic")
1390
+ end
1391
+ end
1392
+ end
1393
+ end
1394
+
1395
+ context "comparing different partitioners with same key" do
1396
+ it "should route different partition keys to potentially different partitions" do
1397
+ keys = ["key1", "key2", "key3", "key4", "key5"]
1398
+
1399
+ all_partitioners.each do |partitioner|
1400
+ reports = keys.map do |key|
1401
+ handle = producer.produce(
1402
+ topic: "partitioner_test_topic",
1403
+ payload: "test payload",
1404
+ partition_key: key,
1405
+ partitioner: partitioner
1406
+ )
1407
+ handle.wait(max_wait_timeout: 5)
1408
+ end
1409
+
1410
+ partitions = reports.map(&:partition).uniq
1411
+
1412
+ # Should distribute across multiple partitions for most partitioners
1413
+ # (though some might hash all keys to same partition by chance)
1414
+ expect(partitions.all? { |p| p >= 0 && p < producer.partition_count("partitioner_test_topic") }).to be true
1415
+ end
1416
+ end
1417
+ end
1418
+
1419
+ context "partition key vs regular key behavior" do
1420
+ it "should use partition key for partitioning when both key and partition_key are provided" do
1421
+ # Use keys that would hash to different partitions
1422
+ regular_key = "regular-key-123"
1423
+ partition_key = "partition-key-456"
1424
+
1425
+ # Message with both keys
1426
+ handle1 = producer.produce(
1427
+ topic: "partitioner_test_topic",
1428
+ payload: "test payload 1",
1429
+ key: regular_key,
1430
+ partition_key: partition_key
1431
+ )
1432
+
1433
+ # Message with only partition key (should go to same partition)
1434
+ handle2 = producer.produce(
1435
+ topic: "partitioner_test_topic",
1436
+ payload: "test payload 2",
1437
+ partition_key: partition_key
1438
+ )
1439
+
1440
+ # Message with only regular key (should go to different partition)
1441
+ handle3 = producer.produce(
1442
+ topic: "partitioner_test_topic",
1443
+ payload: "test payload 3",
1444
+ key: regular_key
1445
+ )
1446
+
1447
+ report1 = handle1.wait(max_wait_timeout: 5)
1448
+ report2 = handle2.wait(max_wait_timeout: 5)
1449
+ report3 = handle3.wait(max_wait_timeout: 5)
1450
+
1451
+ # Messages 1 and 2 should go to same partition (both use partition_key)
1452
+ expect(report1.partition).to eq(report2.partition)
1453
+
1454
+ # Message 3 should potentially go to different partition (uses regular key)
1455
+ expect(report3.partition).not_to eq(report1.partition)
1456
+ end
1457
+ end
1458
+
1459
+ context "edge case combinations with different partitioners" do
1460
+ it "should handle nil partition key with all partitioners" do
1461
+ all_partitioners.each do |partitioner|
1462
+ handle = producer.produce(
1463
+ topic: "partitioner_test_topic",
1464
+ payload: "test payload",
1465
+ key: "test-key",
1466
+ partition_key: nil,
1467
+ partitioner: partitioner
1468
+ )
1469
+
1470
+ report = handle.wait(max_wait_timeout: 5)
1471
+ expect(report.partition).to be >= 0
1472
+ expect(report.partition).to be < producer.partition_count("partitioner_test_topic")
1473
+ end
1474
+ end
1475
+
1476
+ it "should handle whitespace-only partition key with all partitioners" do
1477
+ all_partitioners.each do |partitioner|
1478
+ handle = producer.produce(
1479
+ topic: "partitioner_test_topic",
1480
+ payload: "test payload",
1481
+ partition_key: " ",
1482
+ partitioner: partitioner
1483
+ )
1484
+
1485
+ report = handle.wait(max_wait_timeout: 5)
1486
+ expect(report.partition).to be >= 0
1487
+ expect(report.partition).to be < producer.partition_count("partitioner_test_topic")
1488
+ end
1489
+ end
1490
+
1491
+ it "should handle newline characters in partition key with all partitioners" do
1492
+ all_partitioners.each do |partitioner|
1493
+ handle = producer.produce(
1494
+ topic: "partitioner_test_topic",
1495
+ payload: "test payload",
1496
+ partition_key: "key\nwith\nnewlines",
1497
+ partitioner: partitioner
1498
+ )
1499
+
1500
+ report = handle.wait(max_wait_timeout: 5)
1501
+ expect(report.partition).to be >= 0
1502
+ expect(report.partition).to be < producer.partition_count("partitioner_test_topic")
1503
+ end
1504
+ end
1505
+ end
1506
+
1507
+ context "debugging partitioner issues" do
1508
+ it "should show if all partitioners return 0 (indicating a problem)" do
1509
+ test_key = "debug-test-key"
1510
+ zero_count = 0
1511
+
1512
+ all_partitioners.each do |partitioner|
1513
+ handle = producer.produce(
1514
+ topic: "partitioner_test_topic",
1515
+ payload: "debug payload",
1516
+ partition_key: test_key,
1517
+ partitioner: partitioner
1518
+ )
1519
+
1520
+ report = handle.wait(max_wait_timeout: 5)
1521
+ zero_count += 1 if report.partition == 0
1522
+ end
1523
+
1524
+ expect(zero_count).to be < all_partitioners.size
1525
+ end
1526
+ end
1527
+ end
1043
1528
  end
data/spec/spec_helper.rb CHANGED
@@ -15,7 +15,12 @@ require "securerandom"
15
15
 
16
16
  def rdkafka_base_config
17
17
  {
18
- :"bootstrap.servers" => "localhost:9092"
18
+ :"api.version.request" => false,
19
+ :"broker.version.fallback" => "1.0",
20
+ :"bootstrap.servers" => "127.0.0.1:9092",
21
+ # Display statistics and refresh often just to cover those in specs
22
+ :'statistics.interval.ms' => 1_000,
23
+ :'topic.metadata.refresh.interval.ms' => 1_000
19
24
  }
20
25
  end
21
26
 
@@ -73,18 +78,32 @@ end
73
78
 
74
79
  def wait_for_message(topic:, delivery_report:, timeout_in_seconds: 30, consumer: nil)
75
80
  new_consumer = consumer.nil?
76
- consumer ||= rdkafka_consumer_config.consumer
81
+ consumer ||= rdkafka_consumer_config('allow.auto.create.topics': true).consumer
77
82
  consumer.subscribe(topic)
78
83
  timeout = Time.now.to_i + timeout_in_seconds
84
+ retry_count = 0
85
+ max_retries = 10
86
+
79
87
  loop do
80
88
  if timeout <= Time.now.to_i
81
89
  raise "Timeout of #{timeout_in_seconds} seconds reached in wait_for_message"
82
90
  end
83
- message = consumer.poll(100)
84
- if message &&
85
- message.partition == delivery_report.partition &&
86
- message.offset == delivery_report.offset
87
- return message
91
+
92
+ begin
93
+ message = consumer.poll(100)
94
+ if message &&
95
+ message.partition == delivery_report.partition &&
96
+ message.offset == delivery_report.offset
97
+ return message
98
+ end
99
+ rescue Rdkafka::RdkafkaError => e
100
+ if e.code == :unknown_topic_or_part && retry_count < max_retries
101
+ retry_count += 1
102
+ sleep(0.1) # Small delay before retry
103
+ next
104
+ else
105
+ raise
106
+ end
88
107
  end
89
108
  end
90
109
  ensure
@@ -123,6 +142,12 @@ RSpec.configure do |config|
123
142
  config.filter_run focus: true
124
143
  config.run_all_when_everything_filtered = true
125
144
 
145
+ config.before(:each) do
146
+ Rdkafka::Config.statistics_callback = nil
147
+ # We need to clear it so state does not leak between specs
148
+ Rdkafka::Producer.partitions_count_cache.to_h.clear
149
+ end
150
+
126
151
  config.before(:suite) do
127
152
  admin = rdkafka_config.admin
128
153
  {