shashank created CAMEL-25809:
--------------------------------
Summary: camel-aws2-sqs - with extendMessageVisibility=true and
maxMessagesPerPoll > 10, the first extension can come after the visibility
timeout ran out when the poll waited for a long-polling receive
Key: CAMEL-25809
URL: https://issues.apache.org/jira/browse/CAMEL-25809
Project: Camel
Issue Type: Bug
Components: camel-aws2
Reporter: shashank
Assignee: shashank
With {{extendMessageVisibility=true}} the SQS consumer keeps the messages it
processes invisible: its {{TimeoutExtender}} runs every {{max(1,
visibilityTimeout / 2)}} seconds and extends a message whose deadline falls
before its next run. The deadline is 80 % of {{visibilityTimeout}} counted from
the moment the message is registered with the extender, at the start of
{{processBatch}} (since CAMEL-25700; {{Sqs2Consumer}} lines 142-147 and 532 on
main 62c9a3ebe409). SQS counts the visibility timeout from the receive.
Normally the two are milliseconds apart. Not when a poll sends several receive
requests: with {{maxMessagesPerPoll}} greater than 10, {{PollingTask}} sends
one {{ReceiveMessage}} per 10 messages concurrently and joins them
({{CompletableFuture.get}}), so the batch starts only when the last request
returned. With long polling ({{waitTimeSeconds}}), a request that finds no more
messages waits up to {{waitTimeSeconds}}, while the messages returned by the
first request are already counting down.
With a gap L between the receive and the start of the batch, the first
extension comes between {{L + 0.3 x visibilityTimeout}} and {{L + 0.8 x
visibilityTimeout}} after the receive (depending on the phase of the extender).
It is late as soon as L is more than about 20 % of {{visibilityTimeout}}:
||visibilityTimeout||waitTimeSeconds (gap L)||first extension after the
receive||late for||
|30|20|29 to 44 s|14 of 15 phases|
|30|10|19 to 34 s|4 of 15 phases|
|60|20|38 to 68 s|8 of 30 phases|
|10|5|8 to 13 s|3 of 5 phases|
A late message becomes visible again while the route still processes it, and
another consumer (another instance, or {{concurrentConsumers}} > 1) receives
it: it is processed twice, which is what {{extendMessageVisibility}} is for.
The extender's {{ChangeMessageVisibility}} then fails ({{MessageNotInflight}} /
{{ReceiptHandleIsInvalid}}) and is only logged. This was noted as a follow-up
in the review of CAMEL-25700 (apache/camel#27985).
Trigger: {{extendMessageVisibility=true}}, {{maxMessagesPerPoll}} > 10, long
polling with {{waitTimeSeconds}} (or the queue's
{{ReceiveMessageWaitTimeSeconds}}) above about 20 % of {{visibilityTimeout}},
and a queue that does not fill every receive request of a poll at once (a quiet
or moderately busy queue). With the default {{maxMessagesPerPoll}}, or without
long polling, the gap stays small and nothing changes.
*Fix*: the messages of a poll are dated from the start of the poll (which is
never after their receive), and when one of them is due before the next
scheduled run of the extender, the extender runs at once (on its own single
thread, so it does not overlap a scheduled run). Extensions after the first are
unchanged. Cost: with long polling, one extra {{ChangeMessageVisibilityBatch}}
call for a batch whose poll waited long.
*Tests*: new {{SqsConsumerExtendMessageVisibilityLongPollTest}} with the
module's {{AmazonSQSClientMock}} (no AWS):
{{maxMessagesPerPoll=20&visibilityTimeout=4&extendMessageVisibility=true}}, one
request of the poll returns the message at once, the other waits 3 s (a long
poll on a queue with no more messages), the route holds the message until it
was extended. It asserts that the first {{ChangeMessageVisibilityBatch}} for
the message comes less than 4 s after its receive. On main it fails:
{{Expecting actual: 4995L to be less than: 4000L}} (4998 ms on the reruns).
With the fix it passes; camel-aws2-sqs unit tests: 78 run, 0 failures.
*How it was found*: a Z3 model of the extension schedule vs the visibility
window (period {{max(1, V/2)}} s, deadline registration + 0.8 V, gap L between
receive and registration): no counterexample for L = 0, a late first extension
once L exceeds about 0.2 V; then the JUnit test above.
*Affected*: main, camel-4.23.x, camel-4.22.x and camel-4.18.x (same deadline,
period and concurrent receive requests; on those branches without CAMEL-25700 a
message is registered only when its turn in the batch comes, so the gap is
larger; read, not run).
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)