18
18
*/
19
19
package org .apache .pulsar .client .api ;
20
20
21
+ import static org .assertj .core .api .Assertions .assertThat ;
21
22
import static org .testng .Assert .assertEquals ;
22
23
import static org .testng .Assert .assertFalse ;
23
24
import static org .testng .Assert .assertNull ;
24
25
import static org .testng .Assert .assertTrue ;
25
26
import static org .testng .Assert .fail ;
26
- import java .lang .reflect .Field ;
27
27
import java .util .HashMap ;
28
28
import java .util .HashSet ;
29
29
import java .util .List ;
36
36
import lombok .Data ;
37
37
import org .apache .avro .AvroRuntimeException ;
38
38
import org .apache .avro .reflect .Nullable ;
39
+ import org .apache .pulsar .broker .BrokerTestUtil ;
39
40
import org .apache .pulsar .client .api .schema .GenericRecord ;
40
- import org .apache .pulsar .client .impl .ConsumerImpl ;
41
- import org .apache .pulsar .client .impl .MultiTopicsConsumerImpl ;
42
41
import org .apache .pulsar .client .util .RetryMessageUtil ;
43
- import org .reflections . ReflectionUtils ;
42
+ import org .apache . pulsar . common . policies . data . SchemaCompatibilityStrategy ;
44
43
import org .slf4j .Logger ;
45
44
import org .slf4j .LoggerFactory ;
46
45
import org .testng .annotations .AfterMethod ;
@@ -617,10 +616,12 @@ public void testRetryTopicByCustomTopicName() throws Exception {
617
616
618
617
@ Test (timeOut = 30000L )
619
618
public void testRetryTopicException () throws Exception {
620
- final String topic = "persistent://my-property/my-ns/retry-topic" ;
619
+ String retryLetterTopic = BrokerTestUtil .newUniqueName ("persistent://my-property/my-ns/retry-topic" );
620
+ final String topic = BrokerTestUtil .newUniqueName ("persistent://my-property/my-ns/input-topic" );
621
621
final int maxRedeliveryCount = 2 ;
622
622
final int sendMessages = 1 ;
623
623
// subscribe before publish
624
+ @ Cleanup
624
625
Consumer <byte []> consumer = pulsarClient .newConsumer (Schema .BYTES )
625
626
.topic (topic )
626
627
.subscriptionName ("my-subscription" )
@@ -629,7 +630,7 @@ public void testRetryTopicException() throws Exception {
629
630
.receiverQueueSize (100 )
630
631
.deadLetterPolicy (DeadLetterPolicy .builder ()
631
632
.maxRedeliverCount (maxRedeliveryCount )
632
- .retryLetterTopic ("persistent://my-property/my-ns/my-subscription-custom-Retry" )
633
+ .retryLetterTopic (retryLetterTopic )
633
634
.build ())
634
635
.subscriptionInitialPosition (SubscriptionInitialPosition .Earliest )
635
636
.subscribe ();
@@ -642,30 +643,16 @@ public void testRetryTopicException() throws Exception {
642
643
}
643
644
producer .close ();
644
645
645
- // mock a retry producer exception when reconsumelater is called
646
- MultiTopicsConsumerImpl <byte []> multiTopicsConsumer = (MultiTopicsConsumerImpl <byte []>) consumer ;
647
- List <ConsumerImpl <byte []>> consumers = multiTopicsConsumer .getConsumers ();
648
- for (ConsumerImpl <byte []> c : consumers ) {
649
- Set <Field > deadLetterPolicyField =
650
- ReflectionUtils .getAllFields (c .getClass (), ReflectionUtils .withName ("deadLetterPolicy" ));
651
-
652
- if (deadLetterPolicyField .size () != 0 ) {
653
- Field field = deadLetterPolicyField .iterator ().next ();
654
- field .setAccessible (true );
655
- DeadLetterPolicy deadLetterPolicy = (DeadLetterPolicy ) field .get (c );
656
- deadLetterPolicy .setRetryLetterTopic ("#persistent://invlaid-topic#" );
657
- }
658
- }
646
+ admin .topics ().terminateTopic (retryLetterTopic );
647
+
659
648
Message <byte []> message = consumer .receive ();
660
649
log .info ("consumer received message : {} {}" , message .getMessageId (), new String (message .getData ()));
661
650
try {
662
651
consumer .reconsumeLater (message , 1 , TimeUnit .SECONDS );
663
- } catch (PulsarClientException .InvalidTopicNameException e ) {
664
- assertEquals (e .getClass (), PulsarClientException .InvalidTopicNameException .class );
665
- } catch (Exception e ) {
666
- fail ("exception should be PulsarClientException.InvalidTopicNameException" );
652
+ fail ("exception should be PulsarClientException.TopicTerminatedException" );
653
+ } catch (PulsarClientException .TopicTerminatedException e ) {
654
+ // ok
667
655
}
668
- consumer .close ();
669
656
}
670
657
671
658
@@ -718,10 +705,12 @@ public void testRetryProducerWillCloseByConsumer() throws Exception {
718
705
719
706
@ Test (timeOut = 30000L )
720
707
public void testRetryTopicExceptionWithConcurrent () throws Exception {
721
- final String topic = "persistent://my-property/my-ns/retry-topic" ;
708
+ String retryLetterTopic = BrokerTestUtil .newUniqueName ("persistent://my-property/my-ns/retry-topic" );
709
+ final String topic = BrokerTestUtil .newUniqueName ("persistent://my-property/my-ns/input-topic" );
722
710
final int maxRedeliveryCount = 2 ;
723
711
final int sendMessages = 10 ;
724
712
// subscribe before publish
713
+ @ Cleanup
725
714
Consumer <byte []> consumer = pulsarClient .newConsumer (Schema .BYTES )
726
715
.topic (topic )
727
716
.subscriptionName ("my-subscription" )
@@ -730,7 +719,7 @@ public void testRetryTopicExceptionWithConcurrent() throws Exception {
730
719
.receiverQueueSize (100 )
731
720
.deadLetterPolicy (DeadLetterPolicy .builder ()
732
721
.maxRedeliverCount (maxRedeliveryCount )
733
- .retryLetterTopic ("persistent://my-property/my-ns/my-subscription-custom-Retry" )
722
+ .retryLetterTopic (retryLetterTopic )
734
723
.build ())
735
724
.subscriptionInitialPosition (SubscriptionInitialPosition .Earliest )
736
725
.subscribe ();
@@ -739,24 +728,11 @@ public void testRetryTopicExceptionWithConcurrent() throws Exception {
739
728
.topic (topic )
740
729
.create ();
741
730
for (int i = 0 ; i < sendMessages ; i ++) {
742
- producer .newMessage (). key ( "1" ). value ( String .format ("Hello Pulsar [%d]" , i ).getBytes ()). send ( );
731
+ producer .send ( String .format ("Hello Pulsar [%d]" , i ).getBytes ());
743
732
}
744
733
producer .close ();
745
734
746
- // mock a retry producer exception when reconsumelater is called
747
- MultiTopicsConsumerImpl <byte []> multiTopicsConsumer = (MultiTopicsConsumerImpl <byte []>) consumer ;
748
- List <ConsumerImpl <byte []>> consumers = multiTopicsConsumer .getConsumers ();
749
- for (ConsumerImpl <byte []> c : consumers ) {
750
- Set <Field > deadLetterPolicyField =
751
- ReflectionUtils .getAllFields (c .getClass (), ReflectionUtils .withName ("deadLetterPolicy" ));
752
-
753
- if (deadLetterPolicyField .size () != 0 ) {
754
- Field field = deadLetterPolicyField .iterator ().next ();
755
- field .setAccessible (true );
756
- DeadLetterPolicy deadLetterPolicy = (DeadLetterPolicy ) field .get (c );
757
- deadLetterPolicy .setRetryLetterTopic ("#persistent://invalid-topic#" );
758
- }
759
- }
735
+ admin .topics ().terminateTopic (retryLetterTopic );
760
736
761
737
List <Message <byte []>> messages = Lists .newArrayList ();
762
738
for (int i = 0 ; i < sendMessages ; i ++) {
@@ -769,16 +745,114 @@ public void testRetryTopicExceptionWithConcurrent() throws Exception {
769
745
new Thread (() -> {
770
746
try {
771
747
consumer .reconsumeLater (message , 1 , TimeUnit .SECONDS );
772
- } catch (Exception ignore ) {
773
-
774
- } finally {
748
+ } catch (PulsarClientException .TopicTerminatedException e ) {
749
+ // ok
775
750
latch .countDown ();
751
+ } catch (PulsarClientException e ) {
752
+ // unexpected exception
753
+ fail ("unexpected exception" , e );
776
754
}
777
755
}).start ();
778
756
}
779
757
780
- latch .await ();
758
+ latch .await (sendMessages , TimeUnit . SECONDS );
781
759
consumer .close ();
782
760
}
783
761
762
+ @ Data
763
+ static class Payload {
764
+ String number ;
765
+
766
+ public Payload () {
767
+
768
+ }
769
+
770
+ public Payload (String number ) {
771
+ this .number = number ;
772
+ }
773
+ }
774
+
775
+ @ Data
776
+ static class PayloadIncompatible {
777
+ long number ;
778
+
779
+ public PayloadIncompatible () {
780
+
781
+ }
782
+
783
+ public PayloadIncompatible (long number ) {
784
+ this .number = number ;
785
+ }
786
+ }
787
+
788
+ // reproduce similar issue as reported in https://github.com/apache/pulsar/issues/20635#issuecomment-1709616321
789
+ // but for retry topic
790
+ @ Test
791
+ public void testCloseRetryLetterTopicProducerOnExceptionToPreventProducerLeak () throws Exception {
792
+ String namespace = BrokerTestUtil .newUniqueName ("my-property/my-ns" );
793
+ admin .namespaces ().createNamespace (namespace );
794
+ // don't enforce schema validation
795
+ admin .namespaces ().setSchemaValidationEnforced (namespace , false );
796
+ // set schema compatibility strategy to always compatible
797
+ admin .namespaces ().setSchemaCompatibilityStrategy (namespace , SchemaCompatibilityStrategy .ALWAYS_COMPATIBLE );
798
+
799
+ Schema <Payload > schema = Schema .AVRO (Payload .class );
800
+ Schema <PayloadIncompatible > schemaIncompatible = Schema .AVRO (
801
+ PayloadIncompatible .class );
802
+ String topic = BrokerTestUtil .newUniqueName ("persistent://" + namespace
803
+ + "/testCloseDeadLetterTopicProducerOnExceptionToPreventProducerLeak" );
804
+ String dlqTopic = topic + "-DLQ" ;
805
+ String retryTopic = topic + "-RETRY" ;
806
+
807
+ // create topics
808
+ admin .topics ().createNonPartitionedTopic (topic );
809
+ admin .topics ().createNonPartitionedTopic (dlqTopic );
810
+ admin .topics ().createNonPartitionedTopic (retryTopic );
811
+
812
+ Consumer <Payload > payloadConsumer = null ;
813
+ try {
814
+ payloadConsumer = pulsarClient .newConsumer (schema ).topic (topic )
815
+ .subscriptionType (SubscriptionType .Shared ).subscriptionName ("sub" )
816
+ .ackTimeout (1 , TimeUnit .SECONDS )
817
+ .negativeAckRedeliveryDelay (1 , TimeUnit .MILLISECONDS )
818
+ .enableRetry (true )
819
+ .deadLetterPolicy (DeadLetterPolicy .builder ().retryLetterTopic (retryTopic ).maxRedeliverCount (3 )
820
+ .deadLetterTopic (dlqTopic ).build ())
821
+ .messageListener ((c , msg ) -> {
822
+ try {
823
+ c .reconsumeLater (msg , 1 , TimeUnit .MILLISECONDS );
824
+ } catch (PulsarClientException e ) {
825
+ throw new RuntimeException (e );
826
+ }
827
+ }).subscribe ();
828
+
829
+ // send a message to the topic with the incompatible schema
830
+ PayloadIncompatible payloadIncompatible = new PayloadIncompatible (123 );
831
+ try (Producer <PayloadIncompatible > producer = pulsarClient .newProducer (schemaIncompatible ).topic (topic )
832
+ .create ()) {
833
+ producer .send (payloadIncompatible );
834
+ }
835
+
836
+ Thread .sleep (2000L );
837
+
838
+ assertThat (pulsar .getBrokerService ().getTopicReference (retryTopic ).get ().getProducers ().size ())
839
+ .describedAs ("producer count of retry topic %s should be <= 1 so that it doesn't leak producers" ,
840
+ retryTopic )
841
+ .isLessThanOrEqualTo (1 );
842
+
843
+ } finally {
844
+ if (payloadConsumer != null ) {
845
+ try {
846
+ payloadConsumer .close ();
847
+ } catch (PulsarClientException e ) {
848
+ // ignore
849
+ }
850
+ }
851
+ }
852
+
853
+ assertThat (pulsar .getBrokerService ().getTopicReference (retryTopic ).get ().getProducers ().size ())
854
+ .describedAs ("producer count of retry topic %s should be 0 here" ,
855
+ retryTopic )
856
+ .isEqualTo (0 );
857
+ }
784
858
}
0 commit comments