shashank created CAMEL-25816:
--------------------------------

             Summary: camel-hazelcast - a transacted hazelcast-seda consumer 
whose route continues on another thread never commits: processed messages come 
back when the application leaves the cluster, failed ones are not retried
                 Key: CAMEL-25816
                 URL: https://issues.apache.org/jira/browse/CAMEL-25816
             Project: Camel
          Issue Type: Bug
          Components: camel-hazelcast
            Reporter: shashank
            Assignee: shashank


{{HazelcastSedaConsumer}} (main 936d6f4517a0, {{run()}} lines 86-180, callback 
at 116-135) begins a Hazelcast transaction on its polling thread when 
{{transacted=true}}, polls the message inside it, and hands the exchange to the 
asynchronous routing engine ({{processor.process(exchange, callback)}}). The 
{{done}} callback commits the transaction, or rolls it back when the exchange 
failed or was marked rollback only.

A Hazelcast transaction belongs to the thread that began it: 
{{TransactionImpl.commit()}} and {{rollback()}} check the thread and throw 
{{IllegalStateException: Transaction cannot span multiple threads!}} on any 
other thread (Hazelcast 5.4.0). When the route continues on another thread, the 
callback runs there. That is the case with an asynchronous producer, for 
example camel-kafka (the Kafka producer continues routing on its worker pool), 
netty or vertx-http, and with {{threads()}} or an asynchronous delay. Then:

* the commit fails; the reactive executor logs {{WARN Error executing reactive 
work due to Transaction cannot span multiple threads!. This exception is 
ignored.}} and the message stays reserved by a transaction that never ends;
* a failed exchange is not rolled back either, so the failed message is not 
taken again (the documented transacted behaviour), it just disappears from the 
queue;
* the next poll on the consumer thread fails with {{Nested transactions are not 
allowed!}} (the open transaction is still bound to the thread), is reported to 
the exception handler and waits {{onErrorDelay}};
* when the application leaves the Hazelcast cluster (a restart or a redeploy), 
the other members roll back the transactions it left open, so every message the 
route processed since it started is put back into the queue and consumed again.

The option says the messages "will only be removed if the transaction commits, 
which happens when the processing is complete"; here they are never removed, 
and are consumed twice.

*Fix*: with {{transacted=true}} the consumer thread processes the exchange 
synchronously (it waits for the exchange to complete, also when the route 
continued on another thread) and commits or rolls back the transaction itself; 
the non-transacted path is unchanged. A transacted consumer thread then takes 
the next message only when the previous one is complete 
({{concurrentConsumers}} gives more threads). Option description and upgrade 
note (4.24) updated.

*Tests*: new {{HazelcastSedaTransactedAsyncRouteTest}} (2 tests, two embedded 
Hazelcast members on the loopback interface, a route with {{threads(1)}}). 
{{testCompletedExchangesAreCommitted}} routes three messages, stops the route 
and shuts down the application's member; on main the remaining member gets them 
back: {{The processed messages were not removed from the queue ==> expected: 
<0> but was: <3>}}. {{testFailedExchangeIsRolledBack}} fails the first attempt; 
on main the message is never taken again: {{mock://failed Received message 
count. Expected: <1> but was: <0>}}. Both pass with the fix (3 runs). 
camel-hazelcast unit tests with the fix: 244 run, 0 failures, 1 error in 
{{HazelcastUserConfigSerializationFilterWarningTest.warnsForUserClientConfigWithoutFilter}}
 (a Hazelcast client cannot connect to the member in the test environment; it 
fails the same way on main).

*How it was found*: reading the transacted consumer for transaction state held 
across threads, then a probe with an embedded member (the WARN above, and the 
three processed messages back in the queue after the member left), then the 
JUnit test above. Not run against a separate Hazelcast cluster.

*Affected*: main, camel-4.23.x, camel-4.22.x, camel-4.18.x and camel-4.14.x 
(the same callback; read, not run).

_Filed with Claude Code on behalf of allthingssecurity._




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

Reply via email to