2020import java .util .concurrent .TimeoutException ;
2121
2222import 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 ;
2328import com .rabbitmq .client .Method ;
2429import com .rabbitmq .utility .BlockingValueOrException ;
2530import org .slf4j .Logger ;
@@ -66,6 +71,8 @@ public abstract class AMQChannel extends ShutdownNotifierComponent {
6671 /** Timeout for RPC calls */
6772 protected final int _rpcTimeout ;
6873
74+ private final boolean _checkRpcResponseType ;
75+
6976 /**
7077 * Construct a channel on the given connection, with the given channel number.
7178 * @param connection the underlying connection for this channel
@@ -78,6 +85,7 @@ public AMQChannel(AMQConnection connection, int channelNumber) {
7885 throw new IllegalArgumentException ("Continuation timeout on RPC calls cannot be less than 0" );
7986 }
8087 this ._rpcTimeout = connection .getChannelRpcTimeout ();
88+ this ._checkRpcResponseType = connection .willCheckRpcResponseType ();
8189 }
8290
8391 /**
@@ -153,8 +161,19 @@ public void handleCompleteInboundCommand(AMQCommand command) throws IOException
153161 // waiting RPC continuation.
154162 if (!processAsync (command )) {
155163 // 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 ();
158177 // the outstanding RPC can be null when calling Channel#asyncRpc
159178 if (nextOutstandingRpc != null ) {
160179 nextOutstandingRpc .handleCommand (command );
@@ -229,7 +248,7 @@ public AMQCommand rpc(Method m, int timeout)
229248 private AMQCommand privateRpc (Method m )
230249 throws IOException , ShutdownSignalException
231250 {
232- SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation ();
251+ SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation (m );
233252 rpc (m , k );
234253 // At this point, the request method has been sent, and we
235254 // should wait for the reply to arrive.
@@ -266,7 +285,7 @@ protected ChannelContinuationTimeoutException wrapTimeoutException(final Method
266285
267286 private AMQCommand privateRpc (Method m , int timeout )
268287 throws IOException , ShutdownSignalException , TimeoutException {
269- SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation ();
288+ SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation (m );
270289 rpc (m , k );
271290
272291 try {
@@ -384,13 +403,25 @@ public AMQConnection getConnection() {
384403
385404 public interface RpcContinuation {
386405 void handleCommand (AMQCommand command );
406+ /** @return true if the reply command can be handled for this request */
407+ boolean canHandleReply (AMQCommand command );
387408 void handleShutdownSignal (ShutdownSignalException signal );
388409 }
389410
390411 public static abstract class BlockingRpcContinuation <T > implements RpcContinuation {
391412 public final BlockingValueOrException <T , ShutdownSignalException > _blocker =
392413 new BlockingValueOrException <T , ShutdownSignalException >();
393414
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+
394425 @ Override
395426 public void handleCommand (AMQCommand command ) {
396427 _blocker .setValue (transformReply (command ));
@@ -412,12 +443,79 @@ public T getReply(int timeout)
412443 return _blocker .uninterruptibleGetValue (timeout );
413444 }
414445
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+
415504 public abstract T transformReply (AMQCommand command );
416505 }
417506
418507 public static class SimpleBlockingRpcContinuation
419508 extends BlockingRpcContinuation <AMQCommand >
420509 {
510+
511+ public SimpleBlockingRpcContinuation () {
512+ super ();
513+ }
514+
515+ public SimpleBlockingRpcContinuation (final Method method ) {
516+ super (method );
517+ }
518+
421519 @ Override
422520 public AMQCommand transformReply (AMQCommand command ) {
423521 return command ;
0 commit comments