/* * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package reactor.core.publisher; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.reactivestreams.Processor; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import reactor.core.Disposable; import reactor.core.Scannable; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; import reactor.test.StepVerifier; import reactor.test.subscriber.AssertSubscriber; import reactor.util.concurrent.QueueSupplier; import static org.assertj.core.api.Assertions.assertThat; /** * @author Stephane Maldini */ public class EmitterProcessorTest { @Test public void testColdIdentityProcessor() throws InterruptedException { final int elements = 10; CountDownLatch latch = new CountDownLatch(elements + 1); Processor<Integer, Integer> processor = EmitterProcessor.create(16); List<Integer> list = new ArrayList<>(); processor.subscribe(new Subscriber<Integer>() { Subscription s; @Override public void onSubscribe(Subscription s) { this.s = s; s.request(1); } @Override public void onNext(Integer integer) { synchronized (list) { list.add(integer); } latch.countDown(); if (latch.getCount() > 0) { s.request(1); } } @Override public void onError(Throwable t) { t.printStackTrace(); } @Override public void onComplete() { System.out.println("completed!"); latch.countDown(); } }); Flux.range(1, 10) .subscribe(processor); //stream.broadcastComplete(); latch.await(8, TimeUnit.SECONDS); long count = latch.getCount(); org.junit.Assert.assertTrue("Count > 0 : " + count + " (" + list + ") , Running on " + Schedulers.DEFAULT_POOL_SIZE + " CPU", latch.getCount() == 0); } /*@Test public void test100Hot() throws InterruptedException { for (int i = 0; i < 10000; i++) { testHotIdentityProcessor(); } } */ @Test public void testHotIdentityProcessor() throws InterruptedException { final int elements = 10000; CountDownLatch latch = new CountDownLatch(elements); Processor<Integer, Integer> processor = EmitterProcessor.create(1024); EmitterProcessor<Integer> stream = EmitterProcessor.create(); FluxSink<Integer> session = stream.sink(); stream.subscribe(processor); processor.subscribe(new Subscriber<Integer>() { @Override public void onSubscribe(Subscription s) { s.request(elements); } @Override public void onNext(Integer integer) { latch.countDown(); } @Override public void onError(Throwable t) { System.out.println("error! " + t); } @Override public void onComplete() { System.out.println("completed!"); //latch.countDown(); } }); for (int i = 0; i < elements; i++) { session.next(i); } //stream.then(); latch.await(8, TimeUnit.SECONDS); long count = latch.getCount(); org.junit.Assert.assertTrue("Count > 0 : " + count + " , Running on " + Schedulers.DEFAULT_POOL_SIZE + " CPU", latch.getCount() == 0); stream.onComplete(); } @Test(expected = NullPointerException.class) public void onNextNull() { EmitterProcessor.create().onNext(null); } @Test(expected = NullPointerException.class) public void onErrorNull() { EmitterProcessor.create().onError(null); } @Test(expected = NullPointerException.class) public void onSubscribeNull() { EmitterProcessor.create().onSubscribe(null); } @Test(expected = NullPointerException.class) public void subscribeNull() { EmitterProcessor.create().subscribe((Subscriber<Object>)null); } @Test public void normal() { EmitterProcessor<Integer> tp = EmitterProcessor.create(); StepVerifier.create(tp) .then(() -> { Assert.assertTrue("No subscribers?", tp.hasDownstreams()); Assert.assertFalse("Completed?", tp.isTerminated()); Assert.assertNull("Has error?", tp.getError()); }) .then(() -> { tp.onNext(1); tp.onNext(2); }) .expectNext(1, 2) .then(() -> { tp.onNext(3); tp.onComplete(); }) .expectNext(3) .expectComplete() .verify(); Assert.assertFalse("Subscribers present?", tp.hasDownstreams()); Assert.assertTrue("Not completed?", tp.isTerminated()); Assert.assertNull("Has error?", tp.getError()); } @Test public void normalBackpressured() { EmitterProcessor<Integer> tp = EmitterProcessor.create(); StepVerifier.create(tp, 0L) .then(() -> { Assert.assertTrue("No subscribers?", tp.hasDownstreams()); Assert.assertFalse("Completed?", tp.isTerminated()); Assert.assertNull("Has error?", tp.getError()); }) .then(() -> { tp.onNext(1); tp.onNext(2); tp.onComplete(); }) .thenRequest(10L) .expectNext(1, 2) .expectComplete() .verify(); Assert.assertFalse("Subscribers present?", tp.hasDownstreams()); Assert.assertTrue("Not completed?", tp.isTerminated()); Assert.assertNull("Has error?", tp.getError()); } @Test public void normalAtomicRingBufferBackpressured() { EmitterProcessor<Integer> tp = EmitterProcessor.create(100); StepVerifier.create(tp, 0L) .then(() -> { Assert.assertTrue("No subscribers?", tp.hasDownstreams()); Assert.assertFalse("Completed?", tp.isTerminated()); Assert.assertNull("Has error?", tp.getError()); }) .then(() -> { tp.onNext(1); tp.onNext(2); tp.onComplete(); }) .thenRequest(10L) .expectNext(1, 2) .expectComplete() .verify(); Assert.assertFalse("Subscribers present?", tp.hasDownstreams()); Assert.assertTrue("Not completed?", tp.isTerminated()); Assert.assertNull("Has error?", tp.getError()); } @Test public void state(){ EmitterProcessor<Integer> tp = EmitterProcessor.create(); assertThat(tp.getPending()).isEqualTo(0); assertThat(tp.getBufferSize()).isEqualTo(QueueSupplier.SMALL_BUFFER_SIZE); assertThat(tp.isCancelled()).isFalse(); assertThat(tp.inners()).isEmpty(); Disposable d1 = tp.subscribe(); assertThat(tp.inners()).hasSize(1); FluxSink<Integer> s = tp.sink(); s.next(2); s.next(3); s.next(4); assertThat(tp.getPending()).isEqualTo(0); AtomicReference<Subscription> d2 = new AtomicReference<>(); tp.subscribe(new Subscriber<Integer>() { @Override public void onSubscribe(Subscription s) { d2.set(s); } @Override public void onNext(Integer integer) { } @Override public void onError(Throwable t) { } @Override public void onComplete() { } }); s.next(5); s.next(6); s.next(7); assertThat(tp.scan(Scannable.Attr.BUFFERED)).isEqualTo(3); assertThat(tp.isTerminated()).isFalse(); s.complete(); assertThat(tp.isTerminated()).isFalse(); d1.dispose(); d2.get().cancel(); assertThat(tp.isTerminated()).isTrue(); StepVerifier.create(tp) .verifyComplete(); tp.onNext(8); //noop EmitterProcessor<Void> empty = EmitterProcessor.create(); empty.onComplete(); assertThat(empty.isTerminated()).isTrue(); } @Test(expected = IllegalArgumentException.class) public void failNullBufferSize() { EmitterProcessor.create(0); } @Test(expected = NullPointerException.class) public void failNullNext() { EmitterProcessor.create().onNext(null); } @Test(expected = NullPointerException.class) public void failNullError() { EmitterProcessor.create().onError(null); } @Test public void failDoubleError() { EmitterProcessor<Integer> ep = EmitterProcessor.create(); StepVerifier.create(ep) .then(() -> { assertThat(ep.getError()).isNull(); ep.onError(new Exception("test")); assertThat(ep.getError()).hasMessage("test"); ep.onError(new Exception("test2")); }) .expectErrorMessage("test") .verifyThenAssertThat() .hasDroppedErrorWithMessage("test2"); } @Test public void failCompleteThenError() { EmitterProcessor<Integer> ep = EmitterProcessor.create(); StepVerifier.create(ep) .then(() -> { ep.onComplete(); ep.onComplete();//noop ep.onError(new Exception("test")); }) .expectComplete() .verifyThenAssertThat() .hasDroppedErrorWithMessage("test"); } @Test public void ignoreDoubleOnSubscribe() { EmitterProcessor<Integer> ep = EmitterProcessor.create(); ep.sink(); assertThat(ep.sink().isCancelled()).isTrue(); } @Test(expected = IllegalArgumentException.class) public void failNegativeBufferSize() { EmitterProcessor.create(-1); } static final List<String> DATA = new ArrayList<>(); static final int MAX_SIZE = 100; static { for (int i = 1; i <= MAX_SIZE; i++) { DATA.add("" + i); } } @Test @Ignore public void test() { Scheduler asyncGroup = Schedulers.single(); FluxProcessor<String, String> emitter = EmitterProcessor.create(); CountDownLatch requestReceived = new CountDownLatch(1); AtomicLong demand = new AtomicLong(0); Publisher<String> publisher = s -> s.onSubscribe(new Subscription() { @Override public void request(long n) { System.out.println("request: " + n + " " + s); demand.addAndGet(n); requestReceived.countDown(); } @Override public void cancel() { System.out.println("cancel"); } }); Flux.from(publisher).subscribeOn(asyncGroup).subscribe(emitter); AssertSubscriber<String> subscriber = AssertSubscriber.create(); emitter.subscribe(subscriber); int i = 0; for (; ; ) { if (getAndSub(demand, 1) != 0) { emitter.onNext("" + (i++)); } else { System.out.println("NO REQUESTED: " + emitter); LockSupport.parkNanos(100_000_000); } } } @Test @Ignore public void testPerformance() { FluxProcessor<String, String> emitter = EmitterProcessor.create(); CountDownLatch requestReceived = new CountDownLatch(1); AtomicLong maxDelay = new AtomicLong(0); AtomicLong demand = new AtomicLong(0); Publisher<String> publisher = new Publisher<String>() { long lastTimeRequestReceivedNs = -1; @Override public void subscribe(Subscriber<? super String> s) { s.onSubscribe(new Subscription() { @Override public void request(long n) { requestReceived.countDown(); long now = System.nanoTime(); if (lastTimeRequestReceivedNs > 0) { maxDelay.set(now - lastTimeRequestReceivedNs); } lastTimeRequestReceivedNs = now; demand.addAndGet(n); } @Override public void cancel() { System.out.println("cancel"); } }); } }; publisher.subscribe(emitter); AssertSubscriber<String> subscriber = AssertSubscriber.create(); emitter.subscribe(subscriber); String buffer = "Hello"; int i = 0; for (; ; ) { if (getAndSub(demand, 1) > 0) { emitter.onNext(buffer); } if (i++ % 1000000 == 0) { System.out.println("maxDelay: " + TimeUnit.MICROSECONDS.toMillis(maxDelay.get()) + " µs"); } } } @Test public void testRed() { FluxProcessor<String, String> processor = EmitterProcessor.create(); AssertSubscriber<String> subscriber = AssertSubscriber.create(1); processor.subscribe(subscriber); Flux.fromIterable(DATA) .log() .subscribe(processor); subscriber.awaitAndAssertNextValues("1"); } @Test public void testGreen() { FluxProcessor<String, String> processor = EmitterProcessor.create(); AssertSubscriber<String> subscriber = AssertSubscriber.create(1); processor.subscribe(subscriber); Flux.fromIterable(DATA) .log() .subscribe(processor); subscriber.awaitAndAssertNextValues("1"); } @Test public void testHanging() { FluxProcessor<String, String> processor = EmitterProcessor.create(2); AssertSubscriber<String> first = AssertSubscriber.create(0); processor.log("after-1").subscribe(first); AssertSubscriber<String> second = AssertSubscriber.create(0); processor.log("after-2").subscribe(second); Flux.fromIterable(DATA) .log() .subscribe(processor); second.request(1); second.assertNoValues(); first.request(3); second.awaitAndAssertNextValues("1"); second.cancel(); first.awaitAndAssertNextValues("1", "2", "3"); first.cancel(); assertThat(processor.scanOrDefault(Scannable.Attr.CANCELLED, false)).isTrue(); } @Test public void testNPE() { FluxProcessor<String, String> processor = EmitterProcessor.create(8); AssertSubscriber<String> first = AssertSubscriber.create(1); processor.log().take(1).subscribe(first); AssertSubscriber<String> second = AssertSubscriber.create(3); processor.log().subscribe(second); Flux.fromIterable(DATA) .log() .subscribe(processor); first.awaitAndAssertNextValues("1"); second.awaitAndAssertNextValues("1", "2", "3"); } static class MyThread extends Thread { private final Flux<String> processor; private final CyclicBarrier barrier; private final int n; private volatile Throwable lastException; class MyUncaughtExceptionHandler implements UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { lastException = e; } } public MyThread(FluxProcessor<String, String> processor, CyclicBarrier barrier, int n, int index) { this.processor = processor.log("consuming."+index); this.barrier = barrier; this.n = n; setUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); } @Override public void run() { try { doRun(); } catch (Exception e) { throw new RuntimeException(e); } } public void doRun() throws Exception { AssertSubscriber<String> subscriber = AssertSubscriber.create(5); processor.subscribe(subscriber); barrier.await(); subscriber.request(3); subscriber.request(4); subscriber.request(1); subscriber .await() .assertValueCount(n) .assertComplete(); } public Throwable getLastException() { return lastException; } } @Test @Ignore public void testRacing() throws Exception { int N_THREADS = 3; int N_ITEMS = 8; FluxProcessor<String, String> processor = EmitterProcessor.create(4); List<String> data = new ArrayList<>(); for (int i = 1; i <= N_ITEMS; i++) { data.add(String.valueOf(i)); } Flux.fromIterable(data) .log("publishing") .subscribe(processor); CyclicBarrier barrier = new CyclicBarrier(N_THREADS); MyThread threads[] = new MyThread[N_THREADS]; for (int i = 0; i < N_THREADS; i++) { threads[i] = new MyThread(processor, barrier, N_ITEMS, i); threads[i].start(); } for (int j = 0; j < N_THREADS; j++) { threads[j].join(); Throwable lastException = threads[j].getLastException(); if (lastException != null) { lastException.printStackTrace(); Assert.fail(); } } } /** * Concurrent substraction bound to 0 and Long.MAX_VALUE. * Any concurrent write will "happen" before this operation. * * @param sequence current atomic to update * @param toSub delta to sub * @return value before subscription, 0 or Long.MAX_VALUE */ public static long getAndSub(AtomicLong sequence, long toSub) { long r, u; do { r = sequence.get(); if (r == 0 || r == Long.MAX_VALUE) { return r; } u = Operators.subOrZero(r, toSub); } while (!sequence.compareAndSet(r, u)); return r; } }