use of org.reactivestreams.tck.TestEnvironment.ManualSubscriber in project reactive-streams-jvm by reactive-streams.
the class PublisherVerification method required_spec303_mustNotAllowUnboundedRecursion.
@Override
@Test
public void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable {
final long oneMoreThanBoundedLimit = boundedDepthOfOnNextAndRequestRecursion() + 1;
activePublisherTest(oneMoreThanBoundedLimit, false, new PublisherTestRun<T>() {
@Override
public void run(Publisher<T> pub) throws Throwable {
final ThreadLocal<Long> stackDepthCounter = new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
return 0L;
}
};
final Latch runCompleted = new Latch(env);
final ManualSubscriber<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(env) {
// counts the number of signals received, used to break out from possibly infinite request/onNext loops
long signalsReceived = 0L;
@Override
public void onNext(T element) {
// NOT calling super.onNext as this test only cares about stack depths, not the actual values of elements
// which also simplifies this test as we do not have to drain the test buffer, which would otherwise be in danger of overflowing
signalsReceived += 1;
stackDepthCounter.set(stackDepthCounter.get() + 1);
if (env.debugEnabled()) {
env.debug(String.format("%s(recursion depth: %d)::onNext(%s)", this, stackDepthCounter.get(), element));
}
final long callsUntilNow = stackDepthCounter.get();
if (callsUntilNow > boundedDepthOfOnNextAndRequestRecursion()) {
env.flop(String.format("Got %d onNext calls within thread: %s, yet expected recursive bound was %d", callsUntilNow, Thread.currentThread(), boundedDepthOfOnNextAndRequestRecursion()));
// stop the recursive call chain
runCompleted.close();
return;
} else if (signalsReceived >= oneMoreThanBoundedLimit) {
// since max number of signals reached, and recursion depth not exceeded, we judge this as a success and
// stop the recursive call chain
runCompleted.close();
return;
}
// request more right away, the Publisher must break the recursion
subscription.value().request(1);
stackDepthCounter.set(stackDepthCounter.get() - 1);
}
@Override
public void onComplete() {
super.onComplete();
runCompleted.close();
}
@Override
public void onError(Throwable cause) {
super.onError(cause);
runCompleted.close();
}
};
try {
env.subscribe(pub, sub);
// kick-off the `request -> onNext -> request -> onNext -> ...`
sub.request(1);
final String msg = String.format("Unable to validate call stack depth safety, " + "awaited at-most %s signals (`maxOnNextSignalsInRecursionTest()`) or completion", oneMoreThanBoundedLimit);
runCompleted.expectClose(env.defaultTimeoutMillis(), msg);
env.verifyNoAsyncErrorsNoDelay();
} finally {
// since the request/onNext recursive calls may keep the publisher running "forever",
// we MUST cancel it manually before exiting this test case
sub.cancel();
}
}
});
}
Aggregations