hu.akarnokd.rxjava2.processors.MulticastProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rxjava2-extensions Show documentation
Show all versions of rxjava2-extensions Show documentation
rxjava2-extensions developed by David Karnok
/*
* Copyright 2016-2017 David Karnok
*
* 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 hu.akarnokd.rxjava2.processors;
import java.util.concurrent.atomic.*;
import org.reactivestreams.*;
import io.reactivex.exceptions.*;
import io.reactivex.internal.fuseable.*;
import io.reactivex.internal.queue.*;
import io.reactivex.internal.subscriptions.*;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.processors.FlowableProcessor;
/**
* A {@code Processor} implementation that coordinates downstream requrests through
* a front-buffer and stable-prefetching, optionally cancelling the upstream if all
* subscribers have cancelled.
* @param the input and output value type
* @since 0.16.4
*/
public final class MulticastProcessor extends FlowableProcessor {
final AtomicInteger wip;
final AtomicReference upstream;
final AtomicReference[]> subscribers;
final AtomicBoolean once;
final int bufferSize;
final int limit;
final boolean refcount;
volatile SimpleQueue queue;
volatile boolean done;
volatile Throwable error;
int consumed;
int fusionMode;
@SuppressWarnings("rawtypes")
static final MulticastSubscription[] EMPTY = new MulticastSubscription[0];
@SuppressWarnings("rawtypes")
static final MulticastSubscription[] TERMINATED = new MulticastSubscription[0];
/**
* Constructs a fresh instance with the default Flowable.bufferSize() prefetch
* amount and no refCount-behavior.
* @param the input and output value type
* @return the new MulticastProcessor instance
*/
public static MulticastProcessor create() {
return new MulticastProcessor(bufferSize(), false);
}
/**
* Constructs a fresh instance with the default Flowable.bufferSize() prefetch
* amount and no refCount-behavior.
* @param the input and output value type
* @param refCount if true and if all Subscribers have unsubscribed, the upstream
* is cancelled
* @return the new MulticastProcessor instance
*/
public static MulticastProcessor create(boolean refCount) {
return new MulticastProcessor(bufferSize(), refCount);
}
/**
* Constructs a fresh instance with the given prefetch amount and no refCount behavior.
* @param bufferSize the prefetch amount
* @param the input and output value type
* @return the new MulticastProcessor instance
*/
public static MulticastProcessor create(int bufferSize) {
return new MulticastProcessor(bufferSize, false);
}
/**
* Constructs a fres instance with the given prefetch amount and the optional
* refCount-behavior.
* @param bufferSize the prefech amount
* @param refCount if true and if all Subscribers have unsubscribed, the upstream
* is cancelled
* @param the input and output value type
* @return the new MulticastProcessor instance
*/
public static MulticastProcessor create(int bufferSize, boolean refCount) {
return new MulticastProcessor(bufferSize, refCount);
}
/**
* Constructs a fres instance with the given prefetch amount and the optional
* refCount-behavior.
* @param bufferSize the prefech amount
* @param refCount if true and if all Subscribers have unsubscribed, the upstream
* is cancelled
*/
@SuppressWarnings("unchecked")
MulticastProcessor(int bufferSize, boolean refCount) {
this.bufferSize = bufferSize;
this.limit = bufferSize - (bufferSize >> 2);
this.wip = new AtomicInteger();
this.subscribers = new AtomicReference[]>(EMPTY);
this.upstream = new AtomicReference();
this.refcount = refCount;
this.once = new AtomicBoolean();
}
/**
* Initializes this Processor by setting an upstream Subscription that
* ignores request amounts, uses a fixed buffer
* and allows using the onXXX and offer methods
* afterwards.
*/
public void start() {
if (SubscriptionHelper.setOnce(upstream, EmptySubscription.INSTANCE)) {
queue = new SpscArrayQueue(bufferSize);
}
}
/**
* Initializes this Processor by setting an upstream Subscription that
* ignores request amounts, uses an unbounded buffer
* and allows using the onXXX and offer methods
* afterwards.
*/
public void startUnbounded() {
if (SubscriptionHelper.setOnce(upstream, EmptySubscription.INSTANCE)) {
queue = new SpscLinkedArrayQueue(bufferSize);
}
}
@Override
public void onSubscribe(Subscription s) {
if (SubscriptionHelper.setOnce(upstream, s)) {
if (s instanceof QueueSubscription) {
@SuppressWarnings("unchecked")
QueueSubscription qs = (QueueSubscription)s;
int m = qs.requestFusion(QueueSubscription.ANY);
if (m == QueueSubscription.SYNC) {
fusionMode = m;
queue = qs;
done = true;
drain();
return;
}
if (m == QueueSubscription.ASYNC) {
fusionMode = m;
queue = qs;
s.request(bufferSize);
return;
}
}
queue = new SpscArrayQueue(bufferSize);
s.request(bufferSize);
}
}
@Override
public void onNext(T t) {
if (once.get()) {
return;
}
if (fusionMode == QueueSubscription.NONE) {
if (t == null) {
throw new NullPointerException("t is null");
}
if (!queue.offer(t)) {
SubscriptionHelper.cancel(upstream);
onError(new MissingBackpressureException());
return;
}
}
drain();
}
/**
* Tries to offer an item into the internal queue and returns false
* if the queue is full.
* @param t the item to offer, not null
* @return true if successful, false if the queue is full
*/
public boolean offer(T t) {
if (once.get()) {
return false;
}
if (t == null) {
throw new NullPointerException("t is null");
}
if (fusionMode == QueueSubscription.NONE) {
if (queue.offer(t)) {
drain();
return true;
}
}
return false;
}
@Override
public void onError(Throwable t) {
if (t == null) {
throw new NullPointerException("t is null");
}
if (once.compareAndSet(false, true)) {
error = t;
done = true;
drain();
} else {
RxJavaPlugins.onError(t);
}
}
@Override
public void onComplete() {
if (once.compareAndSet(false, true)) {
done = true;
drain();
}
}
@Override
public boolean hasSubscribers() {
return subscribers.get().length != 0;
}
@Override
public boolean hasThrowable() {
return once.get() && error != null;
}
@Override
public boolean hasComplete() {
return once.get() && error == null;
}
@Override
public Throwable getThrowable() {
return once.get() ? error : null;
}
@Override
protected void subscribeActual(Subscriber super T> s) {
MulticastSubscription ms = new MulticastSubscription(s, this);
s.onSubscribe(ms);
if (add(ms)) {
if (ms.get() == Long.MIN_VALUE) {
remove(ms);
} else {
drain();
}
} else {
if (once.get() || !refcount) {
Throwable ex = error;
if (ex != null) {
s.onError(ex);
return;
}
}
s.onComplete();
}
}
boolean add(MulticastSubscription inner) {
for (;;) {
MulticastSubscription[] a = subscribers.get();
if (a == TERMINATED) {
return false;
}
int n = a.length;
@SuppressWarnings("unchecked")
MulticastSubscription[] b = new MulticastSubscription[n + 1];
System.arraycopy(a, 0, b, 0, n);
b[n] = inner;
if (subscribers.compareAndSet(a, b)) {
return true;
}
}
}
@SuppressWarnings("unchecked")
void remove(MulticastSubscription inner) {
for (;;) {
MulticastSubscription[] a = subscribers.get();
int n = a.length;
if (n == 0) {
return;
}
int j = -1;
for (int i = 0; i < n; i++) {
if (a[i] == inner) {
j = i;
break;
}
}
if (j < 0) {
break;
}
if (n == 1) {
if (refcount) {
if (subscribers.compareAndSet(a, TERMINATED)) {
SubscriptionHelper.cancel(upstream);
once.set(true);
break;
}
} else {
if (subscribers.compareAndSet(a, EMPTY)) {
break;
}
}
} else {
MulticastSubscription[] b = new MulticastSubscription[n - 1];
System.arraycopy(a, 0, b, 0, j);
System.arraycopy(a, j + 1, b, j, n - j - 1);
if (subscribers.compareAndSet(a, b)) {
break;
}
}
}
}
@SuppressWarnings("unchecked")
void drain() {
if (wip.getAndIncrement() != 0) {
return;
}
int missed = 1;
AtomicReference[]> subs = subscribers;
int c = consumed;
int lim = limit;
SimpleQueue q = queue;
int fm = fusionMode;
outer:
for (;;) {
MulticastSubscription[] as = subs.get();
int n = as.length;
if (n != 0) {
long r = -1L;
for (MulticastSubscription a : as) {
long ra = a.get();
if (ra >= 0L) {
if (r == -1L) {
r = ra - a.emitted;
} else {
r = Math.min(r, ra - a.emitted);
}
}
}
while (r > 0L) {
MulticastSubscription[] bs = subs.get();
if (bs == TERMINATED) {
q.clear();
return;
}
if (as != bs) {
continue outer;
}
boolean d = done;
T v;
try {
v = q != null ? q.poll() : null;
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
SubscriptionHelper.cancel(upstream);
d = true;
v = null;
error = ex;
done = true;
}
boolean empty = v == null;
if (d && empty) {
Throwable ex = error;
if (ex != null) {
for (MulticastSubscription inner : subs.getAndSet(TERMINATED)) {
inner.onError(ex);
}
} else {
for (MulticastSubscription inner : subs.getAndSet(TERMINATED)) {
inner.onComplete();
}
}
return;
}
if (empty) {
break;
}
for (MulticastSubscription inner : as) {
inner.onNext(v);
}
r--;
if (fm != QueueSubscription.SYNC) {
if (++c == lim) {
c = 0;
upstream.get().request(lim);
}
}
}
if (r == 0) {
MulticastSubscription[] bs = subs.get();
if (bs == TERMINATED) {
q.clear();
return;
}
if (as != bs) {
continue outer;
}
if (done && q.isEmpty()) {
Throwable ex = error;
if (ex != null) {
for (MulticastSubscription inner : subs.getAndSet(TERMINATED)) {
inner.onError(ex);
}
} else {
for (MulticastSubscription inner : subs.getAndSet(TERMINATED)) {
inner.onComplete();
}
}
return;
}
}
}
int w = wip.get();
if (w == missed) {
consumed = c;
missed = wip.addAndGet(-missed);
if (missed == 0) {
break;
}
} else {
missed = w;
}
}
}
static final class MulticastSubscription extends AtomicLong implements Subscription {
private static final long serialVersionUID = -363282618957264509L;
final Subscriber super T> actual;
final MulticastProcessor parent;
long emitted;
MulticastSubscription(Subscriber super T> actual, MulticastProcessor parent) {
this.actual = actual;
this.parent = parent;
}
@Override
public void request(long n) {
if (SubscriptionHelper.validate(n)) {
for (;;) {
long r = get();
if (r == Long.MIN_VALUE || r == Long.MAX_VALUE) {
break;
}
long u = r + n;
if (u < 0L) {
u = Long.MAX_VALUE;
}
if (compareAndSet(r, u)) {
parent.drain();
break;
}
}
}
}
@Override
public void cancel() {
if (getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) {
parent.remove(this);
}
}
void onNext(T t) {
if (get() != Long.MIN_VALUE) {
emitted++;
actual.onNext(t);
}
}
void onError(Throwable t) {
if (get() != Long.MIN_VALUE) {
actual.onError(t);
}
}
void onComplete() {
if (get() != Long.MIN_VALUE) {
actual.onComplete();
}
}
}
}