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

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

Fixed by https://github.com/apache/camel/pull/28318

> camel-nats - the JetStream push consumer stops consuming for good when the 
> server deleted its ephemeral consumer (after a disconnect of more than 5 s or 
> a server restart)
> --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-25845
>                 URL: https://issues.apache.org/jira/browse/CAMEL-25845
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-nats
>            Reporter: shashank
>            Assignee: shashank
>            Priority: Major
>             Fix For: 4.24.0
>
>
> With {{jetstreamEnabled=true}} and {{pullSubscription=false}} the consumer 
> subscribes with a JetStream push subscription, and without {{durableName}} 
> the JetStream consumer behind it is ephemeral 
> ({{NatsConsumer.setupJetStreamConsumer}}, main 20cdb309f7d0, lines 240-262). 
> The NATS server deletes an ephemeral push consumer when nobody is subscribed 
> to its deliver subject for its inactive threshold (nats-server 
> {{consumer.go}}: on lost interest an ephemeral consumer is deleted after 
> {{JsDeleteWaitTimeDefault}}, 5 seconds by default). A connection outage of 
> more than about 5 seconds does exactly that (jnats reconnects for about 2 
> minutes by default), and a server restart loses an ephemeral consumer as well.
> jnats does not create a push consumer again. After the reconnect it 
> subscribes the deliver subject (an inbox) again and fires {{RESUBSCRIBED}}, 
> but nothing delivers to that subject any more: {{PushMessageManager}} of 
> jnats 2.26.3 has no recovery (only the ordered consumer recreates its 
> consumer), and Camel sets no idle heartbeat, so not even a heartbeat alarm is 
> raised. The route stays started, nothing is consumed again and nothing is 
> logged. The messages stay in the stream until the route is restarted, which 
> then creates a new ephemeral consumer from its deliver policy (by default the 
> whole stream, so everything is routed again).
> This is the push twin of CAMEL-25815 (pull subscription). The fix for that 
> issue only changes the pull loop.
> *Fix*: a small helper ({{NatsJetStreamPushConsumerKeeper}}) is the message 
> handler of the push subscription and a connection listener. On 
> {{RESUBSCRIBED}} it asks the server for the consumer ({{getConsumerInfo}}, on 
> the consumer's executor, a reconnect does not queue a second check while one 
> waits). jnats (2.26.3, the version of Camel main) answers 
> {{getConsumerInfo()}} with {{null}} when the server has no such consumer 
> ({{lookupConsumerInfo}} maps api error 10014 to {{null}}), so {{null}} counts 
> as gone (as does a 404 error). When an ephemeral consumer is gone it 
> subscribes again, starting at the first stream sequence delivered to the 
> route and not acknowledged (it keeps the delivered messages and checks their 
> {{lastAck()}}; a nak'ed message counts as not acknowledged), or after the 
> last one delivered, so that nothing is skipped and the stream is not replayed 
> from the deliver policy; the new configuration carries no start time (the 
> server rejects a start time next to a start sequence). With 
> {{AckPolicy.None}} the delivery is the acknowledgement: no message is kept, 
> and the new consumer starts after the last one delivered. A message delivered 
> {{maxDeliver}} times (when set) counts as settled even when it was nak'ed, as 
> the server does not deliver it again ({{deliveredCount()}}), so a poison 
> message does not hold the resume point back for the life of the route. When 
> nothing was delivered yet, a consumer with the deliver policy {{New}} (or 
> {{Last}}) starts after (or at) the last stream sequence seen at the 
> subscribe, so the messages published during the outage are not skipped. 
> Before subscribing again it looks the stream up: a stream with {{WorkQueue}} 
> retention accepts only the deliver policy {{All}} (error 10101 otherwise) and 
> has already removed the acknowledged messages, and a stream whose create time 
> differs from the one seen at the subscribe was created again (its sequences 
> start at 1 again, so a start sequence of the old stream would skip its 
> messages); in both cases the consumer configuration is used as it is (for a 
> re-created stream after clearing what was tracked, with a WARN). A durable 
> consumer is not created again: it is gone only when an administrator deleted 
> it or after its {{inactiveThreshold}}, and creating it again from its 
> configuration would replay the stream from its deliver policy; the keeper 
> logs a WARN and keeps the subscription. A failed check or subscribe is tried 
> again while the route runs: five attempts a second apart, then with a delay 
> that doubles up to 30 seconds, with one WARN after the fifth attempt 
> (JetStream not ready yet after a server restart, or a stream that is gone 
> until it is created again: jnats throws 404 stream not found from 
> {{getConsumerInfo}} and {{getStreamInfo}}). The first failed subscribe of a 
> reconnect (jnats throws {{IllegalStateException}} for some, such as SUB-90007 
> when the stream is gone) goes to the consumer's exception handler, the next 
> ones are logged at DEBUG; a subscribe that fails because the consumer is 
> stopping is not reported. The listener is removed when the consumer stops, 
> and a subscription the keeper makes while the consumer is being stopped is 
> ended again by the keeper. The checks run only after a reconnect, so an idle 
> healthy consumer sends no extra requests. No option changes. A 4.24 
> upgrade-guide entry (like the one of CAMEL-25815) describes the ephemeral 
> case, {{ackPolicy=None}}, {{maxDeliver}}, {{New}}/{{Last}}, WorkQueue and 
> re-created streams, the retries, and that a durable consumer is not created 
> again.
> *Tests*: new {{NatsConsumerPushConsumerDeletedTest}} (12 tests) with a fake 
> JetStream server behind the jnats interfaces the consumer calls 
> ({{Connection}}, {{JetStream}}, {{Dispatcher}}, {{JetStreamSubscription}}, 
> {{Message}}): a push consumer delivers on its own thread, a disconnect 
> deletes the ephemeral consumers and drops the acknowledgements sent to them, 
> and a reconnect fires {{RESUBSCRIBED}} as jnats does. 
> {{consumesAgainAfterTheServerDeletedTheEphemeralConsumer}} fails on main with 
> {{mock://result Received message count. Expected: <2> but was: <0>}}; 
> {{redeliversTheMessageWhoseNakWasLostWithTheConsumer}} (the route fails a 
> message while the connection drops, so its nak is lost with the consumer) 
> fails on main with the same message. Both pass with the fix, and the message 
> acknowledged before the deletion is not routed again. 
> {{endsTheSubscriptionItMadeWhileTheRouteStopped}} stops the route while the 
> consumer subscribes again; it fails without the stop check of the keeper 
> ({{the new subscription is ended ==> expected: <true> but was: <false>}}) and 
> passes with it. {{doesNotCreateAGoneDurableConsumerAgain}} ({{durableName}} 
> set, the durable is deleted during the outage: no new consumer is created), 
> {{reportsAFailedSubscribeAndTriesAgain}} (the first subscribe after the 
> reconnect throws {{IllegalStateException}}: the endpoint's exception handler 
> gets it and the next attempt routes the message) and 
> {{resumesAnEphemeralConsumerThatStartedAtATime}} 
> ({{DeliverPolicy.ByStartTime}}; the fake server rejects a start time next to 
> {{ByStartSequence}}, as nats-server does). The fake server now answers like 
> jnats ({{getConsumerInfo()}} returns {{null}} for a deleted consumer and the 
> info for a live one); against the previous version of the branch 8 of the 9 
> tests then fail, as nothing is routed after the reconnect. 
> {{workQueueStreamIsSubscribedAgainWithDeliverPolicyAll}} (the fake refuses a 
> consumer that is not deliver-all on a work-queue stream, 10101, and removes 
> acknowledged messages), {{consumesAStreamCreatedAgainFromItsStart}} (the 
> stream is created again during the outage, the next message is sequence 1) 
> and {{ackPolicyNoneResumesAfterTheHighestDelivered}} 
> ({{manualAck=true&ackPolicy=None}}, the route never acks) still fail with 
> only the {{null}} fix ({{Expected: <1> but was: <0>}} twice, {{Expected: 
> <order-3> but was: <order-1>}}) and pass with the change. Three more tests 
> fail on the earlier version of the fix: 
> {{aMessageDeliveredMaxDeliverTimesDoesNotHoldBackTheResume}} 
> ({{maxDeliver=3}}, a poison message nak'ed three times; {{Body of message: 0. 
> Expected: <order-4> but was: <order-2>}}), 
> {{keepsCheckingUntilTheStreamIsCreatedAgain}} (the stream is gone after the 
> reconnect and created again after six checks; the check gave up after five) 
> and 
> {{deliverPolicyNewWithNothingDeliveredResumesAfterTheLastMessageAtTheSubscribe}}
>  ({{Expected: <1> but was: <0>}}). The fake now throws 404 stream not found 
> while the stream is gone, as jnats does. camel-nats unit tests with the fix: 
> 17 run, 0 failures.
> *How it was found*: a variant hunt for the CAMEL-25815 pattern (a server-side 
> object the client library does not recreate), then a JUnit test with a fake 
> JetStream server. The server rule is that of nats-server {{consumer.go}}, the 
> client behaviour that of jnats 2.26.3 (read from the classes). Not run 
> against a real NATS server (the integration tests need a container).
> *Affected*: main, camel-4.23.x, camel-4.22.x and camel-4.18.x (the same push 
> subscription without any recovery in {{setupJetStreamConsumer}}; read, not 
> run).
> _Filed with Claude Code on behalf of allthingssecurity._



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

Reply via email to