shashank created CAMEL-25712:
--------------------------------
Summary: 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
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)