20
20
import java .util .concurrent .TimeoutException ;
21
21
22
22
import com .rabbitmq .client .*;
23
+ import com .rabbitmq .client .AMQP .Basic ;
24
+ import com .rabbitmq .client .AMQP .Confirm ;
25
+ import com .rabbitmq .client .AMQP .Exchange ;
26
+ import com .rabbitmq .client .AMQP .Queue ;
27
+ import com .rabbitmq .client .AMQP .Tx ;
23
28
import com .rabbitmq .client .Method ;
24
29
import com .rabbitmq .utility .BlockingValueOrException ;
25
30
import org .slf4j .Logger ;
@@ -66,6 +71,8 @@ public abstract class AMQChannel extends ShutdownNotifierComponent {
66
71
/** Timeout for RPC calls */
67
72
protected final int _rpcTimeout ;
68
73
74
+ private final boolean _checkRpcResponseType ;
75
+
69
76
/**
70
77
* Construct a channel on the given connection, with the given channel number.
71
78
* @param connection the underlying connection for this channel
@@ -78,6 +85,7 @@ public AMQChannel(AMQConnection connection, int channelNumber) {
78
85
throw new IllegalArgumentException ("Continuation timeout on RPC calls cannot be less than 0" );
79
86
}
80
87
this ._rpcTimeout = connection .getChannelRpcTimeout ();
88
+ this ._checkRpcResponseType = connection .willCheckRpcResponseType ();
81
89
}
82
90
83
91
/**
@@ -153,8 +161,19 @@ public void handleCompleteInboundCommand(AMQCommand command) throws IOException
153
161
// waiting RPC continuation.
154
162
if (!processAsync (command )) {
155
163
// The filter decided not to handle/consume the command,
156
- // so it must be some reply to an earlier RPC.
157
- RpcContinuation nextOutstandingRpc = nextOutstandingRpc ();
164
+ // so it must be a response to an earlier RPC.
165
+ if (_checkRpcResponseType ) {
166
+ synchronized (_channelMutex ) {
167
+ // check if this reply command is intended for the current waiting request before calling nextOutstandingRpc()
168
+ if (!_activeRpc .canHandleReply (command )) {
169
+ // this reply command is not intended for the current waiting request
170
+ // most likely a previous request timed out and this command is the reply for that.
171
+ // Throw this reply command away so we don't stop the current request from waiting for its reply
172
+ return ;
173
+ }
174
+ }
175
+ }
176
+ final RpcContinuation nextOutstandingRpc = nextOutstandingRpc ();
158
177
// the outstanding RPC can be null when calling Channel#asyncRpc
159
178
if (nextOutstandingRpc != null ) {
160
179
nextOutstandingRpc .handleCommand (command );
@@ -229,7 +248,7 @@ public AMQCommand rpc(Method m, int timeout)
229
248
private AMQCommand privateRpc (Method m )
230
249
throws IOException , ShutdownSignalException
231
250
{
232
- SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation ();
251
+ SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation (m );
233
252
rpc (m , k );
234
253
// At this point, the request method has been sent, and we
235
254
// should wait for the reply to arrive.
@@ -266,7 +285,7 @@ protected ChannelContinuationTimeoutException wrapTimeoutException(final Method
266
285
267
286
private AMQCommand privateRpc (Method m , int timeout )
268
287
throws IOException , ShutdownSignalException , TimeoutException {
269
- SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation ();
288
+ SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation (m );
270
289
rpc (m , k );
271
290
272
291
try {
@@ -384,13 +403,25 @@ public AMQConnection getConnection() {
384
403
385
404
public interface RpcContinuation {
386
405
void handleCommand (AMQCommand command );
406
+ /** @return true if the reply command can be handled for this request */
407
+ boolean canHandleReply (AMQCommand command );
387
408
void handleShutdownSignal (ShutdownSignalException signal );
388
409
}
389
410
390
411
public static abstract class BlockingRpcContinuation <T > implements RpcContinuation {
391
412
public final BlockingValueOrException <T , ShutdownSignalException > _blocker =
392
413
new BlockingValueOrException <T , ShutdownSignalException >();
393
414
415
+ protected final Method request ;
416
+
417
+ public BlockingRpcContinuation () {
418
+ request = null ;
419
+ }
420
+
421
+ public BlockingRpcContinuation (final Method request ) {
422
+ this .request = request ;
423
+ }
424
+
394
425
@ Override
395
426
public void handleCommand (AMQCommand command ) {
396
427
_blocker .setValue (transformReply (command ));
@@ -412,12 +443,79 @@ public T getReply(int timeout)
412
443
return _blocker .uninterruptibleGetValue (timeout );
413
444
}
414
445
446
+ @ Override
447
+ public boolean canHandleReply (AMQCommand command ) {
448
+ // make a best effort attempt to ensure the reply was intended for this rpc request
449
+ // Ideally each rpc request would tag an id on it that could be returned and referenced on its reply.
450
+ // But because that would be a very large undertaking to add passively this logic at least protects against ClassCastExceptions
451
+ if (request != null ) {
452
+ final Method reply = command .getMethod ();
453
+ if (request instanceof Basic .Qos ) {
454
+ return reply instanceof Basic .QosOk ;
455
+ } else if (request instanceof Basic .Get ) {
456
+ return reply instanceof Basic .GetOk || reply instanceof Basic .GetEmpty ;
457
+ } else if (request instanceof Basic .Consume ) {
458
+ if (!(reply instanceof Basic .ConsumeOk ))
459
+ return false ;
460
+ // can also check the consumer tags match here. handle case where request consumer tag is empty and server-generated.
461
+ final String consumerTag = ((Basic .Consume )request ).getConsumerTag ();
462
+ return consumerTag == null || consumerTag .equals ("" ) || consumerTag .equals (((Basic .ConsumeOk )reply ).getConsumerTag ());
463
+ } else if (request instanceof Basic .Cancel ) {
464
+ if (!(reply instanceof Basic .CancelOk ))
465
+ return false ;
466
+ // can also check the consumer tags match here
467
+ return ((Basic .Cancel )request ).getConsumerTag ().equals (((Basic .CancelOk )reply ).getConsumerTag ());
468
+ } else if (request instanceof Basic .Recover ) {
469
+ return reply instanceof Basic .RecoverOk ;
470
+ } else if (request instanceof Exchange .Declare ) {
471
+ return reply instanceof Exchange .DeclareOk ;
472
+ } else if (request instanceof Exchange .Delete ) {
473
+ return reply instanceof Exchange .DeleteOk ;
474
+ } else if (request instanceof Exchange .Bind ) {
475
+ return reply instanceof Exchange .BindOk ;
476
+ } else if (request instanceof Exchange .Unbind ) {
477
+ return reply instanceof Exchange .UnbindOk ;
478
+ } else if (request instanceof Queue .Declare ) {
479
+ // we cannot check the queue name, as the server can strip some characters
480
+ // see QueueLifecycle test and https://github.com/rabbitmq/rabbitmq-server/issues/710
481
+ return reply instanceof Queue .DeclareOk ;
482
+ } else if (request instanceof Queue .Delete ) {
483
+ return reply instanceof Queue .DeleteOk ;
484
+ } else if (request instanceof Queue .Bind ) {
485
+ return reply instanceof Queue .BindOk ;
486
+ } else if (request instanceof Queue .Unbind ) {
487
+ return reply instanceof Queue .UnbindOk ;
488
+ } else if (request instanceof Queue .Purge ) {
489
+ return reply instanceof Queue .PurgeOk ;
490
+ } else if (request instanceof Tx .Select ) {
491
+ return reply instanceof Tx .SelectOk ;
492
+ } else if (request instanceof Tx .Commit ) {
493
+ return reply instanceof Tx .CommitOk ;
494
+ } else if (request instanceof Tx .Rollback ) {
495
+ return reply instanceof Tx .RollbackOk ;
496
+ } else if (request instanceof Confirm .Select ) {
497
+ return reply instanceof Confirm .SelectOk ;
498
+ }
499
+ }
500
+ // for passivity default to true
501
+ return true ;
502
+ }
503
+
415
504
public abstract T transformReply (AMQCommand command );
416
505
}
417
506
418
507
public static class SimpleBlockingRpcContinuation
419
508
extends BlockingRpcContinuation <AMQCommand >
420
509
{
510
+
511
+ public SimpleBlockingRpcContinuation () {
512
+ super ();
513
+ }
514
+
515
+ public SimpleBlockingRpcContinuation (final Method method ) {
516
+ super (method );
517
+ }
518
+
421
519
@ Override
422
520
public AMQCommand transformReply (AMQCommand command ) {
423
521
return command ;
0 commit comments