use of org.reactivestreams.tck.TestEnvironment.Latch in project reactive-streams-jvm by reactive-streams.
the class PublisherVerification method optional_spec104_mustSignalOnErrorWhenFails.
// Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.4
@Override
@Test
public void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable {
try {
whenHasErrorPublisherTest(new PublisherTestRun<T>() {
@Override
public void run(final Publisher<T> pub) throws InterruptedException {
final Latch onErrorlatch = new Latch(env);
final Latch onSubscribeLatch = new Latch(env);
pub.subscribe(new TestEnvironment.TestSubscriber<T>(env) {
@Override
public void onSubscribe(Subscription subs) {
onSubscribeLatch.assertOpen("Only one onSubscribe call expected");
onSubscribeLatch.close();
}
@Override
public void onError(Throwable cause) {
onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always");
onErrorlatch.assertOpen(String.format("Error-state Publisher %s called `onError` twice on new Subscriber", pub));
onErrorlatch.close();
}
});
onSubscribeLatch.expectClose("Should have received onSubscribe");
onErrorlatch.expectClose(String.format("Error-state Publisher %s did not call `onError` on new Subscriber", pub));
env.verifyNoAsyncErrors();
}
});
} catch (SkipException se) {
throw se;
} catch (Throwable ex) {
// which was wrong of him - he should have signalled on error using onError
throw new RuntimeException(String.format("Publisher threw exception (%s) instead of signalling error via onError!", ex.getMessage()), ex);
}
}
use of org.reactivestreams.tck.TestEnvironment.Latch in project reactive-streams-jvm by reactive-streams.
the class PublisherVerification method stochastic_spec103_mustSignalOnMethodsSequentially.
// Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.3
@Override
@Test
public void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable {
final int iterations = 100;
final int elements = 10;
stochasticTest(iterations, new Function<Integer, Void>() {
@Override
public Void apply(final Integer runNumber) throws Throwable {
activePublisherTest(elements, true, new PublisherTestRun<T>() {
@Override
public void run(Publisher<T> pub) throws Throwable {
final Latch completionLatch = new Latch(env);
final AtomicInteger gotElements = new AtomicInteger(0);
pub.subscribe(new Subscriber<T>() {
private Subscription subs;
private ConcurrentAccessBarrier concurrentAccessBarrier = new ConcurrentAccessBarrier();
/**
* Concept wise very similar to a {@link org.reactivestreams.tck.TestEnvironment.Latch}, serves to protect
* a critical section from concurrent access, with the added benefit of Thread tracking and same-thread-access awareness.
*
* Since a <i>Synchronous</i> Publisher may choose to synchronously (using the same {@link Thread}) call
* {@code onNext} directly from either {@code subscribe} or {@code request} a plain Latch is not enough
* to verify concurrent access safety - one needs to track if the caller is not still using the calling thread
* to enter subsequent critical sections ("nesting" them effectively).
*/
final class ConcurrentAccessBarrier {
private AtomicReference<Thread> currentlySignallingThread = new AtomicReference<Thread>(null);
private volatile String previousSignal = null;
public void enterSignal(String signalName) {
if ((!currentlySignallingThread.compareAndSet(null, Thread.currentThread())) && !isSynchronousSignal()) {
env.flop(String.format("Illegal concurrent access detected (entering critical section)! " + "%s emited %s signal, before %s finished its %s signal.", Thread.currentThread(), signalName, currentlySignallingThread.get(), previousSignal));
}
this.previousSignal = signalName;
}
public void leaveSignal(String signalName) {
currentlySignallingThread.set(null);
this.previousSignal = signalName;
}
private boolean isSynchronousSignal() {
return (previousSignal != null) && Thread.currentThread().equals(currentlySignallingThread.get());
}
}
@Override
public void onSubscribe(Subscription s) {
final String signal = "onSubscribe()";
concurrentAccessBarrier.enterSignal(signal);
subs = s;
subs.request(1);
concurrentAccessBarrier.leaveSignal(signal);
}
@Override
public void onNext(T ignore) {
final String signal = String.format("onNext(%s)", ignore);
concurrentAccessBarrier.enterSignal(signal);
if (// requesting one more than we know are in the stream (some Publishers need this)
gotElements.incrementAndGet() <= elements)
subs.request(1);
concurrentAccessBarrier.leaveSignal(signal);
}
@Override
public void onError(Throwable t) {
final String signal = String.format("onError(%s)", t.getMessage());
concurrentAccessBarrier.enterSignal(signal);
// ignore value
concurrentAccessBarrier.leaveSignal(signal);
}
@Override
public void onComplete() {
final String signal = "onComplete()";
concurrentAccessBarrier.enterSignal(signal);
// entering for completeness
concurrentAccessBarrier.leaveSignal(signal);
completionLatch.close();
}
});
completionLatch.expectClose(elements * env.defaultTimeoutMillis(), String.format("Failed in iteration %d of %d. Expected completion signal after signalling %d elements (signalled %d), yet did not receive it", runNumber, iterations, elements, gotElements.get()));
}
});
return null;
}
});
}
use of org.reactivestreams.tck.TestEnvironment.Latch in project reactive-streams-jvm by reactive-streams.
the class PublisherVerification method required_spec303_mustNotAllowUnboundedRecursion.
// Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.3
@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);
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();
}
}
});
}
use of org.reactivestreams.tck.TestEnvironment.Latch in project reactive-streams-jvm by reactive-streams.
the class PublisherVerification method required_spec109_mustIssueOnSubscribeForNonNullSubscriber.
// Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9
@Override
@Test
public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable {
activePublisherTest(0, false, new PublisherTestRun<T>() {
@Override
public void run(Publisher<T> pub) throws Throwable {
final Latch onSubscribeLatch = new Latch(env);
pub.subscribe(new Subscriber<T>() {
@Override
public void onError(Throwable cause) {
onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always");
}
@Override
public void onSubscribe(Subscription subs) {
onSubscribeLatch.assertOpen("Only one onSubscribe call expected");
onSubscribeLatch.close();
}
@Override
public void onNext(T elem) {
onSubscribeLatch.assertClosed("onSubscribe should be called prior to onNext always");
}
@Override
public void onComplete() {
onSubscribeLatch.assertClosed("onSubscribe should be called prior to onComplete always");
}
});
onSubscribeLatch.expectClose("Should have received onSubscribe");
env.verifyNoAsyncErrorsNoDelay();
}
});
}
Aggregations