Federico Mariani created CAMEL-26693:
----------------------------------------

             Summary: camel-activemq, camel-activemq6 - with only brokerURL 
set, a send from a transacted route is not rolled back
                 Key: CAMEL-26693
                 URL: https://issues.apache.org/jira/browse/CAMEL-26693
             Project: Camel
          Issue Type: Bug
          Components: camel-activemq
    Affects Versions: 4.18.5, 4.23.0, 4.22.2
            Reporter: Federico Mariani


h3. Problem
When the {{activemq}} or {{activemq6}} component creates the connection factory 
itself (only {{brokerURL}} is set, no {{connectionFactory}}), every endpoint 
creates its own connection factory: with the default 
{{usePooledConnection=true}} its own pooled-jms {{JmsPoolConnectionFactory}}, 
with {{usePooledConnection=false}} its own {{ActiveMQConnectionFactory}}.

Two effects:
* *Transactions:* in 
{{from("activemq:queue:in?transacted=true").to("activemq:queue:out")}}, the 
message sent to {{out}} is not part of the transaction of the consumed message. 
When the route fails, the consumed message is rolled back and delivered again, 
but the message sent to {{out}} is already committed, so {{out}} gets 
duplicates. Spring finds the session of the transaction by the connection 
factory instance, and the producer endpoint has another instance than the 
consumer endpoint. This happens without any credentials; CAMEL-26692 is the 
same symptom with {{username}}/{{password}}.
* *Connections:* one pool per endpoint, so one or more broker connections per 
endpoint instead of one pool for the component.

Source on main (48d063cfde0a):
* 
{{components/camel-jms/src/main/java/org/apache/camel/component/jms/JmsComponent.java}}
 line 1221: each endpoint gets a copy of the configuration of the component. 
When the component has no connection factory, the copy has none either.
* 
{{components/camel-jms/src/main/java/org/apache/camel/component/jms/JmsConfiguration.java}}
 lines 910-915 ({{getOrCreateConnectionFactory}}): the copy creates its 
connection factory lazily (line 912), for the listener container, the template 
and the transaction manager of that endpoint.
* 
{{components/camel-activemq/src/main/java/org/apache/camel/component/activemq/ActiveMQConfiguration.java}}
 lines 170-195 ({{createConnectionFactory}}): creates a new 
{{ActiveMQConnectionFactory}}, wrapped in a new {{SingleConnectionFactory}} 
({{useSingleConnection=true}}, line 182) or a new {{JmsPoolConnectionFactory}} 
(default, line 188), and registers that pool with the component (line 191). 
{{components/camel-activemq6/.../ActiveMQConfiguration.java}} has the same code.
* {{ActiveMQComponent#doStop}} (line 240) stops the registered pools, so the 
pool of each endpoint, including the endpoints that {{toD}} creates, lives 
until the component stops (read from the source, not measured).

h3. How to reproduce
Measured on main 48d063cfde0a (tests not committed).

*camel-activemq6*, end to end: embedded ActiveMQ Classic 6.3.2 broker 
({{BrokerService}}, non persistent, TCP connector), {{pooled-jms}} 3.2.5 added 
to the test classpath. Route 
{{from("amq:queue:in?transacted=true").to("amq:queue:out")}} with a processor 
that throws on the first delivery only; a second route consumes {{out}} into a 
mock. One message sent to {{in}}; counted after the redelivery and a quiet 
period of 2 seconds.

||Component setup||Connection factories (consumer / producer 
endpoint)||Deliveries of the consumed message||Messages on out||
|{{brokerURL}} (default {{usePooledConnection=true}})|two 
{{JmsPoolConnectionFactory}} instances|2|2|
|{{brokerURL}}, {{usePooledConnection=false}}|two {{ActiveMQConnectionFactory}} 
instances|2|2|
|control: {{connectionFactory}} = one {{ActiveMQConnectionFactory}}|the same 
instance|2|1|
|control: {{connectionFactory}} = one {{JmsPoolConnectionFactory}}|the same 
instance|2|1|

Connections, same broker: routes {{a -> b -> c -> d -> mock}} (4 endpoints, 
each used by a consumer and a producer), 20 messages sent to {{a}}, open broker 
connections counted at the TCP connector afterwards.
||Component setup||Distinct connection factories||Open broker connections||
|{{brokerURL}} (pooled)|4 ({{JmsPoolConnectionFactory}})|4|
|control: one {{JmsPoolConnectionFactory}} on the component|1|1|

*camel-activemq* (5.19 jakarta client), endpoints created without a broker, 
comparing instances: with {{brokerURL}} only, the component has no connection 
factory, and the transaction manager of {{activemq:queue:in?transacted=true}} 
and the template of {{activemq:queue:out}} use two different 
{{JmsPoolConnectionFactory}} instances (pooled-jms added to the test 
classpath), or two different {{ActiveMQConnectionFactory}} instances with 
{{usePooledConnection=false}}. Not run against a broker; the module's own tests 
use a container.

{{useSingleConnection=true}} creates a {{SingleConnectionFactory}} per endpoint 
the same way: read from the source, not run.

h3. Proposed change
* Create the connection factory once for the component when the component 
creates it, for example on the configuration of the component before it is 
copied to the endpoints, so that the endpoints share it (and its pool), as they 
do when {{connectionFactory}} is set.
* Keep a factory of its own for an endpoint whose options change how the 
factory is created ({{usePooledConnection}} and {{useSingleConnection}} can be 
set on the endpoint, see {{ActiveMQComponent#setProperties}}).
* Risks to check:
** Fewer connections: a shared pool shares its connections (in the run above, 4 
endpoints used 1 connection with the default pool settings), so consumers that 
each had a connection of their own would share them.
** Endpoints with different {{clientId}} values (durable subscriptions) cannot 
share one connection; they may need their own factory, or the pool settings 
must allow it.
** {{useSingleConnection=true}} shared by consumers has the restrictions of the 
Spring {{SingleConnectionFactory}} (one client id, exception listener).
* Add a test per component for the transacted send and for the number of 
factories. An embedded ActiveMQ Classic broker ({{activemq-broker}}) works 
without a container for camel-activemq6.

h3. Impact / upgrade
* It changes the default behaviour for components with only {{brokerURL}}: 
transacted routes would roll back their sends with the consumed message, and 
the application would open fewer broker connections. This needs an entry in the 
upgrade guide.
* Not affected: a {{connectionFactory}} set on the component (the endpoints 
already share it).
* The same code is in 4.18.5, 4.22.2 and 4.23.0 for both components (read from 
the source).

h3. Related
* CAMEL-26692: the same symptom when {{username}}/{{password}} are set; its fix 
shares the credentials adapter of the connection factory of the component, so 
it does not cover this case, where the component has no connection factory.
* CAMEL-19721 (commit b2b77e6af257): camel-activemq uses the jakarta pooled-jms 
connection pool.
* CAMEL-20600: camel-activemq6.

_Claude Code on behalf of Federico Mariani_



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

Reply via email to