77 "encoding/json"
88 "errors"
99 "fmt"
10+ "strconv"
1011 "strings"
12+ "sync/atomic"
1113 "testing"
1214 "time"
1315
@@ -44,15 +46,26 @@ func (m *MockLogger) Log(_ centrifuge.LogEntry) {
4446 // Implement mock logic, e.g., storing log entries for assertions
4547}
4648
49+ func produceManyRecords (records ... * kgo.Record ) error {
50+ client , err := kgo .NewClient (kgo .SeedBrokers (testKafkaBrokerURL ))
51+ if err != nil {
52+ return fmt .Errorf ("failed to create Kafka client: %w" , err )
53+ }
54+ defer client .Close ()
55+ err = client .ProduceSync (context .Background (), records ... ).FirstErr ()
56+ if err != nil {
57+ return fmt .Errorf ("failed to produce message: %w" , err )
58+ }
59+ return nil
60+ }
61+
4762func produceTestMessage (topic string , message []byte ) error {
48- // Create a new client
4963 client , err := kgo .NewClient (kgo .SeedBrokers (testKafkaBrokerURL ))
5064 if err != nil {
5165 return fmt .Errorf ("failed to create Kafka client: %w" , err )
5266 }
5367 defer client .Close ()
5468
55- // Produce a message
5669 err = client .ProduceSync (context .Background (), & kgo.Record {Topic : topic , Partition : 0 , Value : message }).FirstErr ()
5770 if err != nil {
5871 return fmt .Errorf ("failed to produce message: %w" , err )
@@ -61,7 +74,6 @@ func produceTestMessage(topic string, message []byte) error {
6174}
6275
6376func produceTestMessageToPartition (topic string , message []byte , partition int32 ) error {
64- // Create a new client.
6577 client , err := kgo .NewClient (
6678 kgo .SeedBrokers (testKafkaBrokerURL ),
6779 kgo .RecordPartitioner (kgo .ManualPartitioner ()),
@@ -71,7 +83,6 @@ func produceTestMessageToPartition(topic string, message []byte, partition int32
7183 }
7284 defer client .Close ()
7385
74- // Produce a message until we hit desired partition.
7586 res := client .ProduceSync (context .Background (), & kgo.Record {
7687 Topic : topic , Partition : partition , Value : message })
7788 if res .FirstErr () != nil {
@@ -427,3 +438,171 @@ func TestKafkaConsumer_BlockedPartitionDoesNotBlockAnotherPartition(t *testing.T
427438 })
428439 }
429440}
441+
442+ func TestKafkaConsumer_PausePartitions (t * testing.T ) {
443+ t .Parallel ()
444+ testKafkaTopic := "consumer_test_" + uuid .New ().String ()
445+ testPayload1 := []byte (`{"key":"value1"}` )
446+ testPayload2 := []byte (`{"key":"value2"}` )
447+ testPayload3 := []byte (`{"key":"value3"}` )
448+
449+ ctx , cancel := context .WithTimeout (context .Background (), 30 * time .Second )
450+ defer cancel ()
451+
452+ err := createTestTopic (ctx , testKafkaTopic , 1 , 1 )
453+ require .NoError (t , err )
454+
455+ event1Received := make (chan struct {})
456+ event2Received := make (chan struct {})
457+ event3Received := make (chan struct {})
458+ consumerClosed := make (chan struct {})
459+ doneCh := make (chan struct {})
460+
461+ config := KafkaConfig {
462+ Brokers : []string {testKafkaBrokerURL },
463+ Topics : []string {testKafkaTopic },
464+ ConsumerGroup : uuid .New ().String (),
465+ PartitionBufferSize : - 1 ,
466+ }
467+
468+ numCalls := 0
469+
470+ mockDispatcher := & MockDispatcher {
471+ onDispatch : func (ctx context.Context , method string , data []byte ) error {
472+ numCalls ++
473+ if numCalls == 1 {
474+ close (event1Received )
475+ time .Sleep (5 * time .Second )
476+ return nil
477+ } else if numCalls == 2 {
478+ close (event2Received )
479+ return nil
480+ }
481+ close (event3Received )
482+ return nil
483+ },
484+ }
485+ consumer , err := NewKafkaConsumer ("test" , uuid .NewString (), & MockLogger {}, mockDispatcher , config )
486+ require .NoError (t , err )
487+
488+ go func () {
489+ err = produceTestMessage (testKafkaTopic , testPayload1 )
490+ require .NoError (t , err )
491+ <- event1Received
492+ // At this point message 1 is being processed and the next produced message will
493+ // cause a partition pause.
494+ err = produceTestMessage (testKafkaTopic , testPayload2 )
495+ require .NoError (t , err )
496+ <- event2Received
497+ err = produceTestMessage (testKafkaTopic , testPayload3 )
498+ require .NoError (t , err )
499+ }()
500+
501+ go func () {
502+ err := consumer .Run (ctx )
503+ require .ErrorIs (t , err , context .Canceled )
504+ close (consumerClosed )
505+ }()
506+
507+ waitCh (t , event1Received , 30 * time .Second , "timeout waiting for event 1" )
508+ waitCh (t , event2Received , 30 * time .Second , "timeout waiting for event 2" )
509+ waitCh (t , event3Received , 30 * time .Second , "timeout waiting for event 3" )
510+ cancel ()
511+ waitCh (t , consumerClosed , 30 * time .Second , "timeout waiting for consumer closed" )
512+ close (doneCh )
513+ }
514+
515+ func TestKafkaConsumer_WorksCorrectlyInLoadedTopic (t * testing.T ) {
516+ t .Skip ()
517+ t .Parallel ()
518+
519+ testCases := []struct {
520+ numPartitions int32
521+ numMessages int
522+ partitionBuffer int
523+ }{
524+ //{numPartitions: 1, numMessages: 1000, partitionBuffer: -1},
525+ //{numPartitions: 1, numMessages: 1000, partitionBuffer: 1},
526+ //{numPartitions: 10, numMessages: 10000, partitionBuffer: -1},
527+ {numPartitions : 10 , numMessages : 10000 , partitionBuffer : 1 },
528+ }
529+
530+ for _ , tc := range testCases {
531+ name := fmt .Sprintf ("partitions=%d,messages=%d,buffer=%d" , tc .numPartitions , tc .numMessages , tc .partitionBuffer )
532+ t .Run (name , func (t * testing.T ) {
533+ testKafkaTopic := "consumer_test_" + uuid .New ().String ()
534+
535+ ctx , cancel := context .WithTimeout (context .Background (), 300 * time .Second )
536+ defer cancel ()
537+
538+ err := createTestTopic (ctx , testKafkaTopic , tc .numPartitions , 1 )
539+ require .NoError (t , err )
540+
541+ consumerClosed := make (chan struct {})
542+ doneCh := make (chan struct {})
543+
544+ numMessages := tc .numMessages
545+ messageCh := make (chan struct {}, numMessages )
546+
547+ mockDispatcher := & MockDispatcher {
548+ onDispatch : func (ctx context.Context , method string , data []byte ) error {
549+ // Emulate delay due to some work.
550+ time .Sleep (20 * time .Millisecond )
551+ messageCh <- struct {}{}
552+ return nil
553+ },
554+ }
555+ config := KafkaConfig {
556+ Brokers : []string {testKafkaBrokerURL },
557+ Topics : []string {testKafkaTopic },
558+ ConsumerGroup : uuid .New ().String (),
559+ PartitionBufferSize : tc .partitionBuffer ,
560+ }
561+ consumer , err := NewKafkaConsumer ("test" , uuid .NewString (), & MockLogger {}, mockDispatcher , config )
562+ require .NoError (t , err )
563+
564+ var records []* kgo.Record
565+ for i := 0 ; i < numMessages ; i ++ {
566+ records = append (records , & kgo.Record {Topic : testKafkaTopic , Value : []byte (`{"hello": "` + strconv .Itoa (i ) + `"}` )})
567+ if (i + 1 )% 100 == 0 {
568+ err = produceManyRecords (records ... )
569+ if err != nil {
570+ t .Fatal (err )
571+ }
572+ records = nil
573+ t .Logf ("produced %d messages" , i + 1 )
574+ }
575+ }
576+
577+ t .Logf ("all messages produced, 3, 2, 1, go!" )
578+ time .Sleep (time .Second )
579+
580+ go func () {
581+ err := consumer .Run (ctx )
582+ require .ErrorIs (t , err , context .Canceled )
583+ close (consumerClosed )
584+ }()
585+
586+ var numProcessed int64
587+ go func () {
588+ for {
589+ select {
590+ case <- ctx .Done ():
591+ return
592+ case <- time .After (time .Second ):
593+ t .Logf ("processed %d messages" , atomic .LoadInt64 (& numProcessed ))
594+ }
595+ }
596+ }()
597+
598+ for i := 0 ; i < numMessages ; i ++ {
599+ <- messageCh
600+ atomic .AddInt64 (& numProcessed , 1 )
601+ }
602+ t .Logf ("all messages processed" )
603+ cancel ()
604+ waitCh (t , consumerClosed , 30 * time .Second , "timeout waiting for consumer closed" )
605+ close (doneCh )
606+ })
607+ }
608+ }
0 commit comments