
rx.internal.operators.OperatorMergeMaxConcurrent Maven / Gradle / Ivy
/**
* Copyright 2014 Netflix, Inc.
*
* 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 rx.internal.operators;
import java.util.*;
import java.util.concurrent.atomic.*;
import rx.*;
import rx.Observable.Operator;
import rx.Observable;
import rx.exceptions.MissingBackpressureException;
import rx.internal.util.RxRingBuffer;
import rx.observers.SerializedSubscriber;
import rx.subscriptions.CompositeSubscription;
/**
* Flattens a list of Observables into one Observable sequence, without any transformation.
*
*
*
* You can combine the items emitted by multiple Observables so that they act like a single
* Observable, by using the merge operation.
*
* @param the emitted value type
*/
public final class OperatorMergeMaxConcurrent implements Operator> {
final int maxConcurrency;
public OperatorMergeMaxConcurrent(int maxConcurrency) {
this.maxConcurrency = maxConcurrency;
}
@Override
public Subscriber super Observable extends T>> call(Subscriber super T> child) {
final SerializedSubscriber s = new SerializedSubscriber(child);
final CompositeSubscription csub = new CompositeSubscription();
child.add(csub);
SourceSubscriber ssub = new SourceSubscriber(maxConcurrency, s, csub);
child.setProducer(new MergeMaxConcurrentProducer(ssub));
return ssub;
}
/** Routes the requests from downstream to the sourcesubscriber. */
static final class MergeMaxConcurrentProducer implements Producer {
final SourceSubscriber ssub;
public MergeMaxConcurrentProducer(SourceSubscriber ssub) {
this.ssub = ssub;
}
@Override
public void request(long n) {
ssub.downstreamRequest(n);
}
}
static final class SourceSubscriber extends Subscriber> {
final NotificationLite nl = NotificationLite.instance();
final int maxConcurrency;
final Subscriber s;
final CompositeSubscription csub;
final Object guard;
volatile int wip;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater WIP
= AtomicIntegerFieldUpdater.newUpdater(SourceSubscriber.class, "wip");
volatile int sourceIndex;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater SOURCE_INDEX
= AtomicIntegerFieldUpdater.newUpdater(SourceSubscriber.class, "sourceIndex");
/** Guarded by guard. */
int active;
/** Guarded by guard. */
final Queue> queue;
/** Indicates the emitting phase. Guarded by this. */
boolean emitting;
/** Counts the missed emitting calls. Guarded by this. */
int missedEmitting;
/** The last buffer index in the round-robin drain scheme. Accessed while emitting == true. */
int lastIndex;
/** Guarded by itself. */
final List subscribers;
volatile long requested;
@SuppressWarnings("rawtypes")
static final AtomicLongFieldUpdater REQUESTED
= AtomicLongFieldUpdater.newUpdater(SourceSubscriber.class, "requested");
public SourceSubscriber(int maxConcurrency, Subscriber s, CompositeSubscription csub) {
super(s);
this.maxConcurrency = maxConcurrency;
this.s = s;
this.csub = csub;
this.guard = new Object();
this.queue = new ArrayDeque>(maxConcurrency);
this.subscribers = Collections.synchronizedList(new ArrayList());
this.wip = 1;
}
@Override
public void onStart() {
request(maxConcurrency);
}
@Override
public void onNext(Observable extends T> t) {
synchronized (guard) {
queue.add(t);
}
subscribeNext();
}
void subscribeNext() {
Observable extends T> t;
synchronized (guard) {
t = queue.peek();
if (t == null || active >= maxConcurrency) {
return;
}
active++;
queue.poll();
}
MergeItemSubscriber itemSub = new MergeItemSubscriber(SOURCE_INDEX.getAndIncrement(this));
subscribers.add(itemSub);
csub.add(itemSub);
WIP.incrementAndGet(this);
t.unsafeSubscribe(itemSub);
request(1);
}
@Override
public void onError(Throwable e) {
Object[] active;
synchronized (subscribers) {
active = subscribers.toArray();
subscribers.clear();
}
try {
s.onError(e);
unsubscribe();
} finally {
for (Object o : active) {
@SuppressWarnings("unchecked")
MergeItemSubscriber a = (MergeItemSubscriber)o;
a.release();
}
}
}
@Override
public void onCompleted() {
WIP.decrementAndGet(this);
drain();
}
protected void downstreamRequest(long n) {
for (;;) {
long r = requested;
long u;
if (r != Long.MAX_VALUE && n == Long.MAX_VALUE) {
u = Long.MAX_VALUE;
} else
if (r + n < 0) {
u = Long.MAX_VALUE;
} else {
u = r + n;
}
if (REQUESTED.compareAndSet(this, r, u)) {
break;
}
}
drain();
}
protected void drain() {
synchronized (this) {
if (emitting) {
missedEmitting++;
return;
}
emitting = true;
missedEmitting = 0;
}
final List.MergeItemSubscriber> subs = subscribers;
final Subscriber child = s;
Object[] active = new Object[subs.size()];
do {
long r;
outer:
while ((r = requested) > 0) {
int idx = lastIndex;
synchronized (subs) {
if (subs.size() == active.length) {
active = subs.toArray(active);
} else {
active = subs.toArray();
}
}
int resumeIndex = 0;
int j = 0;
for (Object o : active) {
@SuppressWarnings("unchecked")
MergeItemSubscriber e = (MergeItemSubscriber)o;
if (e.index == idx) {
resumeIndex = j;
break;
}
j++;
}
int sumConsumed = 0;
for (int i = 0; i < active.length; i++) {
j = (i + resumeIndex) % active.length;
@SuppressWarnings("unchecked")
final MergeItemSubscriber e = (MergeItemSubscriber)active[j];
final RxRingBuffer b = e.buffer;
lastIndex = e.index;
if (!e.once && b.peek() == null) {
subs.remove(e);
synchronized (guard) {
this.active--;
}
csub.remove(e);
e.release();
subscribeNext();
WIP.decrementAndGet(this);
continue outer;
}
int consumed = 0;
Object v;
while (r > 0 && (v = b.poll()) != null) {
nl.accept(child, v);
if (child.isUnsubscribed()) {
return;
}
r--;
consumed++;
}
if (consumed > 0) {
sumConsumed += consumed;
REQUESTED.addAndGet(this, -consumed);
e.requestMore(consumed);
}
if (r == 0) {
break outer;
}
}
if (sumConsumed == 0) {
break;
}
}
if (active.length == 0) {
if (wip == 0) {
child.onCompleted();
return;
}
}
synchronized (this) {
if (missedEmitting == 0) {
emitting = false;
break;
}
missedEmitting = 0;
}
} while (true);
}
final class MergeItemSubscriber extends Subscriber {
volatile boolean once = true;
final int index;
final RxRingBuffer buffer;
public MergeItemSubscriber(int index) {
buffer = RxRingBuffer.getSpmcInstance();
this.index = index;
}
@Override
public void onStart() {
request(RxRingBuffer.SIZE);
}
@Override
public void onNext(T t) {
try {
buffer.onNext(t);
} catch (MissingBackpressureException ex) {
onError(ex);
return;
}
drain();
}
@Override
public void onError(Throwable e) {
SourceSubscriber.this.onError(e);
}
@Override
public void onCompleted() {
if (once) {
once = false;
drain();
}
}
/** Request more from upstream. */
void requestMore(long n) {
request(n);
}
void release() {
// NO-OP for now
buffer.release();
}
}
}
}