[ 
https://issues.apache.org/jira/browse/CAMEL-26333?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Work on CAMEL-26333 started by shashank.
----------------------------------------
> camel-core - doTry: when the doTry calls a route whose error handler handled 
> a failure (dead letter channel or handled onException), the doFinally block 
> runs only its first step
> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-26333
>                 URL: https://issues.apache.org/jira/browse/CAMEL-26333
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-core
>            Reporter: shashank
>            Assignee: shashank
>            Priority: Major
>
> When a {{doTry}} calls a route (for example with {{direct:}}) whose error 
> handler handles a failure (a dead letter channel, or 
> {{onException(...).handled(true)}}), the {{doFinally}} block runs only its 
> first step and skips the rest, silently (main 8e0ce2d21371, and the released 
> 4.22.1 and 4.23.0 jars):
> {code:java}
> errorHandler(deadLetterChannel("mock:dead"));          // or: 
> onException(IllegalArgumentException.class).handled(true)
> from("direct:a")
>     .doTry()
>         .to("direct:b")
>     .doFinally()
>         .to("mock:f1").to("mock:f2").setHeader("f3", 
> constant("yes")).to("mock:f4")
>     .end()
>     .to("mock:end");
> from("direct:b").setBody(constant("in-b")).throwException(new 
> IllegalArgumentException("x"));
> direct:b fails:           mock:dead 1, mock:f1 1, mock:f2 0, header f3 null, 
> mock:f4 0, mock:end 0
> direct:b does not fail:   mock:dead 0, mock:f1 1, mock:f2 1, header f3 yes,  
> mock:f4 1, mock:end 1
> {code}
> The doFinally EIP is described (model and catalog) as steps "to always 
> execute after a doTry block completes, regardless of whether an exception was 
> thrown or caught". The steps that are skipped are the ones users put there 
> because they must always run (release a lock, write an audit record, 
> compensate).
> Cause: the called route's error handler marks the exchange as handled by the 
> error handler ({{errorHandlerHandled}}) and as failure handled 
> ({{failureHandled}}). {{FinallyProcessor}} clears the exception before it 
> runs the block, but not those marks. The doFinally block is a {{Pipeline}}, 
> which always runs its first step and then stops before each later step when 
> the exchange is handled by the error handler 
> ({{PipelineHelper.continueProcessing}}, "Handled by the error handler"). And 
> an error handler does not handle a new failure of an exchange that is failure 
> handled, so a route called from such a doFinally cannot handle or continue 
> its own failure with its error handler.
> A second consequence: a routing slip or dynamic router can route to the 
> endpoints of a recipient list instead of its own. A recipient list is a 
> pipeline of two processors: the first evaluates the recipients into the 
> exchange property {{CamelEvaluateExpressionResult}}, the second sends and 
> removes the property. When the recipient list is the first step of such a 
> doFinally, only the evaluation runs, and nothing is sent. A routing slip or 
> dynamic router that runs later on the same exchange takes that property as 
> its slip ({{RoutingSlip.process}}), so it sends to the recipient list's 
> endpoints and not to its own:
> {code:java}
> errorHandler(deadLetterChannel("mock:dead"));
> from("direct:r0").doTry().to("direct:r2").doFinally().routingSlip(constant("mock:slip")).end();
> from("direct:r2").doTry().to("direct:r3").doFinally().recipientList(constant("mock:a,mock:b")).end();
> from("direct:r3").throwException(new IOException("r3 failed"));
> -> mock:slip 0, mock:a 1 (sent by the routing slip), mock:b 0
> {code}
> When the stale value is not a valid endpoint list for the routing slip's 
> delimiter (a recipient list with {{delimiter(";")}}), the routing slip sends 
> to one {{direct:}} endpoint without a consumer and blocks for the direct 
> timeout (30 s). The same happens with {{onException(...).handled(true)}} 
> instead of the dead letter channel, and with a dynamic router.
> CAMEL-19441 (Not A Bug) decided that after a called route handled the 
> failure, the caller does not continue routing after the {{doTry}}. That stays 
> true with the fix ({{mock:end}} stays 0); this issue is only about the 
> {{doFinally}} block, which today runs exactly one step.
> *Fix* (proposed): {{FinallyProcessor}} clears both marks while the 
> {{doFinally}} block runs (as {{OnCompletionProcessor}} does for an 
> onCompletion), and sets them again when the block is done (the handled mark 
> only if the block did not set it itself), so every step runs, a route that 
> the block calls handles or continues its own failure as usual, and the route 
> then stops after the {{doTry}} as before. The recipient list in the 
> {{doFinally}} then sends and removes its property, so nothing stale is left 
> for a routing slip. No upgrade guide entry: a {{doFinally}} that now runs all 
> its steps is the fix. Routes that relied on the old behaviour see the second 
> and later steps of such a {{doFinally}} run (and a recipient list or routing 
> slip there send to its endpoints), once per exchange.
> Not changed: {{RoutingSlip.process}} still reads 
> {{CamelEvaluateExpressionResult}}, because camel-bean's {{@RoutingSlip}} and 
> {{@DynamicRouter}} annotations hand their result to the routing slip in that 
> property ({{MethodInfo}}; {{RoutingSlipPOJOTest}}). A dedicated key for that 
> hand-off ("TODO: Maybe use a new constant than EVALUATE_EXPRESSION_RESULT", 
> from CAMEL-13449) would stop a routing slip from reading another EIP's value 
> at all; it touches camel-bean and can be a separate minor issue.
> *Tests*: {{DoFinallyAfterHandledCalledRouteTest}} (6 tests: dead letter 
> channel, the same with asynchronous delayed redelivery, handled 
> {{onException}}, a failing route called from the doFinally that its dead 
> letter channel handles, one that its onException continues, and a control 
> where the called route does not fail; the bug tests also check that the 
> caller does not continue after the {{doTry}}, CAMEL-19441) and 
> {{RoutingSlipInDoFinallyAfterRecipientListTest}} (5 tests: routing slip with 
> a dead letter channel and with a handled {{onException}}, dynamic router, the 
> blocking case with a delimiter, and a control). The 9 non-control tests fail 
> on main and pass with the fix; with a fix that does not set the handled mark 
> again after the block, three {{doFinally}} tests fail on {{mock:end}}, and 
> with a fix that clears only the handled mark, the two tests with a failing 
> route called from the doFinally fail. The doTry/doCatch/doFinally, 
> onException, redelivery, dead letter channel, error handler, routing slip, 
> recipient list, dynamic router and pipeline tests of camel-core pass (984 
> tests with the onCompletion tests, including {{RoutingSlipPOJOTest}}); the 
> camel-core suite (the run was stopped by a resource guard of the machine 
> after 2951 of about 3030 test classes) has only the known failures of the 
> local offline build.
> *How it was found*: a Rocq specification of Camel's error handling, written 
> from the documentation, was compared with real Camel on generated routes, 
> then reproduced on HotSpot.
> *Affected*: main, 4.23.0, and all of camel-4.22.x, 4.18.x and 4.14.x 
> ({{FinallyProcessor}}, {{Pipeline}} and {{PipelineHelper}} have the same code 
> there).
> _Filed with Claude Code on behalf of allthingssecurity._



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

Reply via email to