MonoThen creates deep stack traces leading to StackOverflowError
Expected behavior
some kind of error?
Actual behavior
block() never finishes
Steps to reproduce
following code
- causes java.lang.StackOverflowError (which is I guess expected according to https://github.com/reactor/reactor-core/issues/1441)
- then blocks forever (which doesn't seem expected)
package org.example;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
public class App {
static AtomicInteger depth = new AtomicInteger(100000);
private static Mono<Integer> source() {
return Mono.fromSupplier(() -> {
var i = depth.decrementAndGet();
return i > 0 ? null : 1;
});
}
static Mono<Integer> waitForData() {
return source()
.switchIfEmpty(Mono.defer(() ->
// Mono.just(1).delayElement(Duration.ofNanos(1))
Mono.delay(Duration.ofNanos(1))
.then(Mono.defer(App::waitForData))));
}
public static void main(String[] args) {
waitForData().block();
}
}
MonoThen recursion-related bug. Can have a fix similar to recursion protection in reactor.core.publisher.FluxConcatArray#ConcatArraySubscriber
not quite sure about " Can have a fix similar to recursion protection in reactor.core.publisher.FluxConcatArray#ConcatArraySubscriber".
wil it fix never-finishing block() call?
@nelapsi13. Yes, basically your code creates too deep stack trace so at some point any call that the reactor tries to do ends with Overflowexception which can not be handled. However, if we apply a trick, we can have that stack trace be in constant size, so an exception will not throw and the result wich unblock a lock will be delivered
As a matter of fact, the case is different from reactor.core.publisher.FluxConcatArray. The code is creating a recursion, for which we don't have a way to overcome. The subscription flow is forming a recursive object hierarchy:
block (0) -> waitForData (1) -> switchIfEmpty (2) -> defer (3) -> then (4) -> defer (5) -> waitForData (1') -> ...
As (1) and (1') are different objects, there is no way of determining the recursion and thinking about any internal optimisation. Consider structuring the reactive chain differently, as this type of generator is doomed to cause StackOverflowError.
Perhaps, instead of:
waitForData().block();
this could do the trick instead:
Flux.from(source()).single().retry().block();
StackOverflowError is not a problem (more or less) the problem is that block() never finishes (i.e. blocks forever)
The reactive chain is unable to terminate block on such an occasion, as when a SOE happens, the terminal signal can't be delivered to block's subscriber. All in all, JVM VirtualMachineErrors (https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/VirtualMachineError.html) are not considered recoverable.
Also, please note in the latest version the logging of such erroneous states has been added: https://github.com/reactor/reactor-core/pull/3122
ok, thanks for clarification