Skip to content

Guard against ConcurrentModificationException when the response processes commitActions #27587

Closed
@libetl

Description

Affects: <Spring Framework version>v5.3.8


  • Version : spring-web v5.3.8
  • Server type : reactive
  • Java class : org.springframework.http.server.reactive.AbstractServerHttpResponse
  • Method : protected Mono<Void> doCommit(@Nullable Supplier<? extends Mono<Void>> writeAction) {

Hello,

Sometimes there is a race condition between beforeCommit statements for the same request.
This error only arises during traffic peaks

Both HttpHeaderWriterWebFilter and DefaultWebSessionManager add commitActions to the response
https://github.com/spring-projects/spring-security-reactive/blob/37749a64f782c2b2f81afb3db1b30cea3e956839/spring-security-reactive/src/main/java/org/springframework/security/web/server/header/HttpHeaderWriterWebFilter.java#L42

.doOnNext(session -> exchange.getResponse().beforeCommit(() -> save(exchange, session))));

But sometimes (fortunately it is not happening a lot) DefaultWebSessionManager adds its commitAction after the method doCommit has started.

As a result,
doCommit does Flux.concat(Flux.fromIterable(this.commitActions).map(Supplier::get)), which immediately stores an iterator over commitActions.

allActions = Flux.concat(Flux.fromIterable(this.commitActions).map(Supplier::get))

Then DefaultWebSessionManager adds its commitAction,
which means that this.commitActions has size 2, but the iterator was built for a list of 1.

When the FluxIterable subscribes, it does iterator.next, which invokes ArrayList$Itr.checkForComodification
This is where I get the ConcurrentModificationException .

To illustrate the problem, I am providing you this test class in kotlin.
This test systematically fails, but there is no race condition, it is simply a beforeCommit that takes place during another one, making the code build an iterator on a list of 1 when the list size increases to 2.

package mypackage

import org.junit.jupiter.api.Test
import org.springframework.mock.http.server.reactive.MockServerHttpResponse
import reactor.core.publisher.Mono

class AbstractServerHttpResponseTest {

    @Test
    fun test1() {
        MockServerHttpResponse()
            .apply {
                beforeCommit {
                    beforeCommit {
                        Mono.empty()
                    }
                    Mono.empty()
                }
            }
            .setComplete().block();
    }
}

If it is an app-level error, can we think of a better error handling to avoid such problems ?
Otherwise, if it is a framework-level error, I would be happy to contribute, just let me know.

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions