I am building two Spring versions of a single simple application. One of them is a Servlet stack and the other is a Reactive one. My goal is to show that the Reactive stack doesn't block threads from processing other requests when it's waiting for something else. So, I simulate a delay in the code in both versions. However, the reactive stack seems to not handle other requests when the delay is taking effect. In other words, it's not being reactive, am I doing something wrong? Have I misunderstood the way Spring reactive works? I am simulating the delay incorrectly?
The reactive stack handler class
@Component @RequiredArgsConstructor
public class GradeHandler {
private final GradeRepository gradeRepository;
public Mono<ServerResponse> gradeHandler(ServerRequest serverRequest){
String id = serverRequest.pathVariable("id");
return ServerResponse.
ok().contentType(MediaType.APPLICATION_JSON).
body(getGradeByIdDelayed(id, Duration.ofSeconds(1)), Grade.class);
}
public Mono<ServerResponse> gradeHandler_blocking(ServerRequest serverRequest){
String id = serverRequest.pathVariable("id");
return ServerResponse.
ok().contentType(MediaType.APPLICATION_JSON).
body(getGradeByIdDelayed(id, Duration.ofSeconds(10000)), Grade.class);
}
private Mono<Grade> getGradeByIdDelayed(String id, Duration duration) {
return gradeRepository.
findById(id).
delayElement(duration);
}
}
The reactive stack router config file
@Configuration
public class GradeRouterConfig {
@Bean
public RouterFunction<ServerResponse> gradeRouter(GradeHandler gradeHandler){
return RouterFunctions.
route(GET("grade/{id}").
and(accept(MediaType.APPLICATION_JSON)), gradeHandler::gradeHandler).
andRoute(GET("blocking/grade/{id}").
and(accept(MediaType.APPLICATION_JSON)), gradeHandler::gradeHandler_blocking);
}
}
The repository
@Repository
public interface GradeRepository extends ReactiveCrudRepository<Grade, String> {
}
The entity
@Document @Data @RequiredArgsConstructor @Builder
public class Grade {
@Id
private final String id;
private final double grade;
private final String studentName;
}
This is the client JavaScript code that calls the endpoints
gradesToBeQuried = [1, 2, 3, 4, 5]
var t0 = performance.now()
function httpGetAsync(theUrl, callback)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.onload = () => callback(xmlHttp.responseText);
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
function getRandomGradeId(grades) {
return grades[Math.floor(Math.random() * grades.length)];
}
function getURLWithRandomGradeID(grades){
randomGradeId = getRandomGradeId(grades);
return "http://localhost:8080/grade/" + randomGradeId;
}
function getURLWithRandomGradeID_blocking(grades){
randomGradeId = getRandomGradeId(grades);
return "http://localhost:8080/blocking/grade/" + randomGradeId;
}
function checkFinishConditionAndLog(currentIndex, totalNumberOfQueries) {
if (currentIndex === totalNumberOfQueries - 1) {
var t1 = performance.now()
console.log("it took " + (t1 - t0) / 1000 + " seconds.")
}
}
function queryGrade(currentIndex, totalNumberOfQueries){
let urlWithRandomGradeID = getURLWithRandomGradeID(gradesToBeQuried);
httpGetAsync(urlWithRandomGradeID, (result) => {
console.log(currentIndex + result);
checkFinishConditionAndLog(currentIndex, totalNumberOfQueries);
})
}
function queryGrade_blocking(){
let urlWithRandomGradeID = getURLWithRandomGradeID_blocking(gradesToBeQuried);
httpGetAsync(urlWithRandomGradeID, (result) => console.log("blocking thread is done"))
}
function runQueries(){
const totalNumberOfQueries = 1000;
const totalNumberOfBlockingQueries = 15;
Array(totalNumberOfBlockingQueries).fill().map((_, i) => queryGrade_blocking());
Array(totalNumberOfQueries).fill().map((_, i) => queryGrade(i, totalNumberOfQueries));
}
runQueries();
After much research I came across this answer, it turns out that the delayElement
function blocks the thread.