Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Requeue message on unhandled client exception #23

Closed
damonsutherland opened this issue Jun 1, 2017 · 12 comments
Closed

Requeue message on unhandled client exception #23

damonsutherland opened this issue Jun 1, 2017 · 12 comments

Comments

@damonsutherland
Copy link

In a MessageListener's onMessage(Message) method, if a runtime exception is thrown I was expecting the message to be requeued and redelivered. I am not seeing that happen.

Here is the setup:

  • Two consumers:
    1. QoS (prefetch) is set to 1
    2. Session is AUTO_ACKNOWLEDGE and NOT transacted
    3. First consumer throws runtime exception in onMessage()
    4. Second consumer processes the message
  • One Producer:
    1. Sends 10 messages

Results:

  • exception is thrown
  • consumer 1 fails and does not recover
  • messages alternate consumers, so messages 2,4,6,8 and 10 are processed
  • message 1 is delivered to consumer 1, but is never redelivered
  • messages 3,5,7,9 are then sent to the alternate consumer.

In section 4.5.2 of the JMS 1.1 spec, it states that, although it is a client error to throw in onMessage, if it does happen, messages should be re-queued and redelivered. I briefly checked out the JMS Compliant Test suite you're using, and it doesn't appear this type of test is covered.

@michaelklishin
Copy link
Member

Hi @damonsutherland.

This is how the "regular" RabbitMQ Java client used to work up to https://github.com/rabbitmq/rabbitmq-java-client/blob/master/src/main/java/com/rabbitmq/client/impl/ForgivingExceptionHandler.java.
That feature, even though it's in the spec, is counterproductive in multiple ways.

Requeueing the message is quite trivial but I'd like to suggest making this configurable, just like it is in the "regular" Java client. The default can be different ("requeue on error") if it makes more sense for JMS.

@michaelklishin michaelklishin changed the title JMS Client doesn't requeue message for delivery on exception Requeue message on unhandled client exception Jun 2, 2017
@michaelklishin michaelklishin added this to the 2.0.0 milestone Jun 2, 2017
@michaelklishin
Copy link
Member

This is a breaking change if we make "requeue on exception" the default => can go into 2.0.

@acogoluegnes acogoluegnes self-assigned this Jun 2, 2017
@damonsutherland
Copy link
Author

@michaelklishin, that will work perfectly! Thanks. Do you have an estimated timeframe for the 2.0 release?

@michaelklishin
Copy link
Member

@damonsutherland it's mostly up to @acogoluegnes to decide but I'd say 4 weeks sounds doable. We haven't shipped 1.7.0 yet :)

@acogoluegnes
Copy link
Contributor

@damonsutherland Could you provide some sample code that reproduces the problem?

@damonsutherland
Copy link
Author

Sure. I have attached a test project:
rabbitmq.jms.error.zip

I am also wondering if part of the problem of not redelivering message 0 is occurring because it appears that messages, when Session.AUTO_ACKNOWLEDGE is set, are being acknowledged before being sent to the MessageListener. To prevent lost messages, should messages be acknowledged after being sent to the registered MessageListener?

    public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException {
        logger.trace("consumerTag='{}' envelope='{}'", consumerTag, envelope);
        if (this.rejecting) {
            long dtag = envelope.getDeliveryTag();
            logger.debug("basicNack: dtag='{}'", dtag);
            this.messageConsumer.getSession().explicitNack(dtag);
            return;
        }
        /* Wrap the incoming message in a GetResponse */
        GetResponse response = new GetResponse(envelope, properties, body, 0); // last parameter is remaining message count, which we don't know.
        try {
            long dtag = envelope.getDeliveryTag();
            if (this.messageListener != null) {
                this.messageConsumer.dealWithAcknowledgements(this.autoAck, dtag);
                RMQMessage msg = RMQMessage.convertMessage(this.messageConsumer.getSession(), this.messageConsumer.getDestination(), response);
                this.messageConsumer.getSession().deliverMessage(msg, this.messageListener);
            } else {
                // We are unable to deliver the message, nack it
                logger.debug("basicNack: dtag='{}' (null MessageListener)", dtag);
                this.messageConsumer.getSession().explicitNack(dtag);
            }
        } catch (JMSException x) {
            x.printStackTrace();
            throw new IOException(x);
        } catch (InterruptedException ie) {
            ie.printStackTrace();
            throw new IOException("Interrupted while delivering message", ie);
        }
    }

see line this.messageConsumer.dealWithAcknowledgements(this.autoAck, dtag);

@michaelklishin
Copy link
Member

michaelklishin commented Jun 2, 2017 via email

@damonsutherland
Copy link
Author

Perfect. I did fork this project and have a fix in 1.6 that seems to address the issue. If you would like, I can submit a pull request, or just post the change (it is pretty simple). It isn't configurable though.

@acogoluegnes
Copy link
Contributor

Thanks @damonsutherland for pointing this out. I'm not sure the re-delivery should be the default, the specification doesn't make much sense to me in this case (with auto-acknowledgment enabled, the onus is on the consumer to handle the message, too bad if it fails).
Nevertheless, we can add an option to comply to the specification as much as possible, this could go into 1.7.

acogoluegnes added a commit that referenced this issue Jun 19, 2017
Requeuing a message after a client RuntimeException complies to
the JMS specification. Nevertheless, the default behavior is
still the same: the message is "lost" (as it has been AMQP-acknowledged
before the delivery to the client) if the client fails to process
it (which makes more sense to us).

The option is RMQConnectionFactory#requeueOnMessageListenerException
and is false by default.

Fixes #23
@acogoluegnes acogoluegnes modified the milestones: 1.7.0, 2.0.0 Jun 19, 2017
@michaelklishin
Copy link
Member

michaelklishin commented Jun 19, 2017

@damonsutherland is the solution in #30 good enough for you? That's a bit different from what we do in our "regular" Java client but it's the same fundamental idea.

acogoluegnes added a commit to rabbitmq/rabbitmq-website that referenced this issue Jun 20, 2017
onMessageTimeoutMs: references rabbitmq/rabbitmq-jms-client#5
preferProducerMessageProperty: references rabbitmq/rabbitmq-jms-client#26
requeueOnMessageListenerException: references rabbitmq/rabbitmq-jms-client#23
@damonsutherland
Copy link
Author

@michaelklishin Yes the solution in #30 should work. Thanks.

@acogoluegnes
Copy link
Contributor

@damonsutherland you can have a try in 1.7.0.RC2.

acogoluegnes added a commit that referenced this issue Feb 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants