/**
* 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.internal.operators.observable;
import java.util.concurrent.atomic.*;
import io.reactivex.*;
import io.reactivex.disposables.Disposable;
import io.reactivex.exceptions.Exceptions;
import io.reactivex.functions.Consumer;
import io.reactivex.internal.disposables.DisposableHelper;
import io.reactivex.internal.fuseable.HasUpstreamObservableSource;
import io.reactivex.internal.util.ExceptionHelper;
import io.reactivex.observables.ConnectableObservable;
import io.reactivex.plugins.RxJavaPlugins;
/**
* A connectable observable which shares an underlying source and dispatches source values to observers in a backpressure-aware
* manner.
* @param <T> the value type
*/
public final class ObservablePublish<T> extends ConnectableObservable<T> implements HasUpstreamObservableSource<T> {
/** The source observable. */
final ObservableSource<T> source;
/** Holds the current subscriber that is, will be or just was subscribed to the source observable. */
final AtomicReference<PublishObserver<T>> current;
final ObservableSource<T> onSubscribe;
/**
* Creates a OperatorPublish instance to publish values of the given source observable.
* @param <T> the source value type
* @param source the source observable
* @return the connectable observable
*/
public static <T> ConnectableObservable<T> create(ObservableSource<T> source) {
// the current connection to source needs to be shared between the operator and its onSubscribe call
final AtomicReference<PublishObserver<T>> curr = new AtomicReference<PublishObserver<T>>();
ObservableSource<T> onSubscribe = new PublishSource<T>(curr);
return RxJavaPlugins.onAssembly(new ObservablePublish<T>(onSubscribe, source, curr));
}
private ObservablePublish(ObservableSource<T> onSubscribe, ObservableSource<T> source,
final AtomicReference<PublishObserver<T>> current) {
this.onSubscribe = onSubscribe;
this.source = source;
this.current = current;
}
@Override
public ObservableSource<T> source() {
return source;
}
@Override
protected void subscribeActual(Observer<? super T> observer) {
onSubscribe.subscribe(observer);
}
@Override
public void connect(Consumer<? super Disposable> connection) {
boolean doConnect;
PublishObserver<T> 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
PublishObserver<T> u = new PublishObserver<T>(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; PublishSubject does not have this
* issue because the dispose() was always triggered by the child observers
* themselves.
*/
try {
connection.accept(ps);
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
throw ExceptionHelper.wrapOrThrow(ex);
}
if (doConnect) {
source.subscribe(ps);
}
}
@SuppressWarnings("rawtypes")
static final class PublishObserver<T>
implements Observer<T>, Disposable {
/** Holds onto the current connected PublishObserver. */
final AtomicReference<PublishObserver<T>> current;
/** Indicates an empty array of inner observers. */
static final InnerDisposable[] EMPTY = new InnerDisposable[0];
/** Indicates a terminated PublishObserver. */
static final InnerDisposable[] TERMINATED = new InnerDisposable[0];
/** Tracks the subscribed observers. */
final AtomicReference<InnerDisposable<T>[]> observers;
/**
* Atomically changed from false to true by connect to make sure the
* connection is only performed by one thread.
*/
final AtomicBoolean shouldConnect;
final AtomicReference<Disposable> s = new AtomicReference<Disposable>();
@SuppressWarnings("unchecked")
PublishObserver(AtomicReference<PublishObserver<T>> current) {
this.observers = new AtomicReference<InnerDisposable<T>[]>(EMPTY);
this.current = current;
this.shouldConnect = new AtomicBoolean();
}
@SuppressWarnings("unchecked")
@Override
public void dispose() {
if (observers.get() != TERMINATED) {
InnerDisposable[] ps = observers.getAndSet(TERMINATED);
if (ps != TERMINATED) {
current.compareAndSet(PublishObserver.this, null);
DisposableHelper.dispose(s);
}
}
}
@Override
public boolean isDisposed() {
return observers.get() == TERMINATED;
}
@Override
public void onSubscribe(Disposable s) {
DisposableHelper.setOnce(this.s, s);
}
@Override
public void onNext(T t) {
for (InnerDisposable<T> inner : observers.get()) {
inner.child.onNext(t);
}
}
@SuppressWarnings("unchecked")
@Override
public void onError(Throwable e) {
current.compareAndSet(this, null);
InnerDisposable<T>[] a = observers.getAndSet(TERMINATED);
if (a.length != 0) {
for (InnerDisposable<T> inner : a) {
inner.child.onError(e);
}
} else {
RxJavaPlugins.onError(e);
}
}
@SuppressWarnings("unchecked")
@Override
public void onComplete() {
current.compareAndSet(this, null);
for (InnerDisposable<T> inner : observers.getAndSet(TERMINATED)) {
inner.child.onComplete();
}
}
/**
* 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<T> producer) {
// the state can change so we do a CAS loop to achieve atomicity
for (;;) {
// get the current producer array
InnerDisposable<T>[] 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;
@SuppressWarnings("unchecked")
InnerDisposable<T>[] 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 producer from the observers array.
* @param producer the producer to remove
*/
@SuppressWarnings("unchecked")
void remove(InnerDisposable<T> producer) {
// the state can change so we do a CAS loop to achieve atomicity
for (;;) {
// let's read the current observers array
InnerDisposable<T>[] c = observers.get();
// if it is either empty or terminated, there is nothing to remove so we quit
int len = c.length;
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<T>[] 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
}
}
}
/**
* A Disposable that manages the request and disposed state of a
* child Observer in thread-safe manner.
* {@code this} holds the parent PublishObserver or itself if disposed
* @param <T> the value type
*/
static final class InnerDisposable<T>
extends AtomicReference<Object>
implements Disposable {
private static final long serialVersionUID = -1100270633763673112L;
/** The actual child subscriber. */
final Observer<? super T> child;
InnerDisposable(Observer<? super T> child) {
this.child = child;
}
@Override
public boolean isDisposed() {
return get() == this;
}
@SuppressWarnings("unchecked")
@Override
public void dispose() {
Object o = getAndSet(this);
if (o != null && o != this) {
((PublishObserver<T>)o).remove(this);
}
}
void setParent(PublishObserver<T> p) {
if (!compareAndSet(null, p)) {
p.remove(this);
}
}
}
static final class PublishSource<T> implements ObservableSource<T> {
private final AtomicReference<PublishObserver<T>> curr;
PublishSource(AtomicReference<PublishObserver<T>> curr) {
this.curr = curr;
}
@Override
public void subscribe(Observer<? super T> child) {
// create the backpressure-managing producer for this child
InnerDisposable<T> inner = new InnerDisposable<T>(child);
child.onSubscribe(inner);
// concurrent connection/disconnection may change the state,
// we loop to be atomic while the child subscribes
for (;;) {
// get the current subscriber-to-source
PublishObserver<T> r = curr.get();
// if there isn't one or it is disposed
if (r == null || r.isDisposed()) {
// create a new subscriber to source
PublishObserver<T> u = new PublishObserver<T>(curr);
// let's try setting it as the current subscriber-to-source
if (!curr.compareAndSet(r, u)) {
// didn't work, maybe someone else did it or the current subscriber
// to source has just finished
continue;
}
// we won, let's use it going onwards
r = u;
}
/*
* Try adding it to the current subscriber-to-source, add is atomic in respect
* to other adds and the termination of the subscriber-to-source.
*/
if (r.add(inner)) {
inner.setParent(r);
break; // NOPMD
}
/*
* The current PublishObserver has been terminated, try with a newer one.
*/
/*
* Note: although technically correct, concurrent disconnects can cause
* unexpected behavior such as child observers never receiving anything
* (unless connected again). An alternative approach, similar to
* PublishSubject would be to immediately terminate such child
* observers as well:
*
* Object term = r.terminalEvent;
* if (r.nl.isCompleted(term)) {
* child.onComplete();
* } else {
* child.onError(r.nl.getError(term));
* }
* return;
*
* The original concurrent behavior was non-deterministic in this regard as well.
* Allowing this behavior, however, may introduce another unexpected behavior:
* after disconnecting a previous connection, one might not be able to prepare
* a new connection right after a previous termination by subscribing new child
* observers asynchronously before a connect call.
*/
}
}
}
}