fix(sqs): prevent ConcurrentModificationException on container stop - #1632
Conversation
|
Thanks for the PR @Baqirrizvidev. As explained in the original issue, the Let me know if you'd like to amend the PR. |
|
I'm hitting this one too, so I've been waiting on this PR. I had a look at the concern raised about the snapshot:
I might be missing something, but I don't think that applies to this particular field. The way I read Not arguing against the explicit |
|
@KimDoubleB I recall trying the new ArrayList() approach and getting ConcurrentModificationException, but it's been a while. I'll take another look. |
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
… 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.
533226e to
7209e91
Compare
|
Thanks @Baqirrizvidev for the fix, and @KimDoubleB for the analysis, which I can confirm is correct: the snapshot copy is atomic, since the synchronized wrapper runs I've rebased the branch on current |
…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 #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 #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)
…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)
…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 #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 #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)
|
Backported to 3.4.x in #1671 |
When an SQS message listener container is stopped under heavy load,
AbstractPollingMessageSource.stop()iterates overthis.pollingFuturesto cancel active polls. Each future has awhenCompletecallback registered inmanagePollingFuturethat removes itself from thepollingFutureslist.Cancelling the future inside the iteration loop triggers the callback synchronously/concurrently, modifying the list while it is being iterated, which throws a
ConcurrentModificationExceptionand halts proper container shutdown.This commit resolves the issue by taking a snapshot copy (
new ArrayList<>(this.pollingFutures)) of the collection before iterating and cancelling, decoupling iteration from list modifications.Fixes #1594