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"));
}
}
}
}