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

Claus Ibsen resolved CAMEL-25612.
---------------------------------
    Resolution: Fixed

> camel-sjms - when a session or consumer cannot be created during connection 
> recovery, the next attempt reports success without creating any consumer: the 
> route stays started and receives nothing
> --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-25612
>                 URL: https://issues.apache.org/jira/browse/CAMEL-25612
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-sjms
>            Reporter: shashank
>            Assignee: shashank
>            Priority: Major
>             Fix For: 4.24.0
>
>
> {{SimpleMessageListenerContainer.initConsumers}} (lines 355-374 at main 
> a7169d7c6ab6) assigns the two sets before it fills them:
> {code:java}
> if (consumers == null) {
>     sessions = new HashSet<>(concurrentConsumers);
>     consumers = new HashSet<>(concurrentConsumers);
>     for (int i = 0; i < this.concurrentConsumers; i++) {
>         Session session = createSession(connection, endpoint);
>         MessageConsumer consumer = createMessageConsumer(session);
>         configureConsumer(consumer, session);
>         sessions.add(session);
>         consumers.add(consumer);
>     }
> }
> {code}
> When {{createSession}} or {{createMessageConsumer}} throws, the sets stay: 
> empty, or with the consumers created so far.
> During connection recovery this is permanent. {{onException}} (lines 198-204) 
> sets both fields to {{null}} and schedules the recovery task; 
> {{recoverConnection}} (lines 208-233) calls {{refreshConnection()}} and 
> {{initConsumers()}}:
> # attempt n: the new connection is created, {{initConsumers}} assigns the 
> sets, one of the create calls fails, the attempt fails ("Failed to recover 
> JMS Connection (attempt: n)", WARN) and is repeated after 
> {{recoveryInterval}};
> # attempt n+1: {{refreshConnection}} closes that connection (and with it the 
> sessions of attempt n) and creates another one; {{initConsumers}} finds 
> {{consumers != null}} and creates nothing; {{recoverConnection}} sets 
> {{recoverTask = null}} and returns {{true}} ("Successfully recovered JMS 
> Connection", DEBUG).
> The container now has a started connection without any consumer. The route is 
> started, nothing above DEBUG is logged after the WARN of attempt n, no 
> further recovery is scheduled, and no message is consumed until the route or 
> the context is restarted. The same container is the reply listener of the 
> InOut producer ({{QueueReplyManager}}, {{TemporaryQueueReplyManager}}): there 
> the replies are then not received (read in the code, not tested).
> When does a create call fail on a connection that was just created:
> * the connection fails again between {{refreshConnection}} and the end of the 
> loop (for example a broker that is still restarting; not reproduced with a 
> real broker). The JMS provider then also calls {{onException}}, which sets 
> the fields to {{null}} again; the defect needs that call to arrive before 
> {{initConsumers}} assigns the sets (it is a race with a narrow window);
> * the connection is fine and the destination or the permission is not there 
> yet (for example a broker that accepts connections before its queues are 
> deployed, or an authorization that is loaded later; not reproduced with a 
> real broker, the test makes the create call fail). No {{onException}} 
> follows, so every such failure ends in the state above.
> h3. Reproduction
> New {{SjmsConsumerRecoveryFailedAttemptTest}} with the embedded Artemis 
> broker of the module and a connection factory that tracks the connections, 
> sessions and consumers of the route and can make one create call fail 
> ({{concurrentConsumers=3}}, {{recoveryInterval=200}}). After a simulated 
> connection failure the first session (one test) or the last session (another 
> test) of the next connection cannot be created; the test waits for the 
> following attempt (the third connection) and sends a message. On main both 
> tests fail with {{mock://first Received message count. Expected: <1> but was: 
> <0>}} ({{mock://last}} for the other one): the third connection has no 
> consumer.
> h3. Proposed fix
> {{initConsumers}} closes what it created and sets the two fields back to 
> {{null}} when the loop fails, so the next attempt creates all the consumers.
> Two related things in the same two methods:
> * When the listener container fails to start because a consumer cannot be 
> created, the session of that consumer stayed open on the connection, which is 
> kept for the next start (the sessions that got their consumer are closed by 
> {{stopConsumers}}). The same {{catch}} block closes it. A third test covers 
> it; on main it fails with {{open sessions after the failed start ==> 
> expected: <0> but was: <1>}}.
> * {{onException}} does not schedule a recovery while one is running 
> ({{scheduleConnectionRecovery}}, line 242). When it drops the consumers while 
> the running attempt creates them, the attempt has to run again. On main this 
> mostly happened by accident: the loop added to the fields that 
> {{onException}} had set to {{null}}, and the {{NullPointerException}} failed 
> the attempt (only an {{onException}} after the last pass of the loop was 
> lost). The loop no longer fails that way, so {{recoverConnection}} now 
> returns {{false}} (run again after {{recoveryInterval}}) when the consumers 
> are {{null}} after {{initConsumers}}. A fourth test covers this; it passes on 
> main too, for the reason above.
> No retry loop is added: the attempts are the ones of the existing recovery 
> task, {{recoveryInterval}} apart, with one WARN line (no stack trace) per 
> failed attempt as before.
> camel-sjms tests with the fix: 138 run, 0 failures, 5 skipped. camel-sjms2 
> (it uses the same container) was not run.
> How it was found: a small TLA+ model of the container (connection alive or 
> failed, the pending {{onException}} call of the provider, the {{consumers}} 
> field, the consumers that exist on the current connection, the steps of one 
> recovery attempt). For main TLC reports a state where no recovery is 
> scheduled, the connection is alive and it has no consumer: after two 
> connection failures (11 states), and after one connection failure and one 
> create call that fails on a live connection (9 states). With the fix the 
> invariant holds, and "eventually all consumers exist for good" holds for 3 
> consumers, 3 connection failures and 2 failing create calls (540 states). The 
> model is a sketch of the code and does not cover suspend / resume or the stop 
> of the container; the test above is the evidence.
> Affected: main (a7169d7c6ab6; tested). camel-4.23.x, camel-4.22.x and 
> camel-4.18.x have the same loop in {{initConsumers}} and the same 
> {{recoverConnection}} / {{onException}} (read, not run). On camel-4.14.x the 
> loop is the same, but {{recoverConnection}} is different (it returns 
> {{false}}; the fix of CAMEL-23646 is not there); not checked further.
> Overlap: the open pull request apache/camel#27173 (CAMEL-16039) changes 
> {{onException}} in the same file (it moves the three lines that reset the 
> fields into a method). It does not touch {{initConsumers}} or 
> {{recoverConnection}}; the two changes merge without a conflict today.
> Duplicate check (2026-10-13): JIRA text "sjms initConsumers" (CAMEL-25312 and 
> CAMEL-21705, neither about a failed attempt), "sjms recover connection 
> consumers" (none), "SimpleMessageListenerContainer" in camel-sjms 
> (CAMEL-25312, CAMEL-24626, CAMEL-22026, CAMEL-16039); GitHub pull requests 
> "initConsumers sjms": #27388, #23639, #23641 (the return value of the task), 
> none for this.
> _Filed with Claude Code on behalf of allthingssecurity._



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

Reply via email to