[
https://issues.apache.org/jira/browse/CAMEL-25790?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen updated CAMEL-25790:
--------------------------------
Fix Version/s: 4.24.0
> camel-google-pubsub - the consumer subscribes or pulls again during a
> graceful shutdown (deferShutdown), so the shutdown waits for its timeout, and
> a route restart leaves the previous subscriber loops running
> ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-25790
> URL: https://issues.apache.org/jira/browse/CAMEL-25790
> Project: Camel
> Issue Type: Bug
> Components: camel-google-pubsub
> Reporter: shashank
> Assignee: shashank
> Priority: Major
> Fix For: 4.24.0
>
>
> {{GooglePubsubConsumer}} (main 7aae27315946) runs one loop per concurrent
> consumer ({{SubscriberWrapper}}): the asynchronous loop starts a
> {{Subscriber}} and waits until it terminates, the synchronous loop pulls,
> routes the messages and pulls again. Both loops continue while
> {{isRunAllowed() && !isSuspendingOrSuspended()}} (lines 253 and 323). That
> check misses two cases.
> *1. Graceful shutdown.* Since CAMEL-23291 the consumer is {{ShutdownAware}}:
> {{deferShutdown}} stops the subscribers ({{stopAsync}}) and cancels the
> pending synchronous pulls, and the shutdown strategy then waits for the
> pending and inflight exchanges before it stops the consumer. The consumer is
> still started during that wait, so the loop sees {{isRunAllowed() == true}}
> and at once starts a new subscriber (asynchronous pull) or issues a new pull
> (synchronous pull, as soon as the cancelled {{get()}} returns). New messages
> keep being routed while the strategy waits for the consumer to drain. With a
> steady flow of messages the pending count does not reach zero: the graceful
> shutdown (route stop, context stop) waits for its full timeout (45 s by
> default; longer than the 30 s a Kubernetes pod gets by default) and then
> forces the shutdown, with exchanges still in flight; their messages are
> delivered again (duplicates). CAMEL-23291 added {{deferShutdown}} to avoid
> exactly that.
> *2. Route restart.* {{doStop}} only calls {{stopAsync}} and returns; the
> subscriber terminates later (streams closed, acks flushed). If the consumer
> is started again before that ({{restartRoute}}, a route controller or a
> reload that stops and starts the route), the old loop wakes up in a started
> consumer and subscribes again, next to the loops of the new start. Each
> restart adds {{concurrentConsumers}} subscriber loops (each with its streams
> and flow-control budget), and the executors of the previous start are only
> shut down in {{doShutdown}}. The loops stop only when the route stops for
> good.
> No message is lost; the effects are a shutdown that does not drain (hang
> until the timeout, forced shutdown, redelivered messages) and subscriber
> loops and threads that grow with every restart.
> *Fix*: each loop gets a generation; the consumer increments it when it
> starts, in {{deferShutdown}} and in {{doStop}}, and a loop of an older
> generation ends instead of subscribing or pulling again (the check after the
> subscriber start and after the pull is issued uses the same condition). A
> start after a stop also shuts down (not gracefully,
> {{ExecutorServiceManager.shutdown}}) the two executors of the previous start;
> their loops end on their own. {{doShutdown}} is unchanged.
> *Tests*: new {{GooglePubsubConsumerStopPullingTest}} (3 tests, mocked
> {{Subscriber}} / {{SubscriberStub}} as in
> {{GooglePubsubConsumerStopRaceTest}}). On main all three fail:
> {{asyncPullDoesNotSubscribeAgainAfterDeferShutdown}} ({{subscribers created
> after the consumer stopped pulling ==> expected: <1> but was: <2>}}),
> {{syncPullDoesNotPullAgainAfterDeferShutdown}} ({{pulls issued after the
> consumer stopped pulling ==> expected: <1> but was: <2>}}),
> {{asyncPullLoopOfPreviousStartEndsWhenConsumerIsRestarted}} ({{subscribers
> created by a start and a restart with one concurrent consumer ==> expected:
> <2> but was: <3>}}). With the fix they pass; camel-google-pubsub unit tests:
> 20 run, 0 failures, 10 skipped (need credentials or the emulator).
> *How it was found*: mutation testing (PIT) showed the consumer's stop /
> shutdown paths and the asynchronous pull ({{CamelMessageReceiver}}, the
> subscriber loop) untested, then reading the code against the
> {{ShutdownAware}} contract (CAMEL-23291: "deferShutdown() should stop the
> message pump while keeping connections alive") and the shutdown strategy
> ({{DefaultShutdownStrategy}}: a deferred consumer stays started until the
> pending exchanges are done), then the JUnit tests.
> *Affected*: main, camel-4.23.x, camel-4.22.x and camel-4.18.x (since
> CAMEL-23291, 4.18.3 / 4.21.0: same loop condition with {{deferShutdown}} and
> the executor shut down only in {{doShutdown}}). camel-4.14.x has no
> {{deferShutdown}} and shuts the executor down gracefully in {{doStop}}, which
> waits for the loops to end (read, not run).
> _Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)