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)