2525import java .util .ArrayList ;
2626import java .util .Collection ;
2727import java .util .Collections ;
28+ import java .util .HashSet ;
2829import java .util .List ;
2930import java .util .Set ;
3031import java .util .concurrent .CompletableFuture ;
@@ -98,14 +99,15 @@ private CompletableFuture<Void> deleteMessages(Collection<Message<T>> messagesTo
9899 MessageHeaderUtils .getId (messagesToAck ));
99100 StopWatch watch = new StopWatch ();
100101 watch .start ();
102+ List <Message <T >> orderedMessages = new ArrayList <>(messagesToAck );
101103 return CompletableFutures .exceptionallyCompose (this .sqsAsyncClient
102- .deleteMessageBatch (createDeleteMessageBatchRequest (messagesToAck )).thenCompose (
103- response -> handleDeleteMessageBatchResponse (messagesToAck , response )),
104- t -> toAcknowledgementFailure (messagesToAck , t ))
105- .whenComplete ((v , t ) -> logAckResult (messagesToAck , t , watch ));
104+ .deleteMessageBatch (createDeleteMessageBatchRequest (orderedMessages )).thenCompose (
105+ response -> handleDeleteMessageBatchResponse (orderedMessages , response )),
106+ t -> toAcknowledgementFailure (orderedMessages , t ))
107+ .whenComplete ((v , t ) -> logAckResult (orderedMessages , t , watch ));
106108 }
107109
108- private CompletableFuture <Void > handleDeleteMessageBatchResponse (Collection <Message <T >> messagesToAck ,
110+ private CompletableFuture <Void > handleDeleteMessageBatchResponse (List <Message <T >> messagesToAck ,
109111 DeleteMessageBatchResponse response ) {
110112 if (!response .failed ().isEmpty ()) {
111113 return CompletableFutures .<Void >failedFuture (createPartialFailureException (messagesToAck , response ));
@@ -122,51 +124,73 @@ private CompletableFuture<Void> toAcknowledgementFailure(Collection<Message<T>>
122124 return CompletableFutures .<Void >failedFuture (createAcknowledgementException (messagesToAck , cause ));
123125 }
124126
125- private SqsAcknowledgementException createPartialFailureException (Collection <Message <T >> messages ,
127+ private SqsAcknowledgementException createPartialFailureException (List <Message <T >> messages ,
126128 DeleteMessageBatchResponse response ) {
127- Set <String > messageIds = messages .stream ().map (MessageHeaderUtils ::getId ).collect (Collectors .toSet ());
128- Set <String > failedIds = response .failed ().stream ()
129- .map (BatchResultErrorEntry ::id )
130- .collect (Collectors .toSet ());
129+ Set <Integer > failedIndices = new HashSet <>();
130+ boolean allIdsCorrelated = true ;
131+ for (BatchResultErrorEntry errorEntry : response .failed ()) {
132+ Integer index = parseBatchEntryIndex (errorEntry .id ());
133+ if (index == null || index < 0 || index >= messages .size ()) {
134+ allIdsCorrelated = false ;
135+ break ;
136+ }
137+ failedIndices .add (index );
138+ }
131139
132- if (!messageIds .containsAll (failedIds )) {
140+ if (!allIdsCorrelated ) {
141+ Set <String > rawFailedIds = response .failed ().stream ()
142+ .map (BatchResultErrorEntry ::id )
143+ .collect (Collectors .toSet ());
133144 logger .warn ("Could not correlate all acknowledgement failure ids in queue {}: {}" , this .queueName ,
134- failedIds );
135- return new SqsAcknowledgementException ("Could not correlate acknowledgement failure ids: " + failedIds ,
145+ rawFailedIds );
146+ return new SqsAcknowledgementException ("Could not correlate acknowledgement failure ids: " + rawFailedIds ,
136147 Collections .emptyList (), messages .stream ().map (msg -> (Message <?>) msg ).collect (Collectors .toList ()),
137148 this .queueUrl , null );
138149 }
139150
140151 List <Message <?>> successfulMessages = new ArrayList <>();
141152 List <Message <?>> failedMessages = new ArrayList <>();
142153
143- for ( Message < T > msg : messages ) {
144- if ( failedIds .contains (MessageHeaderUtils . getId ( msg ) )) {
145- failedMessages .add (msg );
154+ for ( int i = 0 ; i < messages . size (); i ++ ) {
155+ if ( failedIndices .contains (i )) {
156+ failedMessages .add (messages . get ( i ) );
146157 } else {
147- successfulMessages .add (msg );
158+ successfulMessages .add (messages . get ( i ) );
148159 }
149160 }
150161
151- logger .warn ("Some messages could not be acknowledged in queue {}: {}" , this .queueName , failedIds );
162+ Set <String > failedMessageIds = failedMessages .stream ()
163+ .map (MessageHeaderUtils ::getId )
164+ .collect (Collectors .toSet ());
165+ logger .warn ("Some messages could not be acknowledged in queue {}: {}" , this .queueName , failedMessageIds );
152166
153- return new SqsAcknowledgementException ("Error acknowledging messages " + failedIds , successfulMessages ,
167+ return new SqsAcknowledgementException ("Error acknowledging messages " + failedMessageIds , successfulMessages ,
154168 failedMessages , this .queueUrl , null );
155169 }
156170
157- private DeleteMessageBatchRequest createDeleteMessageBatchRequest (Collection <Message <T >> messagesToAck ) {
158- return DeleteMessageBatchRequest
159- .builder ()
160- .queueUrl (this .queueUrl )
161- .entries (messagesToAck .stream ().map (this ::toDeleteMessageEntry ).collect (Collectors .toList ()))
162- .build ();
171+ private Integer parseBatchEntryIndex (String id ) {
172+ try {
173+ return Integer .valueOf (id );
174+ }
175+ catch (NumberFormatException ex ) {
176+ return null ;
177+ }
178+ }
179+
180+ private DeleteMessageBatchRequest createDeleteMessageBatchRequest (List <Message <T >> messagesToAck ) {
181+ List <DeleteMessageBatchRequestEntry > entries = new ArrayList <>(messagesToAck .size ());
182+ for (int i = 0 ; i < messagesToAck .size (); i ++) {
183+ entries .add (toDeleteMessageEntry (messagesToAck .get (i ), i ));
184+ }
185+ return DeleteMessageBatchRequest .builder ().queueUrl (this .queueUrl ).entries (entries ).build ();
163186 }
164187
165- private DeleteMessageBatchRequestEntry toDeleteMessageEntry (Message <T > message ) {
188+ // Positional index keeps the batch-local id unique even when SQS redelivers the same message id.
189+ private DeleteMessageBatchRequestEntry toDeleteMessageEntry (Message <T > message , int index ) {
166190 return DeleteMessageBatchRequestEntry
167191 .builder ()
168192 .receiptHandle (MessageHeaderUtils .getHeaderAsString (message , SqsHeaders .SQS_RECEIPT_HANDLE_HEADER ))
169- .id (MessageHeaderUtils . getId ( message ))
193+ .id (Integer . toString ( index ))
170194 .build ();
171195 }
172196 // @formatter:on
0 commit comments