[ 
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)

Reply via email to