hu.akarnokd.rxjava2.parallel.ParallelSortedJoin Maven / Gradle / Ivy
Show all versions of rxjava2-extensions Show documentation
/*
* Copyright 2016 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.parallel;
import java.util.*;
import java.util.concurrent.atomic.*;
import org.reactivestreams.*;
import io.reactivex.Flowable;
import io.reactivex.internal.subscriptions.SubscriptionHelper;
import io.reactivex.internal.util.*;
import io.reactivex.plugins.RxJavaPlugins;
/**
* Given sorted rail sequences (according to the provided comparator) as List
* emit the smallest item from these parallel Lists to the Subscriber.
*
* It expects the source to emit exactly one list (which could be empty).
*
* @param the value type
*/
final class ParallelSortedJoin extends Flowable {
final ParallelFlowable> source;
final Comparator super T> comparator;
ParallelSortedJoin(ParallelFlowable> source, Comparator super T> comparator) {
this.source = source;
this.comparator = comparator;
}
@Override
protected void subscribeActual(Subscriber super T> s) {
SortedJoinSubscription parent = new SortedJoinSubscription(s, source.parallelism(), comparator);
s.onSubscribe(parent);
source.subscribe(parent.subscribers);
}
static final class SortedJoinSubscription
extends AtomicInteger
implements Subscription {
private static final long serialVersionUID = 3481980673745556697L;
final Subscriber super T> actual;
final SortedJoinInnerSubscriber[] subscribers;
final List[] lists;
final int[] indexes;
final Comparator super T> comparator;
final AtomicLong requested = new AtomicLong();
volatile boolean cancelled;
final AtomicInteger remaining = new AtomicInteger();
final AtomicThrowable error = new AtomicThrowable();
@SuppressWarnings("unchecked")
SortedJoinSubscription(Subscriber super T> actual, int n, Comparator super T> comparator) {
this.actual = actual;
this.comparator = comparator;
SortedJoinInnerSubscriber[] s = new SortedJoinInnerSubscriber[n];
for (int i = 0; i < n; i++) {
s[i] = new SortedJoinInnerSubscriber(this, i);
}
this.subscribers = s;
this.lists = new List[n];
this.indexes = new int[n];
remaining.lazySet(n);
}
@Override
public void request(long n) {
if (SubscriptionHelper.validate(n)) {
BackpressureHelper.add(requested, n);
if (remaining.get() == 0) {
drain();
}
}
}
@Override
public void cancel() {
if (!cancelled) {
cancelled = true;
cancelAll();
if (getAndIncrement() == 0) {
Arrays.fill(lists, null);
}
}
}
void cancelAll() {
for (SortedJoinInnerSubscriber s : subscribers) {
s.cancel();
}
}
void innerNext(List value, int index) {
lists[index] = value;
if (remaining.decrementAndGet() == 0) {
drain();
}
}
void innerError(Throwable e) {
if (error.addThrowable(e)) {
drain();
} else {
RxJavaPlugins.onError(e);
}
}
void drain() {
if (getAndIncrement() != 0) {
return;
}
int missed = 1;
Subscriber super T> a = actual;
List[] lists = this.lists;
int[] indexes = this.indexes;
int n = indexes.length;
for (;;) {
long r = requested.get();
long e = 0L;
while (e != r) {
if (cancelled) {
Arrays.fill(lists, null);
return;
}
Throwable ex = error.get();
if (ex != null) {
cancelAll();
Arrays.fill(lists, null);
a.onError(error.terminate());
return;
}
T min = null;
int minIndex = -1;
for (int i = 0; i < n; i++) {
List list = lists[i];
int index = indexes[i];
if (list.size() != index) {
if (min == null) {
min = list.get(index);
minIndex = i;
} else {
T b = list.get(index);
if (comparator.compare(min, b) > 0) {
min = b;
minIndex = i;
}
}
}
}
if (min == null) {
Arrays.fill(lists, null);
a.onComplete();
return;
}
a.onNext(min);
indexes[minIndex]++;
e++;
}
if (e == r) {
if (cancelled) {
Arrays.fill(lists, null);
return;
}
Throwable ex = error.get();
if (ex != null) {
cancelAll();
Arrays.fill(lists, null);
a.onError(error.terminate());
return;
}
boolean empty = true;
for (int i = 0; i < n; i++) {
if (indexes[i] != lists[i].size()) {
empty = false;
break;
}
}
if (empty) {
Arrays.fill(lists, null);
a.onComplete();
return;
}
}
if (e != 0 && r != Long.MAX_VALUE) {
requested.addAndGet(-e);
}
int w = get();
if (w == missed) {
missed = addAndGet(-missed);
if (missed == 0) {
break;
}
} else {
missed = w;
}
}
}
}
static final class SortedJoinInnerSubscriber
extends AtomicReference
implements Subscriber> {
private static final long serialVersionUID = 6751017204873808094L;
final SortedJoinSubscription parent;
final int index;
SortedJoinInnerSubscriber(SortedJoinSubscription parent, int index) {
this.parent = parent;
this.index = index;
}
@Override
public void onSubscribe(Subscription s) {
if (SubscriptionHelper.setOnce(this, s)) {
s.request(Long.MAX_VALUE);
}
}
@Override
public void onNext(List t) {
parent.innerNext(t, index);
}
@Override
public void onError(Throwable t) {
parent.innerError(t);
}
@Override
public void onComplete() {
// ignored
}
void cancel() {
SubscriptionHelper.cancel(this);
}
}
}