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

Work on CAMEL-25504 started by shashank.
----------------------------------------
> camel-jta, camel-spring - a transacted exchange that completes during a 
> graceful shutdown is rolled back instead of committed
> -----------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-25504
>                 URL: https://issues.apache.org/jira/browse/CAMEL-25504
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-spring
>            Reporter: shashank
>            Assignee: shashank
>            Priority: Major
>
> {{DefaultShutdownStrategy}} first stops or suspends the route consumers, then 
> calls {{prepareShutdown(suspendOnly=false, forced=false)}} on the route 
> services ("notify the services we intend to shutdown", 
> {{DefaultShutdownStrategy}} around line 659 at main c578a42a776d), then waits 
> up to the shutdown timeout for the in-flight exchanges. Only if the timeout 
> expires does it call {{prepareShutdown(false, forced=true)}}.
> Both transaction error handlers set {{preparingShutdown = true}} on both 
> calls: {{org.apache.camel.jta.TransactionErrorHandler.prepareShutdown}} 
> directly, and {{org.apache.camel.spring.spi.TransactionErrorHandler}} through 
> {{RedeliveryErrorHandler.prepareShutdown}}, which it calls first. Since 
> CAMEL-23234 both transaction callbacks do, after the route work (camel-jta 
> {{doInTransactionTemplate}} line 205, camel-spring 
> {{doInTransactionTemplate}} line 215):
> {code:java}
> // if forced shutdown is in progress, mark the exchange for rollback
> if (preparingShutdown) {
>     exchange.setRollbackOnly(true);
> }
> {code}
> So every transacted exchange that is in flight when a graceful shutdown 
> (CamelContext stop, or a route stop through the route controller) starts, and 
> that completes normally inside the grace period, is rolled back. Graceful 
> shutdown no longer lets in-flight transactions finish. The 
> {{TransactionRolledbackException}} that forces the rollback is swallowed and 
> no exception is set on the exchange, so a caller that does not redeliver 
> (direct, platform-http/REST, timer, a ProducerTemplate) gets a normal reply 
> while the database work is rolled back; with a transacted JMS consumer the 
> message is redelivered after the restart and any non-transactional side 
> effects run twice.
> The CAMEL-23234 PR description and its tests describe the intended behaviour 
> as a rollback after the grace period has expired (forced shutdown); the 
> graceful case was not covered by its tests 
> ({{TransactionErrorHandlerShutdownTest}} calls {{prepareShutdown(false, 
> true)}}, and the IT only checks the timeout path).
> By reading {{DefaultShutdownStrategy}} (not tested): a route whose consumer 
> is deferred ({{shutdownRoute(Defer)}}) keeps taking new exchanges during the 
> grace period, after the graceful {{prepareShutdown}}, so on main every 
> transacted exchange it processes until the in-flight count reaches zero is 
> rolled back too.
> h3. Reproduction
> New {{TransactionalClientDataSourceGracefulShutdownTest}} (camel-spring-xml, 
> next to {{TransactionalClientDataSourceForcedShutdownTest}}; the 
> {{transactionalClientDataSource.xml}} context: a real 
> {{DataSourceTransactionManager}} on an embedded H2 database, 1 book inserted 
> at startup):
> * {{inFlightTransactionCompletingDuringContextStopIsCommitted}}: 
> {{from("direct:graceful").transacted().bean("bookService")}} inserts a book, 
> then the exchange is held in the route; {{context.stop()}} with a 30 s 
> timeout. A service in the transacted block releases the exchange from its own 
> graceful {{prepareShutdown}} call (the strategy prepares the transaction 
> error handler before it), and the exchange completes long before the timeout. 
> On main:
> {noformat}
> an exchange that completed during a graceful shutdown must not be marked 
> rollback only ==> expected: <false> but was: <true>
> {noformat}
> * {{gracefulPrepareShutdownCommitsInFlightTransaction}}: the same with 
> {{prepareShutdown(false, false)}} called on the handler directly, as the 
> existing forced-shutdown test does with {{prepareShutdown(false, true)}}. On 
> main: {{graceful shutdown must not mark the exchange rollback only ==> 
> expected: <false> but was: <true>}} (and the insert is rolled back: 1 book 
> instead of 2).
> New {{TransactionErrorHandlerGracefulShutdownTest}} (camel-jta, no container):
> * {{inflightExchangeCompletingDuringGracefulShutdownIsCommitted}}: 
> {{from("direct:start").transacted().process(blocking)}}, a test 
> {{JtaTransactionPolicy}} counting commits and rollbacks, shutdown timeout 20 
> s. The exchange is held in the route, {{camelContext.stop()}} is started on 
> another thread, the test waits (Awaitility) until the handler received the 
> graceful {{prepareShutdown}}, then releases the exchange, which completes 
> long before the timeout. On main:
> {noformat}
> an exchange that completed during a graceful shutdown must not be marked 
> rollback only ==> expected: <false> but was: <true>
> {noformat}
> * {{gracefulPrepareShutdownDoesNotRollBack}}: the same on the handler 
> directly with {{prepareShutdown(false, false)}}. On main: {{graceful shutdown 
> must not mark the exchange rollback only ==> expected: <false> but was: 
> <true>}}.
> The existing forced-shutdown tests (camel-jta 
> {{TransactionErrorHandlerShutdownTest}}, camel-spring-xml 
> {{TransactionalClientDataSourceForcedShutdownTest}}) and the suspend/resume 
> tests pass with and without the fix.
> h3. Proposed fix
> In both handlers, a separate {{volatile boolean forcedShutdown}} that 
> {{prepareShutdown}} sets only when {{forced}} is true (reset in 
> {{doStart}}/{{doResume}} like {{preparingShutdown}}); the check after the 
> route work reads it instead of {{preparingShutdown}}. {{preparingShutdown}} 
> itself is unchanged (in the Spring handler it still stops redeliveries during 
> shutdown). The forced path of CAMEL-23234 is unchanged: on timeout the 
> in-flight exchanges are still marked rollback only and an exchange finishing 
> afterwards still rolls back. One secondary change: with 
> {{shutdownNowOnTimeout=false}} the strategy never calls {{prepareShutdown(.., 
> forced=true)}}, so an exchange that completes after the timeout is now 
> committed (as before 4.19). The upgrade guide (4.23) gets a short note. Tests 
> with the fix: camel-jta 5 tests, 0 failures (the Postgres IT 
> {{TransactionErrorHandlerGracePeriodShutdownIT}} was skipped: no Docker; it 
> exercises only the forced path, which the change does not touch); 
> camel-spring-xml 1184 tests, 0 failures, 26 skipped (camel-spring itself has 
> no tests).
> CAMEL-23234 added the check to both handlers in one change, so this ticket 
> fixes both.
> Found with a TLA+ model of the handler (begin, route work, the flag check, 
> commit/rollback) and the shutdown strategy (stop consumer, graceful prepare, 
> wait, timeout, forced marking): "an exchange that completed successfully 
> before the shutdown timeout is committed" is violated in 7 steps (begin, 
> consumer stopped, work done, graceful prepare, check sets rollback only, 
> rollback). With the fix the property holds for 2 and 3 exchanges, and the 
> forced rollback stays reachable. Then confirmed with the real classes as 
> above (the modelled flag logic is the same in both handlers; the model does 
> not include redelivery).
> Affected: main, camel-4.22.x (LTS) and camel-4.19.x, i.e. 4.19.0 onwards 
> (CAMEL-23234); camel-4.18.x and camel-4.14.x do not have the check (GitHub 
> contents API).
> Duplicate check (2026-10-09): JIRA text "TransactionErrorHandler" + 
> "shutdown" (CAMEL-23234 only), "graceful shutdown" + "transacted" 
> (CAMEL-23234, CAMEL-23260 is Service Bus), "rollbackOnly" + "shutdown" 
> (none); GitHub pull requests "jta shutdown", "TransactionErrorHandler" 
> (open): only #22204 (CAMEL-23234, merged).
> _Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to