I am trying to understand the behaviour of blocking functions in Reactor, but something else has completely thrown me off my study. Here is the code:
public static void main(String[] args) throws InterruptedException {
Flux.range(1, 100_000)
.doOnNext(a -> System.out.println(a + ", thread: " + Thread.currentThread().getName()))
.flatMap(a -> Mono.fromCallable(() -> blockingMethod(a)).subscribeOn(Schedulers.elastic()))
.subscribe();
System.out.println("Here");
Thread.sleep(Integer.MAX_VALUE);
}
private static int blockingMethod(int s) {
try {
Thread.sleep(100_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return s;
}
Here's a summary of what happens AFAIK:
Subscription happens on the
mainthread.mainbecomes free inside theflatMapto bring the next element from upstream. Therefore,doOnNextshould always printmain.After processing 100_000 elements,
mainwould become free and printhere.
Instead, this is what happens:
The first 256 elements are printed on
main(indoOnNext) as expected.After around 1 second, the next 256, then the next and so on. Elements from the second batch onwards are printed on
elasticthreads.
Here are my questions:
Why are elements being processed in batch of 256?
Schedulers.elastic()should create threads on demand, so ideally there should always be a thread available to take the request from main (ignoring JVM restrictions on the number of threads that I can create).Why are elements in the second 'batch' (and beyond) being printed on
elasticthreads? I expect them to be published onmain. In fact, this is what happens when you remove the blocking call aspublic static void main(String[] args) throws InterruptedException { Flux.range(1, 100_000) .doOnNext(a -> System.out.println(a + ", thread: " + Thread.currentThread().getName())) .flatMap(a -> Mono.just(a).subscribeOn(Schedulers.elastic())) .subscribe(); System.out.println("Here"); Thread.sleep(Integer.MAX_VALUE); }
Here, all elements print main in doOnNext and here is printed only when the stream finishes (freeing the main thread).
Am I missing something?
flatMapoperator is 256. So it can merge only 256 upstream concurrently. - Mister_JesussubscribeOnaffects to all chain of operators (No matter where was the call - inside other nested operator or before subscribe). You can see that the first bundle ofdoOnNextcallbacks was performed in main thread becausesubscribeOnwas not called yet. If you will print values in subscriber, we will see that its all expected and all call will be inelastic. That why you shouldn't usedoOn*operators with heavy operations. - Mister_Jesus