io.reactivex.observable.internal.operators.ObservableReplay Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rxjava3-observable Show documentation
Show all versions of rxjava3-observable Show documentation
rxjava3-observable developed by David Karnok
The newest version!
/**
* Copyright (c) 2016-present, RxJava Contributors.
*
* 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 io.reactivex.observable.internal.operators;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import io.reactivex.common.*;
import io.reactivex.common.exceptions.Exceptions;
import io.reactivex.common.functions.*;
import io.reactivex.common.internal.disposables.DisposableHelper;
import io.reactivex.common.internal.functions.ObjectHelper;
import io.reactivex.common.internal.utils.ExceptionHelper;
import io.reactivex.observable.*;
import io.reactivex.observable.Observable;
import io.reactivex.observable.Observer;
import io.reactivex.observable.extensions.HasUpstreamObservableSource;
import io.reactivex.observable.internal.disposables.EmptyDisposable;
import io.reactivex.observable.internal.utils.NotificationLite;
public final class ObservableReplay extends ConnectableObservable implements HasUpstreamObservableSource, Disposable {
/** The source observable. */
final ObservableSource source;
/** Holds the current subscriber that is, will be or just was subscribed to the source observable. */
final AtomicReference> current;
/** A factory that creates the appropriate buffer for the ReplayObserver. */
final BufferSupplier bufferFactory;
final ObservableSource onSubscribe;
interface BufferSupplier {
ReplayBuffer call();
}
@SuppressWarnings("rawtypes")
static final BufferSupplier DEFAULT_UNBOUNDED_FACTORY = new UnBoundedFactory();
/**
* Given a connectable observable factory, it multicasts over the generated
* ConnectableObservable via a selector function.
* @param the value type of the ConnectableObservable
* @param the result value type
* @param connectableFactory the factory that returns a ConnectableObservable for each individual subscriber
* @param selector the function that receives an Observable and should return another Observable that will be subscribed to
* @return the new Observable instance
*/
public static Observable multicastSelector(
final Callable extends ConnectableObservable> connectableFactory,
final Function super Observable, ? extends ObservableSource> selector) {
return RxJavaObservablePlugins.onAssembly(new MulticastReplay(connectableFactory, selector));
}
/**
* Child Observers will observe the events of the ConnectableObservable on the
* specified scheduler.
* @param the value type
* @param co the connectable observable instance
* @param scheduler the target scheduler
* @return the new ConnectableObservable instance
*/
public static ConnectableObservable observeOn(final ConnectableObservable co, final Scheduler scheduler) {
final Observable observable = co.observeOn(scheduler);
return RxJavaObservablePlugins.onAssembly(new Replay(co, observable));
}
/**
* Creates a replaying ConnectableObservable with an unbounded buffer.
* @param the value type
* @param source the source observable
* @return the new ConnectableObservable instance
*/
@SuppressWarnings("unchecked")
public static ConnectableObservable createFrom(ObservableSource extends T> source) {
return create(source, DEFAULT_UNBOUNDED_FACTORY);
}
/**
* Creates a replaying ConnectableObservable with a size bound buffer.
* @param the value type
* @param source the source ObservableSource to use
* @param bufferSize the maximum number of elements to hold
* @return the new ConnectableObservable instance
*/
public static ConnectableObservable create(ObservableSource source,
final int bufferSize) {
if (bufferSize == Integer.MAX_VALUE) {
return createFrom(source);
}
return create(source, new ReplayBufferSupplier(bufferSize));
}
/**
* Creates a replaying ConnectableObservable with a time bound buffer.
* @param the value type
* @param source the source ObservableSource to use
* @param maxAge the maximum age of entries
* @param unit the unit of measure of the age amount
* @param scheduler the target scheduler providing the current time
* @return the new ConnectableObservable instance
*/
public static ConnectableObservable create(ObservableSource source,
long maxAge, TimeUnit unit, Scheduler scheduler) {
return create(source, maxAge, unit, scheduler, Integer.MAX_VALUE);
}
/**
* Creates a replaying ConnectableObservable with a size and time bound buffer.
* @param the value type
* @param source the source ObservableSource to use
* @param maxAge the maximum age of entries
* @param unit the unit of measure of the age amount
* @param scheduler the target scheduler providing the current time
* @param bufferSize the maximum number of elements to hold
* @return the new ConnectableObservable instance
*/
public static ConnectableObservable create(ObservableSource source,
final long maxAge, final TimeUnit unit, final Scheduler scheduler, final int bufferSize) {
return create(source, new ScheduledReplaySupplier(bufferSize, maxAge, unit, scheduler));
}
/**
* Creates a OperatorReplay instance to replay values of the given source observable.
* @param source the source observable
* @param bufferFactory the factory to instantiate the appropriate buffer when the observable becomes active
* @return the connectable observable
*/
static ConnectableObservable create(ObservableSource source,
final BufferSupplier bufferFactory) {
// the current connection to source needs to be shared between the operator and its onSubscribe call
final AtomicReference> curr = new AtomicReference>();
ObservableSource onSubscribe = new ReplaySource(curr, bufferFactory);
return RxJavaObservablePlugins.onAssembly(new ObservableReplay(onSubscribe, source, curr, bufferFactory));
}
private ObservableReplay(ObservableSource onSubscribe, ObservableSource source,
final AtomicReference> current,
final BufferSupplier bufferFactory) {
this.onSubscribe = onSubscribe;
this.source = source;
this.current = current;
this.bufferFactory = bufferFactory;
}
@Override
public ObservableSource source() {
return source;
}
@Override
public void dispose() {
current.lazySet(null);
}
@Override
public boolean isDisposed() {
Disposable d = current.get();
return d == null || d.isDisposed();
}
@Override
protected void subscribeActual(Observer super T> observer) {
onSubscribe.subscribe(observer);
}
@Override
public void connect(Consumer super Disposable> connection) {
boolean doConnect;
ReplayObserver ps;
// we loop because concurrent connect/disconnect and termination may change the state
for (;;) {
// retrieve the current subscriber-to-source instance
ps = current.get();
// if there is none yet or the current has been disposed
if (ps == null || ps.isDisposed()) {
// create a new subscriber-to-source
ReplayBuffer buf = bufferFactory.call();
ReplayObserver u = new ReplayObserver(buf);
// try setting it as the current subscriber-to-source
if (!current.compareAndSet(ps, u)) {
// did not work, perhaps a new subscriber arrived
// and created a new subscriber-to-source as well, retry
continue;
}
ps = u;
}
// if connect() was called concurrently, only one of them should actually
// connect to the source
doConnect = !ps.shouldConnect.get() && ps.shouldConnect.compareAndSet(false, true);
break; // NOPMD
}
/*
* Notify the callback that we have a (new) connection which it can dispose
* but since ps is unique to a connection, multiple calls to connect() will return the
* same Disposable and even if there was a connect-disconnect-connect pair, the older
* references won't disconnect the newer connection.
* Synchronous source consumers have the opportunity to disconnect via dispose() on the
* Disposable as subscribe() may never return in its own.
*
* Note however, that asynchronously disconnecting a running source might leave
* child observers without any terminal event; ReplaySubject does not have this
* issue because the dispose() call was always triggered by the child observers
* themselves.
*/
try {
connection.accept(ps);
} catch (Throwable ex) {
if (doConnect) {
ps.shouldConnect.compareAndSet(true, false);
}
Exceptions.throwIfFatal(ex);
throw ExceptionHelper.wrapOrThrow(ex);
}
if (doConnect) {
source.subscribe(ps);
}
}
@SuppressWarnings("rawtypes")
static final class ReplayObserver
extends AtomicReference
implements Observer, Disposable {
private static final long serialVersionUID = -533785617179540163L;
/** Holds notifications from upstream. */
final ReplayBuffer buffer;
/** Indicates this Observer received a terminal event. */
boolean done;
/** Indicates an empty array of inner observers. */
static final InnerDisposable[] EMPTY = new InnerDisposable[0];
/** Indicates a terminated ReplayObserver. */
static final InnerDisposable[] TERMINATED = new InnerDisposable[0];
/** Tracks the subscribed observers. */
final AtomicReference observers;
/**
* Atomically changed from false to true by connect to make sure the
* connection is only performed by one thread.
*/
final AtomicBoolean shouldConnect;
ReplayObserver(ReplayBuffer buffer) {
this.buffer = buffer;
this.observers = new AtomicReference(EMPTY);
this.shouldConnect = new AtomicBoolean();
}
@Override
public boolean isDisposed() {
return observers.get() == TERMINATED;
}
@Override
public void dispose() {
observers.set(TERMINATED);
// unlike OperatorPublish, we can't null out the terminated so
// late observers can still get replay
// current.compareAndSet(ReplayObserver.this, null);
// we don't care if it fails because it means the current has
// been replaced in the meantime
DisposableHelper.dispose(this);
}
/**
* Atomically try adding a new InnerDisposable to this Observer or return false if this
* Observer was terminated.
* @param producer the producer to add
* @return true if succeeded, false otherwise
*/
boolean add(InnerDisposable producer) {
// the state can change so we do a CAS loop to achieve atomicity
for (;;) {
// get the current producer array
InnerDisposable[] c = observers.get();
// if this subscriber-to-source reached a terminal state by receiving
// an onError or onComplete, just refuse to add the new producer
if (c == TERMINATED) {
return false;
}
// we perform a copy-on-write logic
int len = c.length;
InnerDisposable[] u = new InnerDisposable[len + 1];
System.arraycopy(c, 0, u, 0, len);
u[len] = producer;
// try setting the observers array
if (observers.compareAndSet(c, u)) {
return true;
}
// if failed, some other operation succeeded (another add, remove or termination)
// so retry
}
}
/**
* Atomically removes the given InnerDisposable from the observers array.
* @param producer the producer to remove
*/
void remove(InnerDisposable producer) {
// the state can change so we do a CAS loop to achieve atomicity
for (;;) {
// let's read the current observers array
InnerDisposable[] c = observers.get();
int len = c.length;
// if it is either empty or terminated, there is nothing to remove so we quit
if (len == 0) {
return;
}
// let's find the supplied producer in the array
// although this is O(n), we don't expect too many child observers in general
int j = -1;
for (int i = 0; i < len; i++) {
if (c[i].equals(producer)) {
j = i;
break;
}
}
// we didn't find it so just quit
if (j < 0) {
return;
}
// we do copy-on-write logic here
InnerDisposable[] u;
// we don't create a new empty array if producer was the single inhabitant
// but rather reuse an empty array
if (len == 1) {
u = EMPTY;
} else {
// otherwise, create a new array one less in size
u = new InnerDisposable[len - 1];
// copy elements being before the given producer
System.arraycopy(c, 0, u, 0, j);
// copy elements being after the given producer
System.arraycopy(c, j + 1, u, j, len - j - 1);
}
// try setting this new array as
if (observers.compareAndSet(c, u)) {
return;
}
// if we failed, it means something else happened
// (a concurrent add/remove or termination), we need to retry
}
}
@Override
public void onSubscribe(Disposable p) {
if (DisposableHelper.setOnce(this, p)) {
replay();
}
}
@Override
public void onNext(T t) {
if (!done) {
buffer.next(t);
replay();
}
}
@Override
public void onError(Throwable e) {
// The observer front is accessed serially as required by spec so
// no need to CAS in the terminal value
if (!done) {
done = true;
buffer.error(e);
replayFinal();
} else {
RxJavaCommonPlugins.onError(e);
}
}
@Override
public void onComplete() {
// The observer front is accessed serially as required by spec so
// no need to CAS in the terminal value
if (!done) {
done = true;
buffer.complete();
replayFinal();
}
}
/**
* Tries to replay the buffer contents to all known observers.
*/
void replay() {
@SuppressWarnings("unchecked")
InnerDisposable[] a = observers.get();
for (InnerDisposable rp : a) {
buffer.replay(rp);
}
}
/**
* Tries to replay the buffer contents to all known observers.
*/
void replayFinal() {
@SuppressWarnings("unchecked")
InnerDisposable[] a = observers.getAndSet(TERMINATED);
for (InnerDisposable rp : a) {
buffer.replay(rp);
}
}
}
/**
* A Disposable that manages the disposed state of a
* child Observer in thread-safe manner.
* @param the value type
*/
static final class InnerDisposable
extends AtomicInteger
implements Disposable {
private static final long serialVersionUID = 2728361546769921047L;
/**
* The parent subscriber-to-source used to allow removing the child in case of
* child dispose() call.
*/
final ReplayObserver parent;
/** The actual child subscriber. */
final Observer super T> child;
/**
* Holds an object that represents the current location in the buffer.
* Guarded by the emitter loop.
*/
Object index;
volatile boolean cancelled;
InnerDisposable(ReplayObserver parent, Observer super T> child) {
this.parent = parent;
this.child = child;
}
@Override
public boolean isDisposed() {
return cancelled;
}
@Override
public void dispose() {
if (!cancelled) {
cancelled = true;
// remove this from the parent
parent.remove(this);
}
}
/**
* Convenience method to auto-cast the index object.
* @return the index Object or null
*/
@SuppressWarnings("unchecked")
U index() {
return (U)index;
}
}
/**
* The interface for interacting with various buffering logic.
*
* @param the value type
*/
interface ReplayBuffer {
/**
* Adds a regular value to the buffer.
* @param value the value to be stored in the buffer
*/
void next(T value);
/**
* Adds a terminal exception to the buffer.
* @param e the error to be stored in the buffer
*/
void error(Throwable e);
/**
* Adds a completion event to the buffer.
*/
void complete();
/**
* Tries to replay the buffered values to the
* subscriber inside the output if there
* is new value and requests available at the
* same time.
* @param output the receiver of the buffered events
*/
void replay(InnerDisposable output);
}
/**
* Holds an unbounded list of events.
*
* @param the value type
*/
static final class UnboundedReplayBuffer extends ArrayList