io.reactivex.observable.internal.operators.ObservablePublish 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.concurrent.atomic.*;
import io.reactivex.common.*;
import io.reactivex.common.exceptions.Exceptions;
import io.reactivex.common.functions.Consumer;
import io.reactivex.common.internal.disposables.DisposableHelper;
import io.reactivex.common.internal.utils.ExceptionHelper;
import io.reactivex.observable.*;
import io.reactivex.observable.extensions.HasUpstreamObservableSource;
/**
* A connectable observable which shares an underlying source and dispatches source values to observers in a backpressure-aware
* manner.
* @param the value type
*/
public final class ObservablePublish 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;
final ObservableSource onSubscribe;
/**
* Creates a OperatorPublish instance to publish values of the given source observable.
* @param the source value type
* @param source the source observable
* @return the connectable observable
*/
public static ConnectableObservable create(ObservableSource source) {
// the current connection to source needs to be shared between the operator and its onSubscribe call
final AtomicReference> curr = new AtomicReference>();
ObservableSource onSubscribe = new PublishSource(curr);
return RxJavaObservablePlugins.onAssembly(new ObservablePublish(onSubscribe, source, curr));
}
private ObservablePublish(ObservableSource onSubscribe, ObservableSource source,
final AtomicReference> current) {
this.onSubscribe = onSubscribe;
this.source = source;
this.current = current;
}
@Override
public ObservableSource 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 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 u = new PublishObserver(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
implements Observer, Disposable {
/** Holds onto the current connected PublishObserver. */
final AtomicReference> 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[]> 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 s = new AtomicReference();
@SuppressWarnings("unchecked")
PublishObserver(AtomicReference> current) {
this.observers = new AtomicReference[]>(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 inner : observers.get()) {
inner.child.onNext(t);
}
}
@SuppressWarnings("unchecked")
@Override
public void onError(Throwable e) {
current.compareAndSet(this, null);
InnerDisposable[] a = observers.getAndSet(TERMINATED);
if (a.length != 0) {
for (InnerDisposable inner : a) {
inner.child.onError(e);
}
} else {
RxJavaCommonPlugins.onError(e);
}
}
@SuppressWarnings("unchecked")
@Override
public void onComplete() {
current.compareAndSet(this, null);
for (InnerDisposable 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 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;
@SuppressWarnings("unchecked")
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 producer from the observers array.
* @param producer the producer to remove
*/
@SuppressWarnings("unchecked")
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();
// 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[] 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 the value type
*/
static final class InnerDisposable
extends AtomicReference