Hello team, I find that when I upgrade the version of spring-boot-starter-webflux from 3.2.2 to 3.2.6, there is a problem with retryWhen() and block(). Below is my code:
@RestController
public class TestController {
private final Service service;
public TestController(Service service) {
this.service = service;
}
@GetMapping("/test")
public Object testController(){
return service.getDate("/non-existent-path", String.class).single().block();
}
}
@org.springframework.stereotype.Service
@Slf4j
public class Service {
WebClient client = WebClient.create("http://localhost:8080");
public <T> Flux<T> getDate(String uri, Class<T> clazz){
return client.get().uri(uri).retrieve().bodyToFlux(clazz)
.retryWhen(Retry.backoff(3, Duration.ofMillis(2)).doBeforeRetry(
i->log.info(String.valueOf(i.totalRetriesInARow()), i.totalRetries())));
}
}
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>3.2.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
</dependencies>
Here is the specific phenomenon: When I use webclient to call a non-existent uri and an 404 error occurs, block() and retryWhen() will asynchronously capture the error signal, causing my controller to return immediately. At the same time, there is an asynchronous thread executing the retry logic.
Before the retry logic is completed, block() captures the error signal.
What I expect is block() catches the error signal and then return after my retry logic is executed, just as version 3.2.2. There's no such problem when i use version 3.2.2.
Looking forward to your reply. Thank u!
Comment From: YChen9511
I think it is related to netty web server. If I add spring-boot-starter-web (version 3.2.6) and switch the web server from netty to tomcat, there is no such problem.
Comment From: bclozel
This is most likely linked to the Spring Framework version or the Reactor version.
Can you try with your sample application with Spring Boot 3.2.2 and test with:
- overriding reactor with
ext["reactor-bom.version"]="2023.0.6"
(instead of 2023.0.2) - overriding spring framework with
ext["spring-framework.version"]="6.1.8"
(instead of 6.1.3)
Doing so should point in the right direction.
Comment From: YChen9511
This is most likely linked to the Spring Framework version or the Reactor version.
Can you try with your sample application with Spring Boot 3.2.2 and test with:
- overriding reactor with
ext["reactor-bom.version"]="2023.0.6"
(instead of 2023.0.2)- overriding spring framework with
ext["spring-framework.version"]="6.1.8"
(instead of 6.1.3)Doing so should point in the right direction.
When i overrode the versions as you said, the problem did not recur. Here is my build.gradle:
ext{
springFrameworkVersion = '6.1.8'
reactorBomVersion = '2023.0.6'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux:3.2.2'
implementation 'org.springframework:spring-core:${springFrameworkVersion}'
implementation 'org.springframework:spring-context:${springFrameworkVersion}'
implementation 'org.springframework:spring-web:${springFrameworkVersion}'
implementation 'org.springframework:spring-beans:${springFrameworkVersion}'
// import BOM
implementation platform('io.projectreactor:reactor-bom:${reactorBomVersion}')
// add dependencies without a version number
implementation 'io.projectreactor:reactor-core'
}
Comment From: bclozel
My question was about overriding just one, then the other. This will tell us where the issue is.
Comment From: bclozel
Sorry it's me - I didn't get that you didn't reproduce the problem with both overrides. It seems that dependency management is getting in the way here and it might be possible that your tests were not conclusive. Can you share a minimal sample application that reproduces the problem with Spring Boot 3.2.6 (ideally created with start.spring.io) and we'll take it from there?
Comment From: YChen9511
Sorry it's me - I didn't get that you didn't reproduce the problem with both overrides. It seems that dependency management is getting in the way here and it might be possible that your tests were not conclusive. Can you share a minimal sample application that reproduces the problem with Spring Boot 3.2.6 (ideally created with start.spring.io) and we'll take it from there?
sure. The attachment is a minimal sample application. I created it with start.spring.io. demo.zip
Comment From: bclozel
Thanks for the sample application.
I'm not sure about the change of behavior and I couldn't track so far what changes here. I suspect that an unrelated change is uncovering a root problem in your application: you should not call block
within a non-blocking thread and this can lead to unexpected results and racing issues, such like this.
With Spring Boot 3.2.3+, I'm getting the following exception:
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:86) ~[reactor-core-3.6.3.jar:3.6.3]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ HTTP GET "/test" [ExceptionHandlingWebHandler]
Original Stack Trace:
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:86) ~[reactor-core-3.6.3.jar:3.6.3]
at reactor.core.publisher.Mono.block(Mono.java:1779) ~[reactor-core-3.6.3.jar:3.6.3]
at com.example.demo.TestController.testController(TestController.java:18) ~[main/:na]
I'm closing this issue as invalid.
Comment From: YChen9511
Hello team, If I want the HTTP response to not be a Flux or Mono, which client should I use to perform HTTP requests? It seems that RestTemplate will be deprecated in a future version and WebClient is a replacement. I used block() to get objects before, but encountered some issues mentioned earlier. Do you have any suggestions?
Comment From: bclozel
Answering your comment from https://github.com/spring-projects/spring-framework/issues/33930#issue-2677839063
You can see details and code in https://github.com/spring-projects/spring-framework/issues/33567.
I was advised not to use block() in above link.
However, my project is a servlet application.
Your sample application is only using org.springframework.boot:spring-boot-starter-webflux
, so it's not a servlet application. I guess your sample is not showing what it should?
If I want the HTTP response to not be a Flux or Mono, which client should I use to perform HTTP requests? It seems that RestTemplate will be deprecated in a future version and WebClient is a replacement. I used WebClient.block() to get objects before, but encountered some issues mentioned earlier. Do you have any suggestions?
RestTemplate
is not going to be deprecated in a future version. You can use RestClient
if you would like to use a more modern API.