22
22
import com .rabbitmq .client .amqp .AmqpException ;
23
23
import com .rabbitmq .client .amqp .Consumer ;
24
24
import com .rabbitmq .client .amqp .metrics .MetricsCollector ;
25
+ import java .time .Duration ;
25
26
import java .util .concurrent .*;
26
27
import java .util .concurrent .atomic .AtomicBoolean ;
27
28
import java .util .concurrent .atomic .AtomicLong ;
@@ -52,10 +53,12 @@ final class AmqpConsumer extends ResourceBase implements Consumer {
52
53
private final Long id ;
53
54
private final String address ;
54
55
private final AmqpConnection connection ;
55
- private final AtomicBoolean paused = new AtomicBoolean (false );
56
+ private final AtomicReference <PauseStatus > pauseStatus =
57
+ new AtomicReference <>(PauseStatus .UNPAUSED );
56
58
private final AtomicReference <CountDownLatch > echoedFlowAfterPauseLatch = new AtomicReference <>();
57
59
private final MetricsCollector metricsCollector ;
58
60
private final SessionHandler sessionHandler ;
61
+ private final AtomicLong unsettledCount = new AtomicLong (0 );
59
62
// native receiver internal state, accessed only in the native executor/scheduler
60
63
private ProtonReceiver protonReceiver ;
61
64
private Scheduler protonExecutor ;
@@ -115,6 +118,7 @@ private Runnable createReceiveTask(Receiver receiver, MessageHandler messageHand
115
118
while (!Thread .currentThread ().isInterrupted ()) {
116
119
Delivery delivery = receiver .receive (100 , TimeUnit .MILLISECONDS );
117
120
if (delivery != null ) {
121
+ this .unsettledCount .incrementAndGet ();
118
122
this .metricsCollector .consume ();
119
123
AmqpMessage message = new AmqpMessage (delivery .message ());
120
124
AtomicBoolean disposed = new AtomicBoolean (false );
@@ -127,6 +131,7 @@ public void accept() {
127
131
try {
128
132
protonExecutor .execute (() -> replenishCreditIfNeeded ());
129
133
delivery .disposition (DeliveryState .accepted (), true );
134
+ unsettledCount .decrementAndGet ();
130
135
metricsCollector .consumeDisposition (
131
136
MetricsCollector .ConsumeDisposition .ACCEPTED );
132
137
} catch (ClientIllegalStateException | ClientIOException e ) {
@@ -143,6 +148,7 @@ public void discard() {
143
148
try {
144
149
protonExecutor .execute (() -> replenishCreditIfNeeded ());
145
150
delivery .disposition (DeliveryState .rejected ("" , "" ), true );
151
+ unsettledCount .decrementAndGet ();
146
152
metricsCollector .consumeDisposition (
147
153
MetricsCollector .ConsumeDisposition .DISCARDED );
148
154
} catch (ClientIllegalStateException | ClientIOException e ) {
@@ -159,6 +165,7 @@ public void requeue() {
159
165
try {
160
166
protonExecutor .execute (() -> replenishCreditIfNeeded ());
161
167
delivery .disposition (DeliveryState .released (), true );
168
+ unsettledCount .decrementAndGet ();
162
169
metricsCollector .consumeDisposition (
163
170
MetricsCollector .ConsumeDisposition .REQUEUED );
164
171
} catch (ClientIllegalStateException | ClientIOException e ) {
@@ -196,16 +203,23 @@ private void startReceivingLoop() {
196
203
void recoverAfterConnectionFailure () {
197
204
this .nativeReceiver = createNativeReceiver (this .sessionHandler .sessionNoCheck (), this .address );
198
205
this .initStateFromNativeReceiver (this .nativeReceiver );
199
- this .paused .set (false );
206
+ this .pauseStatus .set (PauseStatus . UNPAUSED );
200
207
startReceivingLoop ();
201
208
}
202
209
203
210
private void close (Throwable cause ) {
204
211
if (this .closed .compareAndSet (false , true )) {
205
212
this .state (CLOSING , cause );
206
213
if (cause == null ) {
214
+ LOGGER .debug ("Pausing receiver link before detaching it" );
207
215
// normal closing, pausing message dispatching
208
216
this .pause ();
217
+ LOGGER .debug ("Receiver link paused. Unsettled message(s): {}" , this .unsettledCount .get ());
218
+ LOGGER .debug ("Waiting for unsettled messages to get settled if necessary" );
219
+ waitForUnsettledMessagesToSettle ();
220
+ if (this .unsettledCount .get () > 0 ) {
221
+ LOGGER .debug ("Closing receiver link with {} unsettled message(s)" , this .unsettledCount );
222
+ }
209
223
}
210
224
this .connection .removeConsumer (this );
211
225
if (this .receiveLoop != null ) {
@@ -222,6 +236,21 @@ private void close(Throwable cause) {
222
236
}
223
237
}
224
238
239
+ private void waitForUnsettledMessagesToSettle () {
240
+ Duration timeout = Duration .ofSeconds (10 );
241
+ Duration waitTime = Duration .ofMillis (10 );
242
+ Duration waitedTime = Duration .ZERO ;
243
+ while (this .unsettledCount .get () > 0 && waitedTime .compareTo (timeout ) <= 0 ) {
244
+ try {
245
+ Thread .sleep (waitTime .toMillis ());
246
+ waitedTime = waitedTime .plus (waitTime );
247
+ } catch (InterruptedException e ) {
248
+ Thread .currentThread ().interrupt ();
249
+ return ;
250
+ }
251
+ }
252
+ }
253
+
225
254
long id () {
226
255
return this .id ;
227
256
}
@@ -265,7 +294,7 @@ private void initStateFromNativeReceiver(ClientReceiver receiver) {
265
294
}
266
295
267
296
private void replenishCreditIfNeeded () {
268
- if (!this .paused ()) {
297
+ if (!this .pausedOrPausing ()) {
269
298
int creditWindow = this .initialCredits ;
270
299
int currentCredit = protonReceiver .getCredit ();
271
300
if (currentCredit <= creditWindow * 0.5 ) {
@@ -283,16 +312,24 @@ private void replenishCreditIfNeeded() {
283
312
}
284
313
285
314
void pause () {
286
- if (this .paused .compareAndSet (false , true )) {
287
- CountDownLatch latch = new CountDownLatch (1 );
288
- this .echoedFlowAfterPauseLatch .set (latch );
289
- this .protonExecutor .execute (this ::doPause );
315
+ if (this .pauseStatus .compareAndSet (PauseStatus .UNPAUSED , PauseStatus .PAUSING )) {
290
316
try {
291
- if (!latch .await (10 , TimeUnit .SECONDS )) {
292
- LOGGER .warn ("Did not receive echoed flow to pause receiver" );
317
+ CountDownLatch latch = new CountDownLatch (1 );
318
+ this .echoedFlowAfterPauseLatch .set (latch );
319
+ this .protonExecutor .execute (this ::doPause );
320
+ try {
321
+ boolean echoed = latch .await (10 , TimeUnit .SECONDS );
322
+ if (echoed ) {
323
+ this .pauseStatus .set (PauseStatus .PAUSED );
324
+ } else {
325
+ LOGGER .warn ("Did not receive echoed flow to pause receiver" );
326
+ this .pauseStatus .set (PauseStatus .UNPAUSED );
327
+ }
328
+ } catch (InterruptedException e ) {
329
+ Thread .currentThread ().interrupt ();
293
330
}
294
- } catch (InterruptedException e ) {
295
- Thread . currentThread (). interrupt ( );
331
+ } catch (Exception e ) {
332
+ this . pauseStatus . set ( PauseStatus . UNPAUSED );
296
333
}
297
334
}
298
335
}
@@ -305,7 +342,7 @@ private void doPause() {
305
342
306
343
void unpause () {
307
344
checkOpen ();
308
- if (this .paused .compareAndSet (true , false )) {
345
+ if (this .pauseStatus .compareAndSet (PauseStatus . PAUSED , PauseStatus . UNPAUSED )) {
309
346
try {
310
347
this .nativeReceiver .addCredit (this .initialCredits );
311
348
} catch (ClientException e ) {
@@ -314,7 +351,17 @@ void unpause() {
314
351
}
315
352
}
316
353
317
- private boolean paused () {
318
- return this .paused .get ();
354
+ boolean pausedOrPausing () {
355
+ return this .pauseStatus .get () != PauseStatus .UNPAUSED ;
356
+ }
357
+
358
+ boolean paused () {
359
+ return this .pauseStatus .get () == PauseStatus .PAUSED ;
360
+ }
361
+
362
+ enum PauseStatus {
363
+ UNPAUSED ,
364
+ PAUSING ,
365
+ PAUSED
319
366
}
320
367
}
0 commit comments