/** * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed * subscriber reaches 1 and disconnect after the specified * timeout if all subscribers have unsubscribed. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator itself doesn't interfere with backpressure which is determined by the upstream * {@code ConnectableFlowable}'s backpressure behavior.</dd> * <dt><b>Scheduler:</b></dt> * <dd>This {@code refCount} overload operates on the specified {@link Scheduler}.</dd> * </dl> * <p>History: 2.1.14 - experimental * @param timeout the time to wait before disconnecting after all subscribers unsubscribed * @param unit the time unit of the timeout * @param scheduler the target scheduler to wait on before disconnecting * @return the new Flowable instance * @since 2.2 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.CUSTOM) @BackpressureSupport(BackpressureKind.PASS_THROUGH) public final Flowable<T> refCount(long timeout, TimeUnit unit, Scheduler scheduler) { return refCount(1, timeout, unit, scheduler); }
/** * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed * subscriber reaches the specified count and disconnect if all subscribers have unsubscribed. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator itself doesn't interfere with backpressure which is determined by the upstream * {@code ConnectableFlowable}'s backpressure behavior.</dd> * <dt><b>Scheduler:</b></dt> * <dd>This {@code refCount} overload does not operate on any particular {@link Scheduler}.</dd> * </dl> * <p>History: 2.1.14 - experimental * @param subscriberCount the number of subscribers required to connect to the upstream * @return the new Flowable instance * @since 2.2 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @BackpressureSupport(BackpressureKind.PASS_THROUGH) public final Flowable<T> refCount(int subscriberCount) { return refCount(subscriberCount, 0, TimeUnit.NANOSECONDS, Schedulers.trampoline()); }
@Test public void replayNoLeak() throws Exception { System.gc(); Thread.sleep(100); long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); source = Flowable.fromCallable(new Callable<Object>() { @Override public Object call() throws Exception { return new byte[100 * 1000 * 1000]; } }) .replay(1) .refCount(); source.subscribe(); System.gc(); Thread.sleep(100); long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); source = null; assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); }
/** * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed * subscriber reaches 1 and disconnect after the specified * timeout if all subscribers have unsubscribed. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator itself doesn't interfere with backpressure which is determined by the upstream * {@code ConnectableFlowable}'s backpressure behavior.</dd> * <dt><b>Scheduler:</b></dt> * <dd>This {@code refCount} overload operates on the {@code computation} {@link Scheduler}.</dd> * </dl> * <p>History: 2.1.14 - experimental * @param timeout the time to wait before disconnecting after all subscribers unsubscribed * @param unit the time unit of the timeout * @return the new Flowable instance * @see #refCount(long, TimeUnit, Scheduler) * @since 2.2 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.COMPUTATION) @BackpressureSupport(BackpressureKind.PASS_THROUGH) public final Flowable<T> refCount(long timeout, TimeUnit unit) { return refCount(1, timeout, unit, Schedulers.computation()); }
/** * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed * subscriber reaches the specified count and disconnect after the specified * timeout if all subscribers have unsubscribed. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator itself doesn't interfere with backpressure which is determined by the upstream * {@code ConnectableFlowable}'s backpressure behavior.</dd> * <dt><b>Scheduler:</b></dt> * <dd>This {@code refCount} overload operates on the {@code computation} {@link Scheduler}.</dd> * </dl> * <p>History: 2.1.14 - experimental * @param subscriberCount the number of subscribers required to connect to the upstream * @param timeout the time to wait before disconnecting after all subscribers unsubscribed * @param unit the time unit of the timeout * @return the new Flowable instance * @see #refCount(int, long, TimeUnit, Scheduler) * @since 2.2 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.COMPUTATION) @BackpressureSupport(BackpressureKind.PASS_THROUGH) public final Flowable<T> refCount(int subscriberCount, long timeout, TimeUnit unit) { return refCount(subscriberCount, timeout, unit, Schedulers.computation()); }
/** * Returns a new {@link Publisher} that multicasts (and shares a single subscription to) the original {@link Publisher}. As long as * there is at least one {@link Subscriber} this {@link Publisher} will be subscribed and emitting data. * When all subscribers have canceled it will cancel the source {@link Publisher}. * <p> * This is an alias for {@link #publish()}.{@link ConnectableFlowable#refCount() refCount()}. * <p> * <img width="640" height="510" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/publishRefCount.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator will signal a {@code MissingBackpressureException} to * its {@code Subscriber}s.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code share} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * * @return a {@code Publisher} that upon connection causes the source {@code Publisher} to emit items * to its {@link Subscriber}s * @see <a href="http://reactivex.io/documentation/operators/refcount.html">ReactiveX operators documentation: RefCount</a> */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> share() { return publish().refCount(); }
@Test public void publishNoLeak() throws Exception { System.gc(); Thread.sleep(100); long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); source = Flowable.fromCallable(new Callable<Object>() { @Override public Object call() throws Exception { throw new ExceptionData(new byte[100 * 1000 * 1000]); } }) .publish() .refCount(); source.subscribe(Functions.emptyConsumer(), Functions.emptyConsumer()); System.gc(); Thread.sleep(100); long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); source = null; assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); }
@Test public void publishNoLeak2() throws Exception { System.gc(); Thread.sleep(100); long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); source = Flowable.fromCallable(new Callable<Object>() { @Override public Object call() throws Exception { return new byte[100 * 1000 * 1000]; } }).concatWith(Flowable.never()) .publish() .refCount(); Disposable d1 = source.test(); Disposable d2 = source.test(); d1.dispose(); d2.dispose(); d1 = null; d2 = null; System.gc(); Thread.sleep(100); long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); source = null; assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); }
@Test public void replayNoLeak2() throws Exception { System.gc(); Thread.sleep(100); long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); source = Flowable.fromCallable(new Callable<Object>() { @Override public Object call() throws Exception { return new byte[100 * 1000 * 1000]; } }).concatWith(Flowable.never()) .replay(1) .refCount(); Disposable d1 = source.subscribe(); Disposable d2 = source.subscribe(); d1.dispose(); d2.dispose(); d1 = null; d2 = null; System.gc(); Thread.sleep(100); long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); source = null; assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); }
@Test public void testAlreadyUnsubscribedClient() { Subscriber<Integer> done = CancelledSubscriber.INSTANCE; Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); Flowable<Integer> result = Flowable.just(1).publish().refCount(); result.subscribe(done); result.subscribe(subscriber); verify(subscriber).onNext(1); verify(subscriber).onComplete(); verify(subscriber, never()).onError(any(Throwable.class)); }
@Test public void error() { Flowable.<Integer>error(new IOException()) .publish() .refCount(500, TimeUnit.MILLISECONDS) .test() .assertFailure(IOException.class); }
@Test public void testRefCountSynchronousTake() { final AtomicInteger nextCount = new AtomicInteger(); Flowable<Integer> r = Flowable.just(1, 2, 3, 4, 5, 6, 7, 8, 9) .doOnNext(new Consumer<Integer>() { @Override public void accept(Integer l) { System.out.println("onNext --------> " + l); nextCount.incrementAndGet(); } }) .take(4) .publish().refCount(); final AtomicInteger receivedCount = new AtomicInteger(); r.subscribe(new Consumer<Integer>() { @Override public void accept(Integer l) { receivedCount.incrementAndGet(); } }); System.out.println("onNext: " + nextCount.get()); assertEquals(4, receivedCount.get()); assertEquals(4, receivedCount.get()); }
@Test public void disposed() { TestHelper.checkDisposed(Flowable.just(1).publish().refCount()); }
@Test public void testAlreadyUnsubscribedInterleavesWithClient() { ReplayProcessor<Integer> source = ReplayProcessor.create(); Subscriber<Integer> done = CancelledSubscriber.INSTANCE; Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); InOrder inOrder = inOrder(subscriber); Flowable<Integer> result = source.publish().refCount(); result.subscribe(subscriber); source.onNext(1); result.subscribe(done); source.onNext(2); source.onComplete(); inOrder.verify(subscriber).onNext(1); inOrder.verify(subscriber).onNext(2); inOrder.verify(subscriber).onComplete(); verify(subscriber, never()).onError(any(Throwable.class)); }
@Test public void replayRefCountShallBeThreadSafe() { for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { Flowable<Integer> flowable = Flowable.just(1).replay(1).refCount(); TestSubscriber<Integer> ts1 = flowable .subscribeOn(Schedulers.io()) .test(); TestSubscriber<Integer> ts2 = flowable .subscribeOn(Schedulers.io()) .test(); ts1 .withTag("" + i) .awaitDone(5, TimeUnit.SECONDS) .assertResult(1); ts2 .withTag("" + i) .awaitDone(5, TimeUnit.SECONDS) .assertResult(1); } }
@Test public void disconnectBeforeConnect() { BehaviorProcessor<Integer> processor = BehaviorProcessor.create(); Flowable<Integer> flowable = processor .replay(1) .refCount(); flowable.takeUntil(Flowable.just(1)).test(); processor.onNext(2); flowable.take(1).test().assertResult(2); } }
@Test public void letitTimeout() throws Exception { final int[] subscriptions = { 0 }; PublishProcessor<Integer> pp = PublishProcessor.create(); Flowable<Integer> source = pp .doOnSubscribe(new Consumer<Subscription>() { @Override public void accept(Subscription s) throws Exception { subscriptions[0]++; } }) .publish() .refCount(1, 100, TimeUnit.MILLISECONDS); TestSubscriber<Integer> ts1 = source.test(0); assertEquals(1, subscriptions[0]); ts1.cancel(); assertTrue(pp.hasSubscribers()); Thread.sleep(200); assertFalse(pp.hasSubscribers()); }
@Test public void comeAndGo() { PublishProcessor<Integer> pp = PublishProcessor.create(); Flowable<Integer> source = pp .publish() .refCount(1); TestSubscriber<Integer> ts1 = source.test(0); assertTrue(pp.hasSubscribers()); for (int i = 0; i < 3; i++) { TestSubscriber<Integer> ts2 = source.test(); ts1.cancel(); ts1 = ts2; } ts1.cancel(); assertFalse(pp.hasSubscribers()); }
@Test public void byCount() { final int[] subscriptions = { 0 }; Flowable<Integer> source = Flowable.range(1, 5) .doOnSubscribe(new Consumer<Subscription>() { @Override public void accept(Subscription s) throws Exception { subscriptions[0]++; } }) .publish() .refCount(2); for (int i = 0; i < 3; i++) { TestSubscriber<Integer> ts1 = source.test(); ts1.assertEmpty(); TestSubscriber<Integer> ts2 = source.test(); ts1.assertResult(1, 2, 3, 4, 5); ts2.assertResult(1, 2, 3, 4, 5); } assertEquals(3, subscriptions[0]); }
@Test public void testConnectDisconnectConnectAndSubjectState() { Flowable<Integer> f1 = Flowable.just(10); Flowable<Integer> f2 = Flowable.just(20); Flowable<Integer> combined = Flowable.combineLatest(f1, f2, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } }) .publish().refCount(); TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); combined.subscribe(ts1); combined.subscribe(ts2); ts1.assertTerminated(); ts1.assertNoErrors(); ts1.assertValue(30); ts2.assertTerminated(); ts2.assertNoErrors(); ts2.assertValue(30); }