batwad
batwad

Reputation: 3665

Camel message redelivery not behaving as expected

I have a route in Camel that I want to retry when an exception occurs, but I want to set a property so that the route can do something slightly differently the second time to try to stop the error happening again on the retry. Here's a route that illustrates the idea I'm trying at the moment.

from("direct:onExceptionTest")
    .onException(Exception.class)
        .maximumRedeliveries(1)
        .log("Retrying")
        .setProperty("retrying", constant(true))
    .end()
    .log("Start")   
    .choice()
        .when(property("retrying").isNull())
            .log("Throwing")
            .throwException(new Exception("Hello world"))
        .end()
    .end()
    .log("Done")

Obviously this isn't the real route; the whole choice body just simulates my component erroring in certain cases. I'm expecting to see the following messages logged:

Start
Throwing
Retrying
Start
Done

But what I actually see is:

Start
Throwing
Retrying
Failed delivery for (MessageId: ... on ExchangeId: ...). Exhausted after delivery attempt: 2 caught: java.lang.Exception: Hello world. Processed by failure processor: FatalFallbackErrorHandler[Pipeline[[Channel[Log(onExceptionTest)[Retrying]], Channel[setProperty(retrying, true)]]]]

I've tried adding handled(true) to the exception handler, but all this does is suppress the error message. I don't see the second Start or Done log message.

Why doesn't my route behave as I expect, and what do I need to do to get it to behave the way I want?

Update

@ProgrammerDan points out that the problem is that redelivery isn't intended for what I'm trying to achieve, which would explain why my route doesn't work! So I need to do the work in my handler, but my route calls a web service and has a few other steps and I don't want to duplicate all this in the handler. I've come up with this, which works as expected but it involves the route calling itself again from the start. Is this a bad idea? Will I get myself into knots with this approach?

from("direct:onExceptionTest")
    .onException(Exception.class)
        .onWhen(property("retrying").isNull()) // don't retry forever
        .log("Retrying")
        .setProperty("retrying", constant(true))
        .handled(true)
        .to("direct:onExceptionTest") // is recursion bad?
    .end()
    .log("Start")   
    .choice()
        .when(property("retrying").isNull())
            .log("Throwing")
            .throwException(new Exception("Hello world"))
        .end()
    .end()
    .log("Done")

Upvotes: 4

Views: 10540

Answers (2)

Peter Keller
Peter Keller

Reputation: 7646

Use onRedelivery with a Processor to set the property:

String KEY = "retrying";

from("direct:onExceptionTest")
     .onException(RuntimeException.class)
         .onRedelivery(new Processor() { // Sets a processor that should be processed before a redelivery attempt.
             @Override
             public void process(final Exchange exchange) throws Exception {
                 LOG.info("Retrying");
                 exchange.setProperty(KEY, true);
             }
        })
        .maximumRedeliveries(1)
        .handled(true)
    .end()
    .log("Start")
    .process(new Processor() {
        @Override
        public void process(final Exchange exchange) throws Exception {
            LOG.info("No problem");
        }
    })
    .process(new Processor() {
        @Override
        public void process(final Exchange exchange) throws Exception {
            if (exchange.getProperty(KEY) == null) {
                LOG.info("Throwing");
                throw new RuntimeException("Hello World");
            }
            else {
                LOG.info("No throwing");
            }
        }
    })
    .log("Done");

This prints

[                          main] route1                         INFO  Start
[                          main] OnExceptionHandler             INFO  No problem
[                          main] OnExceptionHandler             INFO  Throwing
[                          main] OnExceptionHandler             INFO  Retrying
[                          main] OnExceptionHandler             INFO  No throwing
[                          main] route1                         INFO  Done

As @ProgrammerDan noted, only the processor that failed is re-executed but not the first processor that passed without any problems.

Edit:

If all the processing has to be re-done then you may use a sub-route with doTry and doCatch as follows:

from("direct:onExceptionTest")
    .doTry()
        .to("direct:subroute")
    .doCatch(RuntimeException.class)
        .setProperty(KEY, constant(true))
        .to("direct:subroute")
    .end()
    .log("Done");

from("direct:subroute")
    .log("Start")
    .process(new Processor() {
        @Override
        public void process(final Exchange exchange) throws Exception {
            LOG.info("No problem");
        }
    })
    .process(new Processor() {
        @Override
        public void process(final Exchange exchange) throws Exception {
            if (exchange.getProperty(KEY) == null) {
                LOG.info("Throwing");
                throw new RuntimeException("Hello World");
            }
            else {
                LOG.info("No throwing");
            }
        }
    });

From the Camel Docs:

When using doTry .. doCatch .. doFinally then the regular Camel Error Handler does not apply. That means any onException or the likes does not trigger. The reason is that doTry .. doCatch .. doFinally is in fact its own error handler and that it aims to mimic and work like how try/catch/finally works in Java.

Upvotes: 4

ProgrammerDan
ProgrammerDan

Reputation: 901

Couple of points to consider about Camel's redelivery mechanism. First, check out the docs on the topic which might challenge your assumptions about how Camel handles redelivery. The point I've linked to is that Camel attempts redelivery at point of failure, it does not start over from the beginning of the route (as you appear to assume). If I'm understanding the docs correctly (I haven't tried this pattern in a while) you are basically telling it to retry throwing an exception several times, which I doubt is what you want to test.

Second, I'd recommend just doing the alternate handling directly in the onException() processor chain, as demonstrated a little further down in the same docs. Basically, you could specify how you want the message handled via a custom processor, and use both handled(true) and stop() to indicate that no further processing is necessary.

To sum it up, redelivery is generally meant to handle typical endpoint delivery failures, like intermittent connectivity drops, receiving server momentary unavailability, etc. where it makes the most sense to just "try again" and have a reasonable expectation of success. If you need more complex logic to handle retries, use a custom processor or series of processors within your onException() processor chain.

Upvotes: 2

Related Questions