Skip to content

Backport SQS fixes to 4.1.x - #1670

Merged
tomazfernandes merged 4 commits into
awspring:4.1.xfrom
tomazfernandes:backport-4.1.x-sqs-fixes
Aug 8, 2026
Merged

Backport SQS fixes to 4.1.x#1670
tomazfernandes merged 4 commits into
awspring:4.1.xfrom
tomazfernandes:backport-4.1.x-sqs-fixes

Conversation

@tomazfernandes

Copy link
Copy Markdown
Contributor

Cherry-picks the SQS fixes merged to main since the 4.1.0 release onto the 4.1.x branch, which was cut from the v4.1.0 tag:

All four are unmodified cherry-picks (-x) of the original commits. Verified locally by running the test classes the picks touch.

BK202503 and others added 4 commits August 8, 2026 14:51
…a message within one ack batch (awspring#1654)

Standard SQS queues can redeliver the same message before the acknowledgement
batch is flushed. SqsAcknowledgementExecutor was using MessageHeaderUtils.getId
(the Spring message id derived from the SQS messageId) as the batch entry id,
so a duplicate delivery within the same batch produced two entries with the
same DeleteMessageBatchRequestEntry.id — AWS then rejected the whole batch
with BatchEntryIdsNotDistinctException.

Switch to a positional-index id. The batch entry id is only a request-local
correlation token, so a per-request unique value is sufficient. Partial-failure
correlation now maps failed ids back to messages by index; if AWS returns an
id that isn't a valid index the executor still falls back to the
"cannot correlate all failure ids" path.

Fixes awspring#1634

Signed-off-by: BK202503 <199436087+BK202503@users.noreply.github.com>
Co-authored-by: BK202503 <199436087+BK202503@users.noreply.github.com>
(cherry picked from commit a123519)
Messages left in the acknowledgement buffer below the acknowledgement
threshold were never flushed on shutdown. The processor spun for the
whole acknowledgementShutdownTimeout, logged

  Acknowledgements did not finish in 20000 ms. Proceeding with shutdown.

and then cleared the buffer, so those messages were never deleted from
SQS and were redelivered once the visibility timeout expired.

Drain the buffer from the shutdown wait loop instead, which makes the
flush a guarantee of the shutdown path itself rather than something that
depends on a scheduled execution being armed. This also covers
acknowledgementInterval = ZERO with a positive threshold, where no
scheduled execution is ever created and a remainder was lost on every
shutdown.

The flush waits for the ack queue to drain so batches are not needlessly
split, and it is retried on every iteration of the wait loop: the polling
thread may be holding a message it has already polled but not yet added
to the buffer, in which case the queue looks empty while the message is
in neither the queue nor the buffer.

Also make running volatile in AbstractOrderingAcknowledgementProcessor.
It is written under lifecycleMonitor but read without synchronization
from the polling and scheduler threads, so those threads had no
guarantee of observing stop().

Issue awspring#1661

(cherry picked from commit 40721da)
…wspring#1664)

hasAcksLeft() decided whether the shutdown wait was done by looking at
the ack queue and the buffer. A message the polling thread has already
taken off the queue but not yet added to the buffer is in neither:

    Message<T> polledMessage = this.acks.poll(1, TimeUnit.SECONDS);
    if (polledMessage != null) {
        addMessageToBuffer(polledMessage);

If the wait loop sampled in that window it saw nothing left and returned,
waitAcknowledgementsToFinish() then set isTimeoutElapsed and cleared the
buffer, and the polling thread added the message to a buffer nothing
would ever flush. Unlike the shutdown timeout case, this dropped the
message with no warning at all.

Track the messages that have been received but are not in the queue nor
the buffer yet, and include them in hasAcksLeft(). The counter is
incremented before the message is offered to the queue and decremented
after it has been added to the buffer, so it is never undercounted; the
brief double counting while the message sits in the buffer only makes the
wait more conservative.

At most one message per shutdown can be in this window, but it widens
whenever addMessageToBuffer() has to wait on the buffer lock, which
happens while an execution is being dispatched - and for
AcknowledgementOrdering.ORDERED, that dispatch takes the ordered
execution lock while holding the buffer lock.

Follow-up to awspring#1663.

(cherry picked from commit 423f8b7)
…wspring#1632)

* fix(sqs): prevent ConcurrentModificationException on container stop

When an SQS message listener container is stopped, AbstractPollingMessageSource.stop() iterates over this.pollingFutures to cancel active polls. Each future has a whenComplete callback registered in managePollingFuture that synchronously/concurrently removes itself from the pollingFutures collection. Iterating over the collection while elements are being removed throws a ConcurrentModificationException.

This commit prevents the exception by taking a shallow snapshot copy of the pollingFutures collection before iterating and cancelling the futures.

Fixes awspring#1594

* test(sqs): add regression test for ConcurrentModificationException on container stop

Cancelling an in-flight polling future during stop() runs the whenComplete
callback registered in managePollingFuture inline on the stopping thread,
removing the future from pollingFutures while the collection is iterated.
The synchronizedCollection mutex is reentrant, so synchronization alone
cannot prevent this; iterating a snapshot copy does.

Regression introduced in awspring#1455 by replacing thenRun with whenComplete,
first released in 3.4.1 and 4.0.0.

---------

Co-authored-by: Tomaz Fernandes <tomaz.fernandes.se@gmail.com>
(cherry picked from commit 131e941)
@github-actions github-actions Bot added the component: sqs SQS integration related issue label Aug 8, 2026
@tomazfernandes
tomazfernandes merged commit 7270e1a into awspring:4.1.x Aug 8, 2026
1 check passed
@tomazfernandes tomazfernandes changed the title Backport SQS fixes to 4.1.x for a 4.1.1 release Backport SQS fixes to 4.1.x Aug 8, 2026
@tomazfernandes
tomazfernandes deleted the backport-4.1.x-sqs-fixes branch August 8, 2026 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: sqs SQS integration related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants