2020import com .rabbitmq .client .Connection ;
2121import com .rabbitmq .client .ConnectionFactory ;
2222import com .rabbitmq .client .DefaultConsumer ;
23+ import com .rabbitmq .client .DefaultSocketConfigurator ;
2324import com .rabbitmq .client .Envelope ;
2425import com .rabbitmq .client .Recoverable ;
2526import com .rabbitmq .client .RecoveryListener ;
3031import org .junit .Test ;
3132
3233import java .io .IOException ;
34+ import java .net .Socket ;
3335import java .util .HashMap ;
3436import java .util .Map ;
3537import java .util .UUID ;
4547/**
4648 * Test to trigger and check the fix of https://github.com/rabbitmq/rabbitmq-java-client/issues/341.
4749 * Conditions:
48- * - client registers consumer and a call QoS after
49- * - client get many messages and the consumer is slow
50+ * - client registers consumer
51+ * - client get many messages as the consumer is slow
5052 * - the work pool queue is full, the reading thread is stuck
5153 * - more messages come from the network and saturates the TCP buffer
5254 * - the connection dies but the client doesn't detect it
53- * - acks of messages fail
55+ * - sending in the consumer fails
5456 * - connection recovery is never triggered
5557 * <p>
5658 * The fix consists in triggering connection recovery when writing
57- * to the socket fails. As the socket is dead, the closing
58- * sequence can take some time, hence the setup of the shutdown
59- * listener, which avoids waiting for the socket termination by
60- * the OS (can take 15 minutes on linux).
59+ * to the socket fails.
6160 */
6261public class NoAutoRecoveryWhenTcpWindowIsFullTest {
6362
64- private static final int QOS_PREFETCH = 64 ;
65- private static final int NUM_MESSAGES_TO_PRODUCE = 100000 ;
63+ private static final int NUM_MESSAGES_TO_PRODUCE = 50000 ;
6664 private static final int MESSAGE_PROCESSING_TIME_MS = 3000 ;
65+ private static final byte [] MESSAGE_CONTENT = ("MESSAGE CONTENT " + NUM_MESSAGES_TO_PRODUCE ).getBytes ();
6766
68- private ExecutorService dispatchingService ;
69- private ExecutorService producerService ;
70- private ExecutorService shutdownService ;
67+ private ExecutorService executorService ;
7168 private AutorecoveringConnection producingConnection ;
7269 private AutorecoveringChannel producingChannel ;
7370 private AutorecoveringConnection consumingConnection ;
7471 private AutorecoveringChannel consumingChannel ;
7572
76- private CountDownLatch consumerRecoverOkLatch ;
73+ private CountDownLatch consumerOkLatch ;
7774
7875 @ Before
7976 public void setUp () throws Exception {
80- dispatchingService = Executors .newSingleThreadExecutor ();
81- shutdownService = Executors .newSingleThreadExecutor ();
82- producerService = Executors .newSingleThreadExecutor ();
77+ // we need several threads to publish, dispatch deliveries, handle RPC responses, etc.
78+ executorService = Executors .newFixedThreadPool (10 );
8379 final ConnectionFactory factory = TestUtils .connectionFactory ();
80+ factory .setSocketConfigurator (new DefaultSocketConfigurator () {
81+
82+ int DEFAULT_RECEIVE_BUFFER_SIZE = 43690 ;
83+
84+ @ Override
85+ public void configure (Socket socket ) throws IOException {
86+ super .configure (socket );
87+ socket .setReceiveBufferSize (DEFAULT_RECEIVE_BUFFER_SIZE );
88+ }
89+ });
8490 factory .setAutomaticRecoveryEnabled (true );
8591 factory .setTopologyRecoveryEnabled (true );
8692 // we try to set the lower values for closing timeouts, etc.
8793 // this makes the test execute faster.
88- factory .setShutdownExecutor (shutdownService );
89- factory .setShutdownTimeout (10000 );
9094 factory .setRequestedHeartbeat (5 );
91- factory .setSharedExecutor (dispatchingService );
92- factory .setNetworkRecoveryInterval (1000 );
95+ factory .setSharedExecutor (executorService );
96+ // we need the shutdown executor: channel shutting down depends on the work pool,
97+ // which is full. Channel shutting down will time out with the shutdown executor
98+ factory .setShutdownExecutor (executorService );
99+ factory .setNetworkRecoveryInterval (2000 );
93100
94101 producingConnection = (AutorecoveringConnection ) factory .newConnection ("Producer Connection" );
95102 producingChannel = (AutorecoveringChannel ) producingConnection .createChannel ();
96103 consumingConnection = (AutorecoveringConnection ) factory .newConnection ("Consuming Connection" );
97104 consumingChannel = (AutorecoveringChannel ) consumingConnection .createChannel ();
98105
99- consumerRecoverOkLatch = new CountDownLatch (1 );
106+ consumerOkLatch = new CountDownLatch (2 );
100107 }
101108
102109 @ After
103110 public void tearDown () throws IOException {
104111 closeConnectionIfOpen (consumingConnection );
105112 closeConnectionIfOpen (producingConnection );
106113
107- dispatchingService .shutdownNow ();
108- producerService .shutdownNow ();
109- shutdownService .shutdownNow ();
114+ executorService .shutdownNow ();
110115 }
111116
112117 @ Test
@@ -116,17 +121,17 @@ public void failureAndRecovery() throws IOException, InterruptedException {
116121 }
117122 final String queue = UUID .randomUUID ().toString ();
118123
119- final CountDownLatch latch = new CountDownLatch (1 );
124+ final CountDownLatch recoveryLatch = new CountDownLatch (1 );
120125
121126 consumingConnection .addRecoveryListener (new RecoveryListener () {
122127
123128 @ Override
124129 public void handleRecovery (Recoverable recoverable ) {
130+ recoveryLatch .countDown ();
125131 }
126132
127133 @ Override
128134 public void handleRecoveryStarted (Recoverable recoverable ) {
129- latch .countDown ();
130135 }
131136 });
132137
@@ -136,12 +141,12 @@ public void handleRecoveryStarted(Recoverable recoverable) {
136141
137142 assertThat (
138143 "Connection should have been closed and should have recovered by now" ,
139- latch .await (60 , TimeUnit .SECONDS ), is (true )
144+ recoveryLatch .await (60 , TimeUnit .SECONDS ), is (true )
140145 );
141146
142147 assertThat (
143148 "Consumer should have recovered by now" ,
144- latch .await (5 , TimeUnit .SECONDS ), is (true )
149+ consumerOkLatch .await (5 , TimeUnit .SECONDS ), is (true )
145150 );
146151 }
147152
@@ -158,12 +163,12 @@ private void declareQueue(final Channel channel, final String queue) throws IOEx
158163
159164 private void produceMessagesInBackground (final Channel channel , final String queue ) throws IOException {
160165 final AMQP .BasicProperties properties = new AMQP .BasicProperties .Builder ().deliveryMode (1 ).build ();
161- producerService .submit (new Callable <Void >() {
166+ executorService .submit (new Callable <Void >() {
162167
163168 @ Override
164169 public Void call () throws Exception {
165170 for (int i = 0 ; i < NUM_MESSAGES_TO_PRODUCE ; i ++) {
166- channel .basicPublish ("" , queue , false , properties , ( "MSG NUM" + i ). getBytes () );
171+ channel .basicPublish ("" , queue , false , properties , MESSAGE_CONTENT );
167172 }
168173 closeConnectionIfOpen (producingConnection );
169174 return null ;
@@ -172,36 +177,29 @@ public Void call() throws Exception {
172177 }
173178
174179 private void startConsumer (final String queue ) throws IOException {
175- consumingChannel .basicConsume (queue , false , "" , false , false , null , new DefaultConsumer (consumingChannel ) {
180+ consumingChannel .basicConsume (queue , true , new DefaultConsumer (consumingChannel ) {
176181
177182 @ Override
178- public void handleRecoverOk (String consumerTag ) {
179- consumerRecoverOkLatch .countDown ();
183+ public void handleConsumeOk (String consumerTag ) {
184+ consumerOkLatch .countDown ();
180185 }
181186
182187 @ Override
183188 public void handleDelivery (String consumerTag , Envelope envelope , AMQP .BasicProperties properties , byte [] body ) throws IOException {
184189 consumerWork ();
185190 try {
186- consumingChannel .basicAck ( envelope . getDeliveryTag (), false );
191+ consumingChannel .basicPublish ( "" , "" , null , "" . getBytes () );
187192 } catch (Exception e ) {
188193 // application should handle writing exceptions
189194 }
190195 }
191196 });
192- try {
193- Thread .sleep (500 );
194- } catch (InterruptedException e ) {
195- e .printStackTrace ();
196- }
197- consumingChannel .basicQos (QOS_PREFETCH );
198197 }
199198
200199 private void consumerWork () {
201200 try {
202201 Thread .sleep (MESSAGE_PROCESSING_TIME_MS );
203202 } catch (InterruptedException e ) {
204- e .printStackTrace ();
205203 }
206204 }
207205}
0 commit comments