All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rx.observables.AsyncOnSubscribe Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2015 Netflix, Inc.
 *
 * 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 rx.observables;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

import rx.*;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Observer;
import rx.annotations.Beta;
import rx.functions.*;
import rx.internal.operators.BufferUntilSubscriber;
import rx.observers.SerializedObserver;
import rx.plugins.RxJavaHooks;
import rx.subscriptions.CompositeSubscription;

/**
 * A utility class to create {@code OnSubscribe} functions that respond correctly to back
 * pressure requests from subscribers. This is an improvement over
 * {@link rx.Observable#unsafeCreate(OnSubscribe) Observable.create(OnSubscribe)} which does not provide
 * any means of managing back pressure requests out-of-the-box. This variant of an OnSubscribe
 * function allows for the asynchronous processing of requests.
 *
 * @param 
 *            the type of the user-define state used in {@link #generateState() generateState(S)} ,
 *            {@link #next(Object, long, Observer) next(S, Long, Observer)}, and
 *            {@link #onUnsubscribe(Object) onUnsubscribe(S)}.
 * @param 
 *            the type of {@code Subscribers} that will be compatible with {@code this}.
 * @since 1.3 - beta
 */
@Beta
public abstract class AsyncOnSubscribe implements OnSubscribe {

    /**
     * Executed once when subscribed to by a subscriber (via {@link #call(Subscriber)})
     * to produce a state value. This value is passed into {@link #next(Object, long, Observer)
     * next(S state, Observer  observer)} on the first iteration. Subsequent iterations of
     * {@code next} will receive the state returned by the previous invocation of {@code next}.
     *
     * @return the initial state value
     */
    protected abstract S generateState();

    /**
     * Called to produce data to the downstream subscribers. To emit data to a downstream subscriber
     * call {@code observer.onNext(t)}. To signal an error condition call
     * {@code observer.onError(throwable)} or throw an Exception. To signal the end of a data stream
     * call {@code observer.onCompleted()}. Implementations of this method must follow the following
     * rules.
     *
     * 
    *
  • Must not call {@code observer.onNext(t)} more than 1 time per invocation.
  • *
  • Must not call {@code observer.onNext(t)} concurrently.
  • *
* * The value returned from an invocation of this method will be passed in as the {@code state} * argument of the next invocation of this method. * * @param state * the state value (from {@link #generateState()} on the first invocation or the * previous invocation of this method. * @param requested * the amount of data requested. An observable emitted to the observer should not * exceed this amount. * @param observer * the observer of data emitted by * @return the next iteration's state value */ protected abstract S next(S state, long requested, Observer> observer); /** * Clean up behavior that is executed after the downstream subscriber's subscription is * unsubscribed. This method will be invoked exactly once. * * @param state * the last state value returned from {@code next(S, Long, Observer)} or * {@code generateState()} at the time when a terminal event is emitted from * {@link #next(Object, long, Observer)} or unsubscribing. */ protected void onUnsubscribe(S state) { // default behavior is no-op } /** * Generates a synchronous {@link AsyncOnSubscribe} that calls the provided {@code next} * function to generate data to downstream subscribers. * * @param the type of the generated values * @param the type of the associated state with each Subscriber * @param generator * generates the initial state value (see {@link #generateState()}) * @param next * produces data to the downstream subscriber (see * {@link #next(Object, long, Observer) next(S, long, Observer)}) * @return an AsyncOnSubscribe that emits data in a protocol compatible with back-pressure. */ public static AsyncOnSubscribe createSingleState(Func0 generator, final Action3>> next) { Func3>, S> nextFunc = new Func3>, S>() { @Override public S call(S state, Long requested, Observer> subscriber) { next.call(state, requested, subscriber); return state; }}; return new AsyncOnSubscribeImpl(generator, nextFunc); } /** * Generates a synchronous {@link AsyncOnSubscribe} that calls the provided {@code next} * function to generate data to downstream subscribers. * * This overload creates a AsyncOnSubscribe without an explicit clean up step. * * @param the type of the generated values * @param the type of the associated state with each Subscriber * @param generator * generates the initial state value (see {@link #generateState()}) * @param next * produces data to the downstream subscriber (see * {@link #next(Object, long, Observer) next(S, long, Observer)}) * @param onUnsubscribe * clean up behavior (see {@link #onUnsubscribe(Object) onUnsubscribe(S)}) * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ public static AsyncOnSubscribe createSingleState(Func0 generator, final Action3>> next, final Action1 onUnsubscribe) { Func3>, S> nextFunc = new Func3>, S>() { @Override public S call(S state, Long requested, Observer> subscriber) { next.call(state, requested, subscriber); return state; }}; return new AsyncOnSubscribeImpl(generator, nextFunc, onUnsubscribe); } /** * Generates a synchronous {@link AsyncOnSubscribe} that calls the provided {@code next} * function to generate data to downstream subscribers. * * @param the type of the generated values * @param the type of the associated state with each Subscriber * @param generator * generates the initial state value (see {@link #generateState()}) * @param next * produces data to the downstream subscriber (see * {@link #next(Object, long, Observer) next(S, long, Observer)}) * @param onUnsubscribe * clean up behavior (see {@link #onUnsubscribe(Object) onUnsubscribe(S)}) * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ public static AsyncOnSubscribe createStateful(Func0 generator, Func3>, ? extends S> next, Action1 onUnsubscribe) { return new AsyncOnSubscribeImpl(generator, next, onUnsubscribe); } /** * Generates a synchronous {@link AsyncOnSubscribe} that calls the provided {@code next} * function to generate data to downstream subscribers. * * @param the type of the generated values * @param the type of the associated state with each Subscriber * @param generator * generates the initial state value (see {@link #generateState()}) * @param next * produces data to the downstream subscriber (see * {@link #next(Object, long, Observer) next(S, long, Observer)}) * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ public static AsyncOnSubscribe createStateful(Func0 generator, Func3>, ? extends S> next) { return new AsyncOnSubscribeImpl(generator, next); } /** * Generates a synchronous {@link AsyncOnSubscribe} that calls the provided {@code next} * function to generate data to downstream subscribers. * * This overload creates a "state-less" AsyncOnSubscribe which does not have an explicit state * value. This should be used when the {@code next} function closes over it's state. * * @param the type of the generated values * @param next * produces data to the downstream subscriber (see * {@link #next(Object, long, Observer) next(S, long, Observer)}) * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ public static AsyncOnSubscribe createStateless(final Action2>> next) { Func3>, Void> nextFunc = new Func3>, Void>() { @Override public Void call(Void state, Long requested, Observer> subscriber) { next.call(requested, subscriber); return state; }}; return new AsyncOnSubscribeImpl(nextFunc); } /** * Generates a synchronous {@link AsyncOnSubscribe} that calls the provided {@code next} * function to generate data to downstream subscribers. * * This overload creates a "state-less" AsyncOnSubscribe which does not have an explicit state * value. This should be used when the {@code next} function closes over it's state. * * @param the type of the generated values * @param next * produces data to the downstream subscriber (see * {@link #next(Object, long, Observer) next(S, long, Observer)}) * @param onUnsubscribe * clean up behavior (see {@link #onUnsubscribe(Object) onUnsubscribe(S)}) * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ public static AsyncOnSubscribe createStateless(final Action2>> next, final Action0 onUnsubscribe) { Func3>, Void> nextFunc = new Func3>, Void>() { @Override public Void call(Void state, Long requested, Observer> subscriber) { next.call(requested, subscriber); return null; }}; Action1 wrappedOnUnsubscribe = new Action1() { @Override public void call(Void t) { onUnsubscribe.call(); }}; return new AsyncOnSubscribeImpl(nextFunc, wrappedOnUnsubscribe); } /** * An implementation of AsyncOnSubscribe that delegates * {@link AsyncOnSubscribe#next(Object, long, Observer)}, * {@link AsyncOnSubscribe#generateState()}, and {@link AsyncOnSubscribe#onUnsubscribe(Object)} * to provided functions/closures. * * @param * the type of the user-defined state * @param * the type of compatible Subscribers */ static final class AsyncOnSubscribeImpl extends AsyncOnSubscribe { private final Func0 generator; private final Func3>, ? extends S> next; private final Action1 onUnsubscribe; AsyncOnSubscribeImpl(Func0 generator, Func3>, ? extends S> next, Action1 onUnsubscribe) { this.generator = generator; this.next = next; this.onUnsubscribe = onUnsubscribe; } public AsyncOnSubscribeImpl(Func0 generator, Func3>, ? extends S> next) { this(generator, next, null); } public AsyncOnSubscribeImpl(Func3>, S> next, Action1 onUnsubscribe) { this(null, next, onUnsubscribe); } public AsyncOnSubscribeImpl(Func3>, S> nextFunc) { this(null, nextFunc, null); } @Override protected S generateState() { return generator == null ? null : generator.call(); } @Override protected S next(S state, long requested, Observer> observer) { return next.call(state, requested, observer); } @Override protected void onUnsubscribe(S state) { if (onUnsubscribe != null) { onUnsubscribe.call(state); } } } @Override public final void call(final Subscriber actualSubscriber) { S state; try { state = generateState(); } catch (Throwable ex) { actualSubscriber.onError(ex); return; } UnicastSubject> subject = UnicastSubject.> create(); final AsyncOuterManager outerProducer = new AsyncOuterManager(this, state, subject); Subscriber concatSubscriber = new Subscriber() { @Override public void onNext(T t) { actualSubscriber.onNext(t); } @Override public void onError(Throwable e) { actualSubscriber.onError(e); } @Override public void onCompleted() { actualSubscriber.onCompleted(); } @Override public void setProducer(Producer p) { outerProducer.setConcatProducer(p); } }; subject.onBackpressureBuffer().concatMap(new Func1, Observable>() { @Override public Observable call(Observable v) { return v.onBackpressureBuffer(); } }).unsafeSubscribe(concatSubscriber); actualSubscriber.add(concatSubscriber); actualSubscriber.add(outerProducer); actualSubscriber.setProducer(outerProducer); } static final class AsyncOuterManager implements Producer, Subscription, Observer> { final AtomicBoolean isUnsubscribed; private final AsyncOnSubscribe parent; private final SerializedObserver> serializedSubscriber; final CompositeSubscription subscriptions = new CompositeSubscription(); private boolean hasTerminated; private boolean onNextCalled; private S state; private final UnicastSubject> merger; boolean emitting; List requests; Producer concatProducer; long expectedDelivery; public AsyncOuterManager(AsyncOnSubscribe parent, S initialState, UnicastSubject> merger) { this.parent = parent; this.serializedSubscriber = new SerializedObserver>(this); this.state = initialState; this.merger = merger; this.isUnsubscribed = new AtomicBoolean(); } @Override public void unsubscribe() { if (isUnsubscribed.compareAndSet(false, true)) { synchronized (this) { if (emitting) { requests = new ArrayList(); requests.add(0L); return; } emitting = true; } cleanup(); } } void setConcatProducer(Producer p) { if (concatProducer != null) { throw new IllegalStateException("setConcatProducer may be called at most once!"); } concatProducer = p; } @Override public boolean isUnsubscribed() { return isUnsubscribed.get(); } public void nextIteration(long requestCount) { state = parent.next(state, requestCount, serializedSubscriber); } void cleanup() { subscriptions.unsubscribe(); try { parent.onUnsubscribe(state); } catch (Throwable ex) { handleThrownError(ex); } } @Override public void request(long n) { if (n == 0) { return; } if (n < 0) { throw new IllegalStateException("Request can't be negative! " + n); } boolean quit = false; synchronized (this) { if (emitting) { List q = requests; if (q == null) { q = new ArrayList(); requests = q; } q.add(n); quit = true; } else { emitting = true; } } concatProducer.request(n); if (quit) { return; } if (tryEmit(n)) { return; } for (;;) { List q; synchronized (this) { q = requests; if (q == null) { emitting = false; return; } requests = null; } for (long r : q) { if (tryEmit(r)) { return; } } } } /** * Called when a source has produced less than its provision (completed prematurely); this will trigger the generation of another * source that will hopefully emit the missing amount. * @param n the missing amount to produce via a new source. */ public void requestRemaining(long n) { if (n == 0) { return; } if (n < 0) { throw new IllegalStateException("Request can't be negative! " + n); } synchronized (this) { if (emitting) { List q = requests; if (q == null) { q = new ArrayList(); requests = q; } q.add(n); return; } emitting = true; } if (tryEmit(n)) { return; } for (;;) { List q; synchronized (this) { q = requests; if (q == null) { emitting = false; return; } requests = null; } for (long r : q) { if (tryEmit(r)) { return; } } } } boolean tryEmit(long n) { if (isUnsubscribed()) { cleanup(); return true; } try { onNextCalled = false; expectedDelivery = n; nextIteration(n); //hasTerminated will be true when onCompleted was already emitted from the request callback //even if the the observer has not seen onCompleted from the requested observable, //so we should not clean up while there are active subscriptions if (hasTerminated && !subscriptions.hasSubscriptions() || isUnsubscribed()) { cleanup(); return true; } if (!onNextCalled) { handleThrownError(new IllegalStateException("No events emitted!")); return true; } } catch (Throwable ex) { handleThrownError(ex); return true; } return false; } private void handleThrownError(Throwable ex) { if (hasTerminated) { RxJavaHooks.onError(ex); } else { hasTerminated = true; merger.onError(ex); cleanup(); } } @Override public void onCompleted() { if (hasTerminated) { throw new IllegalStateException("Terminal event already emitted."); } hasTerminated = true; merger.onCompleted(); } @Override public void onError(Throwable e) { if (hasTerminated) { throw new IllegalStateException("Terminal event already emitted."); } hasTerminated = true; merger.onError(e); } @Override public void onNext(final Observable t) { if (onNextCalled) { throw new IllegalStateException("onNext called multiple times!"); } onNextCalled = true; if (hasTerminated) { return; } subscribeBufferToObservable(t); } @SuppressWarnings("unchecked") private void subscribeBufferToObservable(final Observable t) { final BufferUntilSubscriber buffer = BufferUntilSubscriber. create(); final long expected = expectedDelivery; final Subscriber s = new Subscriber() { long remaining = expected; @Override public void onNext(T t) { remaining--; buffer.onNext(t); } @Override public void onError(Throwable e) { buffer.onError(e); } @Override public void onCompleted() { buffer.onCompleted(); long r = remaining; if (r > 0) { requestRemaining(r); } } }; subscriptions.add(s); Observable doOnTerminate = t.doOnTerminate(new Action0() { @Override public void call() { subscriptions.remove(s); }}); ((Observable)doOnTerminate).subscribe(s); merger.onNext(buffer); } } static final class UnicastSubject extends Observableimplements Observer { private final State state; public static UnicastSubject create() { return new UnicastSubject(new State()); } protected UnicastSubject(final State state) { super(state); this.state = state; } @Override public void onCompleted() { state.subscriber.onCompleted(); } @Override public void onError(Throwable e) { state.subscriber.onError(e); } @Override public void onNext(T t) { state.subscriber.onNext(t); } static final class State implements OnSubscribe { Subscriber subscriber; @Override public void call(Subscriber s) { synchronized (this) { if (subscriber == null) { subscriber = s; return; } } s.onError(new IllegalStateException("There can be only one subscriber")); } } } }