rx.observables.SyncOnSubscribe Maven / Gradle / Ivy
The newest version!
/**
* Copyright 2014 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.concurrent.atomic.AtomicLong;
import rx.*;
import rx.Observable.OnSubscribe;
import rx.exceptions.Exceptions;
import rx.functions.*;
import rx.internal.operators.BackpressureUtils;
import rx.plugins.RxJavaHooks;
/**
* A utility class to create {@code OnSubscribe} functions that responds 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.
*
* @param
* the type of the user-define state used in {@link #generateState() generateState(S)} ,
* {@link #next(Object, Observer) next(S, Subscriber)}, and
* {@link #onUnsubscribe(Object) onUnsubscribe(S)}.
* @param
* the type of {@code Subscribers} that will be compatible with {@code this}.
* @since 1.2
*/
public abstract class SyncOnSubscribe implements OnSubscribe {
/* (non-Javadoc)
* @see rx.functions.Action1#call(java.lang.Object)
*/
@Override
public final void call(final Subscriber subscriber) {
S state;
try {
state = generateState();
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
subscriber.onError(e);
return;
}
SubscriptionProducer p = new SubscriptionProducer(subscriber, this, state);
subscriber.add(p);
subscriber.setProducer(p);
}
/**
* 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, 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 observer
* the observer of data emitted by
* @return the next iteration's state value
*/
protected abstract S next(S state, 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 prior from {@link #generateState()} or
* {@link #next(Object, Observer) next(S, Observer<T>)} before unsubscribe.
*/
protected void onUnsubscribe(S state) {
// default behavior is no-op
}
/**
* Generates a synchronous {@link SyncOnSubscribe} 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, Observer)
* next(S, Subscriber)})
* @return a SyncOnSubscribe that emits data in a protocol compatible with back-pressure.
* @since 1.3
*/
public static SyncOnSubscribe createSingleState(Func0 generator,
final Action2> next) {
Func2, S> nextFunc = new Func2, S>() {
@Override
public S call(S state, Observer subscriber) {
next.call(state, subscriber);
return state;
}
};
return new SyncOnSubscribeImpl(generator, nextFunc);
}
/**
* Generates a synchronous {@link SyncOnSubscribe} that calls the provided {@code next} function
* to generate data to downstream subscribers.
*
* This overload creates a SyncOnSubscribe 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, Observer)
* next(S, Subscriber)})
* @param onUnsubscribe
* clean up behavior (see {@link #onUnsubscribe(Object) onUnsubscribe(S)})
* @return a SyncOnSubscribe that emits data downstream in a protocol compatible with
* back-pressure.
* @since 1.3
*/
public static SyncOnSubscribe createSingleState(Func0 generator,
final Action2> next,
final Action1 onUnsubscribe) {
Func2, S> nextFunc = new Func2, S>() {
@Override
public S call(S state, Observer subscriber) {
next.call(state, subscriber);
return state;
}
};
return new SyncOnSubscribeImpl(generator, nextFunc, onUnsubscribe);
}
/**
* Generates a synchronous {@link SyncOnSubscribe} 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, Observer)
* next(S, Subscriber)})
* @param onUnsubscribe
* clean up behavior (see {@link #onUnsubscribe(Object) onUnsubscribe(S)})
* @return a SyncOnSubscribe that emits data downstream in a protocol compatible with
* back-pressure.
* @since 1.3
*/
public static SyncOnSubscribe createStateful(Func0 generator,
Func2, ? extends S> next,
Action1 onUnsubscribe) {
return new SyncOnSubscribeImpl(generator, next, onUnsubscribe);
}
/**
* Generates a synchronous {@link SyncOnSubscribe} 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, Observer)
* next(S, Subscriber)})
* @return a SyncOnSubscribe that emits data downstream in a protocol compatible with
* back-pressure.
* @since 1.3
*/
public static SyncOnSubscribe createStateful(Func0 generator,
Func2, ? extends S> next) {
return new SyncOnSubscribeImpl(generator, next);
}
/**
* Generates a synchronous {@link SyncOnSubscribe} that calls the provided {@code next} function
* to generate data to downstream subscribers.
*
* This overload creates a "state-less" SyncOnSubscribe 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, Observer)
* next(S, Subscriber)})
* @return a SyncOnSubscribe that emits data downstream in a protocol compatible with
* back-pressure.
* @since 1.3
*/
public static SyncOnSubscribe createStateless(final Action1> next) {
Func2, Void> nextFunc = new Func2, Void>() {
@Override
public Void call(Void state, Observer subscriber) {
next.call(subscriber);
return state;
}
};
return new SyncOnSubscribeImpl(nextFunc);
}
/**
* Generates a synchronous {@link SyncOnSubscribe} that calls the provided {@code next} function
* to generate data to downstream subscribers.
*
* This overload creates a "state-less" SyncOnSubscribe 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, Observer)
* next(S, Subscriber)})
* @param onUnsubscribe
* clean up behavior (see {@link #onUnsubscribe(Object) onUnsubscribe(S)})
* @return a SyncOnSubscribe that emits data downstream in a protocol compatible with
* back-pressure.
* @since 1.3
*/
public static SyncOnSubscribe createStateless(final Action1> next,
final Action0 onUnsubscribe) {
Func2, Void> nextFunc = new Func2, Void>() {
@Override
public Void call(Void state, Observer subscriber) {
next.call(subscriber);
return null;
}
};
Action1 wrappedOnUnsubscribe = new Action1() {
@Override
public void call(Void t) {
onUnsubscribe.call();
}};
return new SyncOnSubscribeImpl(nextFunc, wrappedOnUnsubscribe);
}
/**
* An implementation of SyncOnSubscribe that delegates
* {@link SyncOnSubscribe#next(Object, Observer)}, {@link SyncOnSubscribe#generateState()},
* and {@link SyncOnSubscribe#onUnsubscribe(Object)} to provided functions/closures.
*
* @param
* the type of the user-defined state
* @param
* the type of compatible Subscribers
*/
static final class SyncOnSubscribeImpl extends SyncOnSubscribe {
private final Func0 generator;
private final Func2, ? extends S> next;
private final Action1 onUnsubscribe;
SyncOnSubscribeImpl(Func0 generator, Func2, ? extends S> next, Action1 onUnsubscribe) {
this.generator = generator;
this.next = next;
this.onUnsubscribe = onUnsubscribe;
}
public SyncOnSubscribeImpl(Func0 generator, Func2, ? extends S> next) {
this(generator, next, null);
}
public SyncOnSubscribeImpl(Func2, S> next, Action1 onUnsubscribe) {
this(null, next, onUnsubscribe);
}
public SyncOnSubscribeImpl(Func2, S> nextFunc) {
this(null, nextFunc, null);
}
@Override
protected S generateState() {
return generator == null ? null : generator.call();
}
@Override
protected S next(S state, Observer observer) {
return next.call(state, observer);
}
@Override
protected void onUnsubscribe(S state) {
if (onUnsubscribe != null) {
onUnsubscribe.call(state);
}
}
}
/**
* Contains the producer loop that reacts to downstream requests of work.
*
* @param
* the type of compatible Subscribers
*/
static final class SubscriptionProducer
extends AtomicLong implements Producer, Subscription, Observer {
/** */
private static final long serialVersionUID = -3736864024352728072L;
private final Subscriber actualSubscriber;
private final SyncOnSubscribe parent;
private boolean onNextCalled;
private boolean hasTerminated;
private S state;
SubscriptionProducer(final Subscriber subscriber, SyncOnSubscribe parent, S state) {
this.actualSubscriber = subscriber;
this.parent = parent;
this.state = state;
}
@Override
public boolean isUnsubscribed() {
return get() < 0L;
}
@Override
public void unsubscribe() {
while (true) {
long requestCount = get();
if (compareAndSet(0L, -1L)) {
doUnsubscribe();
return;
}
else if (compareAndSet(requestCount, -2L)) {
// the loop is iterating concurrently
// need to check if requestCount == -1
// and unsubscribe if so after loop iteration
return;
}
}
}
private boolean tryUnsubscribe() {
// only one thread at a time can iterate over request count
// therefore the requestCount atomic cannot be decrement concurrently here
// safe to set to -1 atomically (since this check can only be done by 1 thread)
if (hasTerminated || get() < -1) {
set(-1);
doUnsubscribe();
return true;
}
return false;
}
private void doUnsubscribe() {
try {
parent.onUnsubscribe(state);
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
RxJavaHooks.onError(e);
}
}
@Override
public void request(long n) {
if (n > 0 && BackpressureUtils.getAndAddRequest(this, n) == 0L) {
if (n == Long.MAX_VALUE) {
fastPath();
} else {
slowPath(n);
}
}
}
private void fastPath() {
final SyncOnSubscribe p = parent;
Subscriber a = actualSubscriber;
for (;;) {
try {
onNextCalled = false;
nextIteration(p);
} catch (Throwable ex) {
handleThrownError(a, ex);
return;
}
if (tryUnsubscribe()) {
return;
}
}
}
private void handleThrownError(Subscriber a, Throwable ex) {
if (hasTerminated) {
RxJavaHooks.onError(ex);
} else {
hasTerminated = true;
a.onError(ex);
unsubscribe();
}
}
private void slowPath(long n) {
final SyncOnSubscribe p = parent;
Subscriber a = actualSubscriber;
long numRequested = n;
for (;;) {
long numRemaining = numRequested;
do {
try {
onNextCalled = false;
nextIteration(p);
} catch (Throwable ex) {
handleThrownError(a, ex);
return;
}
if (tryUnsubscribe()) {
return;
}
if (onNextCalled) {
numRemaining--;
}
} while (numRemaining != 0L);
numRequested = addAndGet(-numRequested);
if (numRequested <= 0L) {
break;
}
}
// catches cases where unsubscribe is called before decrementing atomic request count
tryUnsubscribe();
}
private void nextIteration(final SyncOnSubscribe parent) {
state = parent.next(state, this);
}
@Override
public void onCompleted() {
if (hasTerminated) {
throw new IllegalStateException("Terminal event already emitted.");
}
hasTerminated = true;
if (!actualSubscriber.isUnsubscribed()) {
actualSubscriber.onCompleted();
}
}
@Override
public void onError(Throwable e) {
if (hasTerminated) {
throw new IllegalStateException("Terminal event already emitted.");
}
hasTerminated = true;
if (!actualSubscriber.isUnsubscribed()) {
actualSubscriber.onError(e);
}
}
@Override
public void onNext(T value) {
if (onNextCalled) {
throw new IllegalStateException("onNext called multiple times!");
}
onNextCalled = true;
actualSubscriber.onNext(value);
}
}
}