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

shashank updated CAMEL-25869:
-----------------------------
    Description: 
With {{subscriptionType}} {{Shared}}, {{Failover}} or {{Key_Shared}}, the 
consumer creation strategies ({{SharedConsumerStrategy}}, 
{{FailoverConsumerStrategy}}, {{KeySharedConsumerStrategy}}, main 387cab56627b, 
lines 51-62 of each) subscribe {{numberOfConsumers}} consumers in a loop and 
catch the failure of each subscribe:

{code:java}
for (int i = 0; i < configuration.getNumberOfConsumers(); i++) {
    final String consumerName = configuration.getConsumerNamePrefix() + i;
    try {
        ...
        
consumers.add(builder.subscriptionType(SubscriptionType.Shared).subscribe());
    } catch (PulsarClientException exception) {
        LOGGER.error("A PulsarClientException occurred when creating Consumer 
{}, {}", consumerName,
                exception.getMessage(), exception);
    }
}
return consumers;
{code}

{{PulsarConsumer.doStart}} keeps whatever came back. So when a subscribe fails, 
the route still reports Started, with fewer consumers than configured, or with 
none, and nothing subscribes the missing ones again: there is no Pulsar 
consumer object left that could reconnect. In the Pulsar client (4.2.4, 
{{ConsumerImpl.connectionFailed}}), {{subscribe()}} throws once the broker 
could not be reached before the operation timeout (30 s by default), or on an 
error it does not retry; the consumer is then cleaned up from the client. 
Typical cases: the application starts while the broker restarts or is not 
reachable yet, a topic lookup that times out.

Consequences:
* {{numberOfConsumers=1}} (the default): the route is Started and holds no 
consumer. Nothing is consumed and the messages stay in the subscription until 
the route or the application is restarted. Only one ERROR line in the log.
* {{numberOfConsumers=N}}: the route consumes with fewer consumers than 
configured, without notice (less throughput; with {{Key_Shared}} and 
{{Failover}} the subscription is spread over fewer consumers than planned).
* The supervising route controller cannot help, as the start did not fail.

With {{Exclusive}} the failure is not caught, and the route fails to start 
({{ExclusiveConsumerStrategy}}), so the two paths behave differently for the 
same broker error.

*Fix*: the three strategies share one loop 
({{CommonCreationStrategyImpl.createMultipleConsumers}}). When a subscribe 
fails, the consumers subscribed so far are closed and the failure is thrown, so 
the route fails to start as with {{Exclusive}}; the caller or the supervising 
route controller can start it again, and a start that succeeds holds all the 
configured consumers. Upgrade note in the 4.24 guide, as a route that used to 
start with missing consumers now fails to start.

*Tests*: new {{PulsarConsumerSubscribeFailureTest}} (parameterised over 
{{SHARED}}, {{FAILOVER}}, {{KEY_SHARED}}; a mocked {{PulsarClient}} whose 
second subscribe throws {{PulsarClientException.TimeoutException}}, 
{{numberOfConsumers=2}}). On main all three fail with {{Expected 
java.lang.Exception to be thrown, but nothing was thrown}} (the route starts 
with one consumer). With the fix: the start fails, the consumer that did 
subscribe is closed, and a second start opens both consumers. The pull request 
also adds the deterministic simulation test that found the bug 
({{PulsarConsumerDstTest}}, camel-test-dst, 1000 seeds, seed 8 pinned with 
{{@DstSeed}}; see below). camel-pulsar unit tests with the fix: 1034 run (33, 
plus 1001 of the simulation test), 0 failures.

*How it was found*: deterministic simulation of the camel-pulsar consumer 
against a fake Pulsar client (subscribe failures, broker restarts, route 
restarts, suspend/resume, slow and failing processing; an operator that starts 
the route again when its start fails, as the supervising route controller 
does). The world ({{PulsarConsumerDstTest}}, {{PulsarWorld}}, {{FakePulsar}} in 
the {{dst}} package of the camel-pulsar tests) is part of the pull request. The 
invariant "a started route holds the configured number of Pulsar consumers" 
fails on main in 871 of 5000 seeds, all with {{Shared}}, {{Failover}} or 
{{Key_Shared}} and subscribe failures on; in 215 of them the route is Started 
with no consumer at all. In seed 8 ({{KEY_SHARED numberOfConsumers=1}}) the 
route was Started with no consumer, and none of the 11 published messages was 
routed in more than 5 simulated minutes after the faults cleared. With the fix, 
5000 of 5000 seeds pass; without subscribe failures, main passes 1000 of 1000 
(no other invariant failed). The JUnit test above shows it without the 
simulation; no run against a real broker.

*Affected*: main, camel-4.23.x, camel-4.22.x and camel-4.18.x (the same catch 
in the three strategies; read, not run).

_Filed with Claude Code on behalf of allthingssecurity._


  was:
With {{subscriptionType}} {{Shared}}, {{Failover}} or {{Key_Shared}}, the 
consumer creation strategies ({{SharedConsumerStrategy}}, 
{{FailoverConsumerStrategy}}, {{KeySharedConsumerStrategy}}, main 387cab56627b, 
lines 51-62 of each) subscribe {{numberOfConsumers}} consumers in a loop and 
catch the failure of each subscribe:

{code:java}
for (int i = 0; i < configuration.getNumberOfConsumers(); i++) {
    final String consumerName = configuration.getConsumerNamePrefix() + i;
    try {
        ...
        
consumers.add(builder.subscriptionType(SubscriptionType.Shared).subscribe());
    } catch (PulsarClientException exception) {
        LOGGER.error("A PulsarClientException occurred when creating Consumer 
{}, {}", consumerName,
                exception.getMessage(), exception);
    }
}
return consumers;
{code}

{{PulsarConsumer.doStart}} keeps whatever came back. So when a subscribe fails, 
the route still reports Started, with fewer consumers than configured, or with 
none, and nothing subscribes the missing ones again: there is no Pulsar 
consumer object left that could reconnect. In the Pulsar client (4.2.4, 
{{ConsumerImpl.connectionFailed}}), {{subscribe()}} throws once the broker 
could not be reached before the operation timeout (30 s by default), or on an 
error it does not retry; the consumer is then cleaned up from the client. 
Typical cases: the application starts while the broker restarts or is not 
reachable yet, a topic lookup that times out.

Consequences:
* {{numberOfConsumers=1}} (the default): the route is Started and holds no 
consumer. Nothing is consumed and the messages stay in the subscription until 
the route or the application is restarted. Only one ERROR line in the log.
* {{numberOfConsumers=N}}: the route consumes with fewer consumers than 
configured, without notice (less throughput; with {{Key_Shared}} and 
{{Failover}} the subscription is spread over fewer consumers than planned).
* The supervising route controller cannot help, as the start did not fail.

With {{Exclusive}} the failure is not caught, and the route fails to start 
({{ExclusiveConsumerStrategy}}), so the two paths behave differently for the 
same broker error.

*Fix*: the three strategies share one loop 
({{CommonCreationStrategyImpl.createMultipleConsumers}}). When a subscribe 
fails, the consumers subscribed so far are closed and the failure is thrown, so 
the route fails to start as with {{Exclusive}}; the caller or the supervising 
route controller can start it again, and a start that succeeds holds all the 
configured consumers. Upgrade note in the 4.24 guide, as a route that used to 
start with missing consumers now fails to start.

*Tests*: new {{PulsarConsumerSubscribeFailureTest}} (parameterised over 
{{SHARED}}, {{FAILOVER}}, {{KEY_SHARED}}; a mocked {{PulsarClient}} whose 
second subscribe throws {{PulsarClientException.TimeoutException}}, 
{{numberOfConsumers=2}}). On main all three fail with {{Expected 
java.lang.Exception to be thrown, but nothing was thrown}} (the route starts 
with one consumer). With the fix: the start fails, the consumer that did 
subscribe is closed, and a second start opens both consumers. camel-pulsar unit 
tests with the fix: 33 run, 0 failures.

*How it was found*: deterministic simulation of the camel-pulsar consumer 
against a fake Pulsar client (subscribe failures, broker restarts, route 
restarts, suspend/resume, slow and failing processing; an operator that starts 
the route again when its start fails, as the supervising route controller 
does). The invariant "a started route holds the configured number of Pulsar 
consumers" failed in 57 of 300 seeds, first reported on seed 9: {{FAILOVER 
numberOfConsumers=2}}, the subscribe of the first consumer timed out, the route 
reported Started with one consumer. In seed 8 ({{KEY_SHARED 
numberOfConsumers=1}}) the route was Started with no consumer, and none of the 
11 published messages was routed in more than 5 simulated minutes after the 
faults cleared. With the fix, 300 of 300 seeds pass; without subscribe 
failures, main passes 300 of 300 (no other invariant failed). The JUnit test 
above is the evidence; no run against a real broker.

*Affected*: main, camel-4.23.x, camel-4.22.x and camel-4.18.x (the same catch 
in the three strategies; read, not run).

_Filed with Claude Code on behalf of allthingssecurity._



> camel-pulsar - with Shared, Failover or Key_Shared subscriptions, a consumer 
> that cannot subscribe is dropped: the route starts with fewer consumers than 
> configured, or none, and never subscribes them again
> --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-25869
>                 URL: https://issues.apache.org/jira/browse/CAMEL-25869
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-pulsar
>            Reporter: shashank
>            Assignee: shashank
>            Priority: Major
>
> With {{subscriptionType}} {{Shared}}, {{Failover}} or {{Key_Shared}}, the 
> consumer creation strategies ({{SharedConsumerStrategy}}, 
> {{FailoverConsumerStrategy}}, {{KeySharedConsumerStrategy}}, main 
> 387cab56627b, lines 51-62 of each) subscribe {{numberOfConsumers}} consumers 
> in a loop and catch the failure of each subscribe:
> {code:java}
> for (int i = 0; i < configuration.getNumberOfConsumers(); i++) {
>     final String consumerName = configuration.getConsumerNamePrefix() + i;
>     try {
>         ...
>         
> consumers.add(builder.subscriptionType(SubscriptionType.Shared).subscribe());
>     } catch (PulsarClientException exception) {
>         LOGGER.error("A PulsarClientException occurred when creating Consumer 
> {}, {}", consumerName,
>                 exception.getMessage(), exception);
>     }
> }
> return consumers;
> {code}
> {{PulsarConsumer.doStart}} keeps whatever came back. So when a subscribe 
> fails, the route still reports Started, with fewer consumers than configured, 
> or with none, and nothing subscribes the missing ones again: there is no 
> Pulsar consumer object left that could reconnect. In the Pulsar client 
> (4.2.4, {{ConsumerImpl.connectionFailed}}), {{subscribe()}} throws once the 
> broker could not be reached before the operation timeout (30 s by default), 
> or on an error it does not retry; the consumer is then cleaned up from the 
> client. Typical cases: the application starts while the broker restarts or is 
> not reachable yet, a topic lookup that times out.
> Consequences:
> * {{numberOfConsumers=1}} (the default): the route is Started and holds no 
> consumer. Nothing is consumed and the messages stay in the subscription until 
> the route or the application is restarted. Only one ERROR line in the log.
> * {{numberOfConsumers=N}}: the route consumes with fewer consumers than 
> configured, without notice (less throughput; with {{Key_Shared}} and 
> {{Failover}} the subscription is spread over fewer consumers than planned).
> * The supervising route controller cannot help, as the start did not fail.
> With {{Exclusive}} the failure is not caught, and the route fails to start 
> ({{ExclusiveConsumerStrategy}}), so the two paths behave differently for the 
> same broker error.
> *Fix*: the three strategies share one loop 
> ({{CommonCreationStrategyImpl.createMultipleConsumers}}). When a subscribe 
> fails, the consumers subscribed so far are closed and the failure is thrown, 
> so the route fails to start as with {{Exclusive}}; the caller or the 
> supervising route controller can start it again, and a start that succeeds 
> holds all the configured consumers. Upgrade note in the 4.24 guide, as a 
> route that used to start with missing consumers now fails to start.
> *Tests*: new {{PulsarConsumerSubscribeFailureTest}} (parameterised over 
> {{SHARED}}, {{FAILOVER}}, {{KEY_SHARED}}; a mocked {{PulsarClient}} whose 
> second subscribe throws {{PulsarClientException.TimeoutException}}, 
> {{numberOfConsumers=2}}). On main all three fail with {{Expected 
> java.lang.Exception to be thrown, but nothing was thrown}} (the route starts 
> with one consumer). With the fix: the start fails, the consumer that did 
> subscribe is closed, and a second start opens both consumers. The pull 
> request also adds the deterministic simulation test that found the bug 
> ({{PulsarConsumerDstTest}}, camel-test-dst, 1000 seeds, seed 8 pinned with 
> {{@DstSeed}}; see below). camel-pulsar unit tests with the fix: 1034 run (33, 
> plus 1001 of the simulation test), 0 failures.
> *How it was found*: deterministic simulation of the camel-pulsar consumer 
> against a fake Pulsar client (subscribe failures, broker restarts, route 
> restarts, suspend/resume, slow and failing processing; an operator that 
> starts the route again when its start fails, as the supervising route 
> controller does). The world ({{PulsarConsumerDstTest}}, {{PulsarWorld}}, 
> {{FakePulsar}} in the {{dst}} package of the camel-pulsar tests) is part of 
> the pull request. The invariant "a started route holds the configured number 
> of Pulsar consumers" fails on main in 871 of 5000 seeds, all with {{Shared}}, 
> {{Failover}} or {{Key_Shared}} and subscribe failures on; in 215 of them the 
> route is Started with no consumer at all. In seed 8 ({{KEY_SHARED 
> numberOfConsumers=1}}) the route was Started with no consumer, and none of 
> the 11 published messages was routed in more than 5 simulated minutes after 
> the faults cleared. With the fix, 5000 of 5000 seeds pass; without subscribe 
> failures, main passes 1000 of 1000 (no other invariant failed). The JUnit 
> test above shows it without the simulation; no run against a real broker.
> *Affected*: main, camel-4.23.x, camel-4.22.x and camel-4.18.x (the same catch 
> in the three strategies; 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