/** * Connects this {@link ConnectableFlux} to the upstream source when the first {@link org.reactivestreams.Subscriber} * subscribes. * * <p> * <img class="marble" src="doc-files/marbles/autoConnect.svg" alt=""> * * @return a {@link Flux} that connects to the upstream source when the first {@link org.reactivestreams.Subscriber} subscribes */ public final Flux<T> autoConnect() { return autoConnect(1); }
/** * Connects this {@link ConnectableFlux} to the upstream source when the specified amount of * {@link org.reactivestreams.Subscriber} subscribes. * <p> * Subscribing and immediately unsubscribing still contributes to the counter that * triggers the connection. * * <p> * <img class="marble" src="doc-files/marbles/autoConnectWithMinSubscribers.svg" alt=""> * * @param minSubscribers the minimum number of subscribers * * @return a {@link Flux} that connects to the upstream source when the given amount of Subscribers subscribed */ public final Flux<T> autoConnect(int minSubscribers) { return autoConnect(minSubscribers, NOOP_DISCONNECT); }
/** * Turn this {@link Flux} into a hot source and cache last emitted signals for further {@link Subscriber}. * Will retain up to the given history size onNext signals. Completion and Error will also be * replayed. * <p> * Note that {@code cache(0)} will only cache the terminal signal without * expiration. * <p> * <img class="marble" src="doc-files/marbles/cacheWithHistoryLimitForFlux.svg" alt=""> * * @param history number of elements retained in cache * * @return a replaying {@link Flux} * */ public final Flux<T> cache(int history) { return replay(history).autoConnect(); }
/** * Turn this {@link Flux} into a hot source and cache last emitted signals for further * {@link Subscriber}. Will retain up to the given history size and apply a per-item expiry * timeout. * <p> * Completion and Error will also be replayed until {@code ttl} triggers in which case * the next {@link Subscriber} will start over a new subscription. * <p> * <img class="marble" src="doc-files/marbles/cacheWithTtlAndMaxLimitForFlux.svg" * alt=""> * * @param history number of elements retained in cache * @param ttl Time-to-live for each cached item and post termination. * @param timer the {@link Scheduler} on which to measure the duration. * * @return a replaying {@link Flux} */ public final Flux<T> cache(int history, Duration ttl, Scheduler timer) { return replay(history, ttl, timer).autoConnect(); }
protected InstanceEventPublisher() { UnicastProcessor<InstanceEvent> unicastProcessor = UnicastProcessor.create(); this.publishedFlux = unicastProcessor.publish().autoConnect(0); this.sink = unicastProcessor.sink(); }
private SubmissionPublisher(Scheduler scheduler, int maxBufferCapacity) { if (scheduler == null){ throw new NullPointerException(); } if (maxBufferCapacity <= 0){ throw new IllegalArgumentException("capacity must be positive"); } UnicastProcessor<T> processor = UnicastProcessor.<T>create(); sink = processor.sink(); flux = processor .publish(maxBufferCapacity) .autoConnect() .subscribeOn(Schedulers.immediate()) .publishOn(scheduler); numberOfSubscribers = new AtomicInteger(0); }
@Override protected List<Scenario<String, String>> scenarios_touchAndAssertState() { return Arrays.asList( scenario(f -> f.replay().autoConnect()) ); }
@Override public <U> UpdateHandlerRegistration<U> registerUpdateHandler( SubscriptionQueryMessage<?, ?, ?> query, SubscriptionQueryBackpressure backpressure, int updateBufferSize) { EmitterProcessor<SubscriptionQueryUpdateMessage<U>> processor = EmitterProcessor.create(updateBufferSize); FluxSink<SubscriptionQueryUpdateMessage<U>> sink = processor.sink(backpressure.getOverflowStrategy()); sink.onDispose(() -> updateHandlers.remove(query)); FluxSinkWrapper<SubscriptionQueryUpdateMessage<U>> fluxSinkWrapper = new FluxSinkWrapper<>(sink); updateHandlers.put(query, fluxSinkWrapper); Registration registration = () -> { fluxSinkWrapper.complete(); return true; }; return new UpdateHandlerRegistration<>(registration, processor.replay(updateBufferSize).autoConnect()); }
@Test public void advancedConnectableAutoConnect() throws InterruptedException { Flux<Integer> source = Flux.range(1, 3) .doOnSubscribe(s -> System.out.println("subscribed to source")); Flux<Integer> autoCo = source.publish().autoConnect(2); autoCo.subscribe(System.out::println, e -> {}, () -> {}); System.out.println("subscribed first"); Thread.sleep(500); System.out.println("subscribing second"); autoCo.subscribe(System.out::println, e -> {}, () -> {}); }
@Test public void fluxInitialValueAvailableOnceIfBroadcasted() { // "A deferred Flux with an initial value makes that value available once if broadcasted" // given: "a composable with an initial value" Flux<String> stream = Flux.just("test") .publish() .autoConnect(); // when: "the value is retrieved" AtomicReference<String> value = new AtomicReference<>(); AtomicReference<String> value2 = new AtomicReference<>(); stream.subscribe(value::set); stream.subscribe(value2::set); // then: "it is available in value 1 but value 2 has subscribed after dispatching" assertThat(value.get()).isEqualTo("test"); assertThat(value2.get()).isNullOrEmpty(); }
@Override protected List<Scenario<String, String>> scenarios_operatorSuccess() { return Arrays.asList( scenario(f -> f.publish().autoConnect()), scenario(f -> f.publish().refCount()) ); }
@Override protected List<Scenario<String, String>> scenarios_operatorSuccess() { return Arrays.asList( scenario(f -> f.replay().autoConnect()), scenario(f -> f.replay().refCount()) ); }
@Test public void connectImmediately() { EmitterProcessor<Integer> e = EmitterProcessor.create(); AtomicReference<Disposable> cancel = new AtomicReference<>(); e.publish().autoConnect(0, cancel::set); Assert.assertNotNull(cancel.get()); Assert.assertTrue("sp has no subscribers?", e.downstreamCount() != 0); cancel.get().dispose(); Assert.assertFalse("sp has subscribers?", e.downstreamCount() != 0); }
@Test public void connectAfterMany() { EmitterProcessor<Integer> e = EmitterProcessor.create(); AtomicReference<Disposable> cancel = new AtomicReference<>(); Flux<Integer> p = e.publish().autoConnect(2, cancel::set); Assert.assertNull(cancel.get()); Assert.assertFalse("sp has subscribers?", e.downstreamCount() != 0); p.subscribe(AssertSubscriber.create()); Assert.assertNull(cancel.get()); Assert.assertFalse("sp has subscribers?", e.downstreamCount() != 0); p.subscribe(AssertSubscriber.create()); Assert.assertNotNull(cancel.get()); Assert.assertTrue("sp has no subscribers?", e.downstreamCount() != 0); cancel.get().dispose(); Assert.assertFalse("sp has subscribers?", e.downstreamCount() != 0); }
@Test public void cacheFluxHistoryTTL() { Flux<Tuple2<Long, Integer>> source = Flux.just(1, 2, 3) .delayElements(Duration.ofMillis(1000)) .replay(2, Duration.ofMillis(2000)) .autoConnect() .elapsed(); StepVerifier.create(source) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(3))) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 1) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 2) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 3) .verifyComplete(); StepVerifier.create(source) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(3))) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 2) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 3) .verifyComplete(); }
@Test public void cacheFluxTTL() { Flux<Tuple2<Long, Integer>> source = Flux.just(1, 2, 3) .delayElements(Duration.ofMillis(1000)) .replay(Duration.ofMillis(2000)) .autoConnect() .elapsed(); StepVerifier.create(source) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(3))) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 1) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 2) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 3) .verifyComplete(); StepVerifier.create(source) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(3))) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 2) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 3) .verifyComplete(); }
@Test public void cacheFluxTTLMillis() { Flux<Tuple2<Long, Integer>> source = Flux.just(1, 2, 3) .delayElements(Duration.ofMillis(1000)) .replay(Duration.ofMillis(2000), vts) .autoConnect() .elapsed(); StepVerifier.create(source) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(3))) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 1) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 2) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 3) .verifyComplete(); StepVerifier.create(source) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(3))) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 2) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 3) .verifyComplete(); }
@Test public void cacheFluxTTLFused() { Flux<Tuple2<Long, Integer>> source = Flux.just(1, 2, 3) .delayElements(Duration.ofMillis(1000)) .replay(Duration.ofMillis(2000)) .autoConnect() .elapsed(); StepVerifier.create(source) .expectFusion(Fuseable.ANY) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(3))) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 1) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 2) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 3) .verifyComplete(); StepVerifier.create(source) .expectFusion(Fuseable.ANY) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(3))) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 2) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 3) .verifyComplete(); }
@Test public void cacheFluxHistoryTTLFused() { Flux<Tuple2<Long, Integer>> source = Flux.just(1, 2, 3) .delayElements(Duration.ofMillis(1000)) .replay(2, Duration.ofMillis(2000)) .autoConnect() .elapsed() .log(); StepVerifier.create(source) .expectFusion(Fuseable.ANY) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(3))) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 1) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 2) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 3) .verifyComplete(); StepVerifier.create(source) .expectFusion(Fuseable.ANY) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(3))) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 2) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 3) .verifyComplete(); }
@Test public void cacheFlux() { Flux<Tuple2<Long, Integer>> source = Flux.just(1, 2, 3) .delayElements(Duration.ofMillis(1000)) .replay() .autoConnect() .elapsed(); StepVerifier.create(source) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(3))) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 1) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 2) .expectNextMatches(t -> t.getT1() == 1000 && t.getT2() == 3) .verifyComplete(); StepVerifier.create(source) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(3))) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 1) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 2) .expectNextMatches(t -> t.getT1() == 0 && t.getT2() == 3) .verifyComplete(); }