shashank created CAMEL-26327:
--------------------------------

             Summary: camel-core - Routing Slip / Dynamic Router: a redelivered 
step hangs forever when the route it called used a routing slip or dynamic 
router itself
                 Key: CAMEL-26327
                 URL: https://issues.apache.org/jira/browse/CAMEL-26327
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank
            Assignee: shashank


A routing slip or dynamic router step that is redelivered by the error handler 
never completes when the route it called ran a routing slip or dynamic router 
on the same exchange and then failed: the caller waits forever (main 
8e0ce2d21371, also 4.22.1 and 4.23.0):
{code:java}
errorHandler(defaultErrorHandler().maximumRedeliveries(1).redeliveryDelay(0));
from("direct:start").routingSlip(constant("direct:sub"));
from("direct:sub")
    .doTry()
        .routingSlip(constant("mock:inner"))
        .throwException(new IllegalStateException("boom"))
    .endDoTry()
    .doFinally()
    .end();

template.send("direct:start", e -> e.getMessage().setBody("in"));   // never 
returns
{code}
It needs three things: a routing slip or dynamic router step with redelivery, a 
called route (direct passes the same exchange) that runs a routing slip or 
dynamic router, and an exception that leaves that called route. The exception 
can leave through a {{doTry}} without a matching {{doCatch}}, a route with 
{{noErrorHandler()}} or a rethrow. A dead letter channel never gets the 
message, and with {{onException(...).handled(true)}} the result is the same. A 
{{recipientList}} in the called route, or no redelivery, returns as expected.

*Cause*
* {{RoutingSlip.processExchange}} stores the producer of the step in the 
exchange property {{CamelSlipProducer}} ({{RoutingSlip.java:424}}). The error 
handler that wraps each step does not call that producer directly: it calls 
{{RoutingSlipProcessor}}, which reads the producer back from the property on 
every attempt ({{RoutingSlip.java:582-583}}). {{DynamicRouter}} extends 
{{RoutingSlip}} and shares this code. (The error handler per step comes from 
CAMEL-8393; the single error handler around {{RoutingSlipProcessor}} and the 
property come from CAMEL-12120, which replaced the cache of error handlers that 
leaked in CAMEL-10048.)
* When a step completes, its callback removes the property 
({{RoutingSlip.java:429}}). A routing slip in the called route runs on the same 
exchange: it overwrites the outer step's producer and then removes it.
* The exception then reaches the outer step's error handler, which redelivers 
({{RedeliveryErrorHandler.java:1360}}), and {{RoutingSlipProcessor.process}} 
throws {{NullPointerException: "producer" is null}} ({{RoutingSlip.java:583}}).
* The redelivery runs as reactive work: {{DefaultReactiveExecutor}} 
({{:205-207}}) logs a WARN ending with "This exception is ignored" and the 
callback is never called. The caller parks in 
{{DefaultAsyncProcessorAwaitManager.await}} and the exchange stays in flight. 
CAMEL-26301 is the same lost callback in the tokenizer EIP.

*Fix* (proposed): {{processExchange}} remembers the producer that was in the 
property before it sets its own, and the step's callback puts it back (it 
removes the property only when there was none, so nothing is left on the 
exchange after the outermost slip, as CAMEL-12120 intended). 
{{RoutingSlipProcessor}} also fails the exchange with an 
{{IllegalStateException}} and calls the callback when the property is missing, 
instead of throwing, so a missing producer cannot turn into a hang again. No 
other production code sets or reads {{CamelSlipProducer}}.

Not changed: the nested slip also overwrites the {{CamelSlipEndpoint}} property 
of the outer slip, so an outer dynamic router that reads it after such a step 
sees the inner slip's last endpoint. That is not a hang and changing it is 
visible to routes; it can get its own issue. Also not changed: 
{{RedeliveryErrorHandler.RedeliveryTask.redeliver()}} calls the output without 
the try/catch that {{RedeliveryTask.run()}} has for the first attempt, so any 
asynchronous processor that throws from {{process(exchange, callback)}} on a 
redelivery hangs the exchange the same way (on the first attempt the exchange 
fails instead). That is wider than this EIP and can get its own issue.

*Tests*: {{RoutingSlipNestedSlipRedeliveryTest}} (camel-core, 15 tests, every 
send bounded with {{assertTimeoutPreemptively}}): routing slip and dynamic 
router, each calling a route with a routing slip or a dynamic router, with a 
{{doTry}} at the top of the called route (default error handler) or 
{{noErrorHandler()}} and a dead letter channel; a handled {{onException}}; an 
inner step that completes asynchronously; an inner slip step redelivered by the 
called route's error handler before the outer step is redelivered; the original 
generated route; the property missing (the new {{IllegalStateException}}); and 
three controls (a recipient list in the called route, no redelivery, no 
property left after nested slips). The 12 non-control tests time out on main 
and all pass with the fix. The RoutingSlip, DynamicRouter, redelivery, error 
handler and dead letter tests of camel-core pass (388 tests).

*How it was found*: a Rocq specification of Camel's EIPs written from the 
documentation, compared with real Camel on generated routes. One generated 
route (a routing slip calling a route with nested {{doTry}}/{{doFinally}} that 
runs a routing slip, and {{onException}} with redelivery) hung; it was 
minimised and traced on HotSpot.

*Affected*: main, 4.23.0 and 4.22.1 reproduced. camel-4.22.x, camel-4.18.x and 
camel-4.14.x have the same code (set and remove of the property, and the 
unchecked read in {{RoutingSlipProcessor}}).

_Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to