/** * Can receive timeout if subscribe never receives an onError/onCompleted ... which reveals a race * condition. */ @Test(timeout = 10000) public void testSubscribeCompletionRaceCondition() { for (int i = 0; i < 50; i++) { final ReplaySubject<String> subject = ReplaySubject.create(); final AtomicReference<String> value1 = new AtomicReference<String>(); subject.subscribe( new Action1<String>() { @Override public void call(String t1) { try { // simulate a slow observer Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } value1.set(t1); } }); Thread t1 = new Thread( new Runnable() { @Override public void run() { subject.onNext("value"); subject.onCompleted(); } }); SubjectObserverThread t2 = new SubjectObserverThread(subject); SubjectObserverThread t3 = new SubjectObserverThread(subject); SubjectObserverThread t4 = new SubjectObserverThread(subject); SubjectObserverThread t5 = new SubjectObserverThread(subject); t2.start(); t3.start(); t1.start(); t4.start(); t5.start(); try { t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } assertEquals("value", value1.get()); assertEquals("value", t2.value.get()); assertEquals("value", t3.value.get()); assertEquals("value", t4.value.get()); assertEquals("value", t5.value.get()); } }
@Override public void run() { try { // a timeout exception will happen if we don't get a terminal state String v = subject.timeout(2000, TimeUnit.MILLISECONDS).toBlocking().single(); value.set(v); } catch (Exception e) { e.printStackTrace(); } }
@Test public void testErrorThrownIssue1685() { Subject<Object, Object> subject = ReplaySubject.create(); Observable.error(new RuntimeException("oops")) .materialize() .delay(1, TimeUnit.SECONDS) .dematerialize() .subscribe(subject); subject.subscribe(); subject.materialize().toBlocking().first(); System.out.println("Done"); }
@Test(timeout = 4000) public void testReplaySubjectConcurrentSubscribersDoingReplayDontBlockEachOther() throws InterruptedException { final ReplaySubject<Long> replay = ReplaySubject.create(); Thread source = new Thread( new Runnable() { @Override public void run() { Observable.create( new OnSubscribe<Long>() { @Override public void call(Subscriber<? super Long> o) { System.out.println("********* Start Source Data ***********"); for (long l = 1; l <= 10000; l++) { o.onNext(l); } System.out.println("********* Finished Source Data ***********"); o.onCompleted(); } }) .subscribe(replay); } }); source.start(); long v = replay.toBlocking().last(); assertEquals(10000, v); // it's been played through once so now it will all be replays final CountDownLatch slowLatch = new CountDownLatch(1); Thread slowThread = new Thread( new Runnable() { @Override public void run() { Subscriber<Long> slow = new Subscriber<Long>() { @Override public void onCompleted() { System.out.println("*** Slow Observer completed"); slowLatch.countDown(); } @Override public void onError(Throwable e) {} @Override public void onNext(Long args) { if (args == 1) { System.out.println("*** Slow Observer STARTED"); } try { if (args % 10 == 0) { Thread.sleep(1); } } catch (InterruptedException e) { e.printStackTrace(); } } }; replay.subscribe(slow); try { slowLatch.await(); } catch (InterruptedException e1) { e1.printStackTrace(); } } }); slowThread.start(); Thread fastThread = new Thread( new Runnable() { @Override public void run() { final CountDownLatch fastLatch = new CountDownLatch(1); Subscriber<Long> fast = new Subscriber<Long>() { @Override public void onCompleted() { System.out.println("*** Fast Observer completed"); fastLatch.countDown(); } @Override public void onError(Throwable e) {} @Override public void onNext(Long args) { if (args == 1) { System.out.println("*** Fast Observer STARTED"); } } }; replay.subscribe(fast); try { fastLatch.await(); } catch (InterruptedException e1) { e1.printStackTrace(); } } }); fastThread.start(); fastThread.join(); // slow should not yet be completed when fast completes assertEquals(1, slowLatch.getCount()); slowThread.join(); }
@Test public void testReplaySubjectConcurrentSubscriptions() throws InterruptedException { final ReplaySubject<Long> replay = ReplaySubject.create(); Thread source = new Thread( new Runnable() { @Override public void run() { Observable.create( new OnSubscribe<Long>() { @Override public void call(Subscriber<? super Long> o) { System.out.println("********* Start Source Data ***********"); for (long l = 1; l <= 10000; l++) { o.onNext(l); } System.out.println("********* Finished Source Data ***********"); o.onCompleted(); } }) .subscribe(replay); } }); // used to collect results of each thread final List<List<Long>> listOfListsOfValues = Collections.synchronizedList(new ArrayList<List<Long>>()); final List<Thread> threads = Collections.synchronizedList(new ArrayList<Thread>()); for (int i = 1; i <= 200; i++) { final int count = i; if (count == 20) { // start source data after we have some already subscribed // and while others are in process of subscribing source.start(); } if (count == 100) { // wait for source to finish then keep adding after it's done source.join(); } Thread t = new Thread( new Runnable() { @Override public void run() { List<Long> values = replay.toList().toBlocking().last(); listOfListsOfValues.add(values); System.out.println("Finished thread: " + count); } }); t.start(); System.out.println("Started thread: " + i); threads.add(t); } // wait for all threads to complete for (Thread t : threads) { t.join(); } // assert all threads got the same results List<Long> sums = new ArrayList<Long>(); for (List<Long> values : listOfListsOfValues) { long v = 0; for (long l : values) { v += l; } sums.add(v); } long expected = sums.get(0); boolean success = true; for (long l : sums) { if (l != expected) { success = false; System.out.println("FAILURE => Expected " + expected + " but got: " + l); } } if (success) { System.out.println("Success! " + sums.size() + " each had the same sum of " + expected); } else { throw new RuntimeException("Concurrency Bug"); } }