-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Description
Affects: <Spring Framework version>v5.3.8
- Version :
spring-webv5.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
Line 88 in b595dc1
| .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.
Line 243 in 9c33d27
| 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.