use of org.reactivestreams.Publisher in project ratpack by ratpack.
the class FlattenPublisher method subscribe.
@Override
public void subscribe(Subscriber<? super T> subscriber) {
subscriber.onSubscribe(new ManagedSubscription<T>(subscriber, disposer) {
private Subscription outerSubscription;
private Subscription innerSubscription;
private final AtomicReference<State> state = new AtomicReference<>(State.INIT);
volatile boolean pendingComplete;
@Override
protected void onRequest(long n) {
if (state.compareAndSet(State.INIT, State.SUBSCRIBE)) {
if (outerSubscription == null) {
subscribeUpstream();
}
} else if (innerSubscription != null) {
innerSubscription.request(n);
} else {
nextPublisher();
}
}
private void subscribeUpstream() {
publisher.subscribe(new Subscriber<Publisher<T>>() {
@Override
public void onSubscribe(Subscription subscription) {
outerSubscription = subscription;
outerSubscription.request(1);
}
@Override
public void onNext(Publisher<T> next) {
next.subscribe(new Subscriber<T>() {
@Override
public void onSubscribe(Subscription s) {
innerSubscription = s;
state.set(State.EMITTING);
innerSubscription.request(getDemand());
}
@Override
public void onNext(T t) {
emitNext(t);
}
@Override
public void onError(Throwable t) {
outerSubscription.cancel();
emitError(t);
}
@Override
public void onComplete() {
innerSubscription = null;
state.set(State.IDLE);
nextPublisher();
}
});
}
@Override
public void onError(Throwable t) {
if (innerSubscription != null) {
innerSubscription.cancel();
innerSubscription = null;
}
emitError(t);
}
@Override
public void onComplete() {
pendingComplete = true;
}
});
}
@Override
protected void onCancel() {
if (innerSubscription != null) {
innerSubscription.cancel();
innerSubscription = null;
}
if (outerSubscription != null) {
outerSubscription.cancel();
outerSubscription = null;
}
}
private void nextPublisher() {
if (state.compareAndSet(State.IDLE, State.PENDING)) {
if (pendingComplete) {
emitComplete();
} else if (hasDemand()) {
outerSubscription.request(1);
} else {
state.set(State.IDLE);
if (hasDemand()) {
nextPublisher();
}
}
}
}
});
}
use of org.reactivestreams.Publisher 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.Publisher in project reactive-streams-jvm by reactive-streams.
the class PublisherVerificationTest method demandIgnoringAsynchronousPublisherVerification.
/**
* Verification using a Publisher that publishes elements even with no demand available, from multiple threads (!).
*/
final PublisherVerification<Integer> demandIgnoringAsynchronousPublisherVerification(final ExecutorService signallersPool, final boolean swallowOnNextExceptions) {
final AtomicInteger startedSignallingThreads = new AtomicInteger(0);
final int maxSignallingThreads = 2;
final AtomicBoolean concurrentAccessCaused = new AtomicBoolean(false);
return new PublisherVerification<Integer>(newTestEnvironment()) {
@Override
public Publisher<Integer> createPublisher(long elements) {
return new Publisher<Integer>() {
@Override
public void subscribe(final Subscriber<? super Integer> s) {
s.onSubscribe(new NoopSubscription() {
@Override
public void request(final long n) {
Runnable signalling = new Runnable() {
@Override
public void run() {
for (long i = 0; i <= n; i++) {
try {
final long signal = i;
signallersPool.execute(new Runnable() {
@Override
public void run() {
try {
s.onNext((int) signal);
} catch (Exception ex) {
if (!swallowOnNextExceptions) {
throw new RuntimeException("onNext threw an exception!", ex);
} else {
// yes, swallow the exception, we're not asserting and they'd just end up being logged (stdout),
// which we do not need in this specific PublisherVerificationTest
}
}
}
});
} catch (Exception ex) {
if (ex instanceof TestEnvironment.Latch.ExpectedOpenLatchException) {
if (concurrentAccessCaused.compareAndSet(false, true)) {
throw new RuntimeException("Concurrent access detected", ex);
} else {
// error signalled once already, stop more errors from propagating
return;
}
} else if (ex instanceof RejectedExecutionException) {
// ignore - this may happen since one thread may have already gotten into a concurrent access
// problem and initiated the pool's shutdown. It will then throw RejectedExecutionException.
} else {
if (concurrentAccessCaused.get()) {
return;
} else {
throw new RuntimeException(ex);
}
}
}
}
}
};
// must be guarded like this in case a Subscriber triggers request() synchronously from it's onNext()
while (startedSignallingThreads.getAndAdd(1) < maxSignallingThreads) {
signallersPool.execute(signalling);
}
}
});
}
};
}
@Override
public Publisher<Integer> createFailedPublisher() {
return SKIP;
}
};
}
use of org.reactivestreams.Publisher in project reactive-streams-jvm by reactive-streams.
the class PublisherVerificationTest method required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue_forSynchronousPublisher.
@Test
public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue_forSynchronousPublisher() throws Throwable {
final AtomicInteger sent = new AtomicInteger();
customPublisherVerification(new Publisher<Integer>() {
@Override
public void subscribe(final Subscriber<? super Integer> downstream) {
downstream.onSubscribe(new Subscription() {
boolean started;
boolean cancelled;
@Override
public void request(long n) {
if (!started) {
started = true;
while (!cancelled) {
downstream.onNext(sent.getAndIncrement());
}
}
}
@Override
public void cancel() {
cancelled = true;
}
});
}
}).required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue();
// 11 due to the implementation of this particular TCK test (see impl)
Assert.assertEquals(sent.get(), 11);
}
use of org.reactivestreams.Publisher in project reactive-streams-jvm by reactive-streams.
the class PublisherVerificationTest method stochastic_spec103_mustSignalOnMethodsSequentially_shouldFailBy_concurrentlyAccessingOnNext.
@Test
public void stochastic_spec103_mustSignalOnMethodsSequentially_shouldFailBy_concurrentlyAccessingOnNext() throws Throwable {
final AtomicInteger startedSignallingThreads = new AtomicInteger(0);
// this is an arbitrary number, we just need "many threads" to try to force an concurrent access scenario
final int maxSignallingThreads = 10;
final ExecutorService signallersPool = Executors.newFixedThreadPool(maxSignallingThreads);
final AtomicBoolean concurrentAccessCaused = new AtomicBoolean(false);
// highly specialised threadpool driven publisher which aims to FORCE concurrent access,
// so that we can confirm the test is able to catch this.
final Publisher<Integer> concurrentAccessPublisher = new Publisher<Integer>() {
@Override
public void subscribe(final Subscriber<? super Integer> s) {
s.onSubscribe(new NoopSubscription() {
@Override
public void request(final long n) {
Runnable signalling = new Runnable() {
@Override
public void run() {
for (long i = 0; i < n; i++) {
try {
// shutdown cleanly in when the threadpool is shutting down
if (Thread.interrupted()) {
return;
}
s.onNext((int) i);
} catch (Exception ex) {
// signal others to shut down
signallersPool.shutdownNow();
if (ex instanceof TestEnvironment.Latch.ExpectedOpenLatchException) {
if (!concurrentAccessCaused.getAndSet(true)) {
throw new RuntimeException("Concurrent access detected", ex);
} else {
// error signalled once already, stop more errors from propagating
return;
}
} else {
throw new RuntimeException(ex);
}
}
}
}
};
// must be guarded like this in case a Subscriber triggers request() synchronously from it's onNext()
while (startedSignallingThreads.getAndAdd(1) < maxSignallingThreads && !signallersPool.isShutdown()) {
try {
signallersPool.execute(signalling);
} catch (RejectedExecutionException ex) {
// ignore, should be safe as it means the pool is shutting down -> which means we triggered the problem we wanted to
return;
}
}
}
});
}
};
try {
requireTestFailure(new ThrowingRunnable() {
@Override
public void run() throws Throwable {
customPublisherVerification(concurrentAccessPublisher).stochastic_spec103_mustSignalOnMethodsSequentially();
}
}, "Illegal concurrent access detected");
} finally {
signallersPool.shutdownNow();
signallersPool.awaitTermination(1, TimeUnit.SECONDS);
}
}
Aggregations