[
https://issues.apache.org/jira/browse/CAMEL-25712?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Work on CAMEL-25712 started by shashank.
----------------------------------------
> camel-google-pubsub - with synchronousPull, a failed nack of a message that
> reached maxDeliveryAttempts ends the pull loop: the consumer never receives
> again while the route stays started
> -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-25712
> URL: https://issues.apache.org/jira/browse/CAMEL-25712
> Project: Camel
> Issue Type: Bug
> Components: camel-google-pubsub
> Reporter: shashank
> Assignee: shashank
> Priority: Major
>
> With {{synchronousPull=true}} the consumer pulls in a loop on its own thread
> ({{GooglePubsubConsumer.SubscriberWrapper.synchronousPull}}). Since
> CAMEL-23142 a message whose delivery attempt has reached
> {{maxDeliveryAttempts}} is nacked without routing, so that Pub/Sub moves it
> to the dead-letter topic. The limit is the endpoint option, or, when the
> option is not set, the {{maxDeliveryAttempts}} of the dead-letter policy of
> the subscription, read at start.
> The nack is a {{ModifyAckDeadline}} call with deadline 0
> ({{AcknowledgeSync.nack}}), which wraps any failure in a
> {{RuntimeCamelException}}. At main 18f53efde6c7 the call
> ({{GooglePubsubConsumer}} line 365) is inside the {{try}} of the loop, but
> that {{try}} only catches {{CancellationException}}, {{IOException}} and
> {{ExecutionException}}. A failed nack leaves {{synchronousPull}}, {{run()}}
> logs it at ERROR ("Failure getting messages from PubSub") and the consumer
> thread ends. Then:
> * the route stays started and the consumer never pulls again; every message
> of the subscription waits until the route is restarted;
> * the messages pulled in the same batch after the poison message are not
> routed (they are delivered again after their ack deadline, to nobody).
> Trigger: {{synchronousPull=true}}, a subscription with a dead-letter policy
> (or {{maxDeliveryAttempts}} set), a message that keeps failing in the route
> until it reaches the limit (the case the option exists for), and a failure of
> that one {{ModifyAckDeadline}} call: Pub/Sub returning {{UNAVAILABLE}} for
> longer than the client's retry budget (with the default stub settings of
> google-cloud-pubsub 1.157.0, {{ModifyAckDeadline}} is retried only on
> {{UNAVAILABLE}}, within 60 s in total), any other error status, such as
> {{DEADLINE_EXCEEDED}}, which is not retried, or an expired ack id on a
> subscription with exactly-once delivery (the batch is not extended in
> synchronous pull mode, so an ack id can expire while the earlier messages of
> the batch are routed). With the default {{concurrentConsumers=1}} one failure
> stops all consumption.
> h3. Reproduction
> New {{GooglePubsubConsumerNackFailureTest}} (Mockito {{SubscriberStub}}, no
> emulator): the first pull returns a message at the limit
> ({{deliveryAttempt=3}}, {{maxDeliveryAttempts=3}}) and its
> {{ModifyAckDeadline}} fails with {{UnavailableException}}; the second pull
> returns a normal message. On main: {{mock://result Received message count.
> Expected: <1> but was: <0>}}, and the log shows {{ERROR GooglePubsubConsumer
> - Failure getting messages from PubSub}} with {{RuntimeCamelException:
> com.google.api.gax.rpc.UnavailableException}} at {{AcknowledgeSync.nack}};
> nothing is pulled after it.
> h3. Proposed fix
> Catch the failure of that nack, report it through the consumer's
> {{ExceptionHandler}} (WARN by default, or the route's error handler with
> {{bridgeErrorHandler}}), and go on with the next message. The message is
> delivered again when its ack deadline expires and is nacked then; Pub/Sub's
> own dead-letter forwarding is not affected. No option or default changes, so
> no upgrade guide entry.
> camel-google-pubsub tests with the fix: 17 run, 0 failures, 0 errors, 10
> skipped (the skipped ones need the Pub/Sub emulator container). Not run: the
> integration tests with the emulator, a real outage.
> How it was found: a sweep of consumers that run their own loop instead of a
> scheduled poll, looking for calls whose exception leaves the loop; the test
> confirmed it.
> Not changed: an {{ExecutionException}} of the pull whose cause is not
> retryable still ends the loop on purpose (the retryable codes can be widened
> with the component option {{synchronousPullRetryableCodes}}), as the
> asynchronous subscriber does for non-retryable errors.
> Affected: main (18f53efde6c7; tested). camel-4.23.x and camel-4.22.x (line
> 365) and camel-4.18.x (line 358) have the same call outside any matching
> catch (read, not run). camel-4.14.x has no maxDeliveryAttempts enforcement.
> Duplicate check (2026-10-16): JIRA text "google-pubsub synchronous pull"
> (CAMEL-17185 server error, 2021; CAMEL-17871 graceful shutdown), "pubsub
> maxDeliveryAttempts" (CAMEL-23142), "pubsub nack" (CAMEL-22947 ack/nack
> callbacks), "AcknowledgeSync" (CAMEL-15252, CAMEL-24350, producer),
> "synchronousPull nack" (none); GitHub pull requests "pubsub synchronous",
> "pubsub maxDeliveryAttempts", "AcknowledgeSync": nothing about this path.
> _Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)