io.reactivex.rxjava3.internal.operators.observable.ObservableReplay Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rxjava Show documentation
Show all versions of rxjava Show documentation
Reactive Extensions for Java
/*
* 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.rxjava3.internal.operators.observable;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.*;
import io.reactivex.rxjava3.core.*;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.exceptions.Exceptions;
import io.reactivex.rxjava3.functions.*;
import io.reactivex.rxjava3.internal.disposables.*;
import io.reactivex.rxjava3.internal.fuseable.HasUpstreamObservableSource;
import io.reactivex.rxjava3.internal.util.*;
import io.reactivex.rxjava3.observables.ConnectableObservable;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
import io.reactivex.rxjava3.schedulers.Timed;
public final class ObservableReplay extends ConnectableObservable implements HasUpstreamObservableSource {
/** 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 Supplier extends ConnectableObservable> connectableFactory,
final Function super Observable, ? extends ObservableSource> selector) {
return RxJavaPlugins.onAssembly(new MulticastReplay<>(connectableFactory, selector));
}
/**
* 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
* @param eagerTruncate if true, the head reference is refreshed to avoid unwanted item retention
* @return the new ConnectableObservable instance
*/
public static ConnectableObservable create(ObservableSource source,
final int bufferSize, boolean eagerTruncate) {
if (bufferSize == Integer.MAX_VALUE) {
return createFrom(source);
}
return create(source, new ReplayBufferSupplier<>(bufferSize, eagerTruncate));
}
/**
* 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
* @param eagerTruncate if true, the head reference is refreshed to avoid unwanted item retention
* @return the new ConnectableObservable instance
*/
public static ConnectableObservable create(ObservableSource source,
long maxAge, TimeUnit unit, Scheduler scheduler, boolean eagerTruncate) {
return create(source, maxAge, unit, scheduler, Integer.MAX_VALUE, eagerTruncate);
}
/**
* 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
* @param eagerTruncate if true, the head reference is refreshed to avoid unwanted item retention
* @return the new ConnectableObservable instance
*/
public static ConnectableObservable create(ObservableSource source,
final long maxAge, final TimeUnit unit, final Scheduler scheduler, final int bufferSize, boolean eagerTruncate) {
return create(source, new ScheduledReplaySupplier<>(bufferSize, maxAge, unit, scheduler, eagerTruncate));
}
/**
* Creates a OperatorReplay instance to replay values of the given source observable.
* @param the value type
* @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 RxJavaPlugins.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 reset() {
ReplayObserver conn = current.get();
if (conn != null && conn.isDisposed()) {
current.compareAndSet(conn, null);
}
}
@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, current);
// 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) {
Exceptions.throwIfFatal(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;
/** The current connection. */
final AtomicReference> current;
ReplayObserver(ReplayBuffer buffer, AtomicReference> current) {
this.buffer = buffer;
this.current = current;
this.observers = new AtomicReference<>(EMPTY);
this.shouldConnect = new AtomicBoolean();
}
@Override
public boolean isDisposed() {
return observers.get() == TERMINATED;
}
@Override
public void dispose() {
observers.set(TERMINATED);
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 {
RxJavaPlugins.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);
// make sure the last known node is not retained
index = null;
}
}
/**
* Convenience method to auto-cast the index object.
* @param type index to be casted to
* @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
© 2015 - 2024 Weber Informatics LLC | Privacy Policy