All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hu.akarnokd.rxjava2.operators.FlowableFlatMapSync Maven / Gradle / Ivy

There is a newer version: 0.20.10
Show newest version
/*
 * 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.operators;

import java.util.concurrent.atomic.*;

import org.reactivestreams.*;

import io.reactivex.*;
import io.reactivex.exceptions.Exceptions;
import io.reactivex.functions.Function;
import io.reactivex.internal.functions.ObjectHelper;
import io.reactivex.internal.fuseable.*;
import io.reactivex.internal.queue.SpscArrayQueue;
import io.reactivex.internal.subscriptions.SubscriptionHelper;
import io.reactivex.internal.util.*;
import io.reactivex.plugins.RxJavaPlugins;

/**
 * FlatMap a bounded number of inner, non-trivial flows (unbound not supported).
 *
 * @param  the input value type
 * @param  the result value type
 *
 * @since 0.16.0
 */
final class FlowableFlatMapSync extends Flowable implements FlowableTransformer {

    final Publisher source;

    final Function> mapper;

    final int maxConcurrency;

    final int bufferSize;

    final boolean depthFirst;

    FlowableFlatMapSync(Publisher source, Function> mapper,
            int maxConcurrency, int bufferSize, boolean depthFirst) {
        this.source = source;
        this.mapper = mapper;
        this.maxConcurrency = maxConcurrency;
        this.bufferSize = bufferSize;
        this.depthFirst = depthFirst;
    }

    @Override
    protected void subscribeActual(Subscriber s) {
        source.subscribe(new FlatMapOuterSubscriber(s, mapper, maxConcurrency, bufferSize, depthFirst));
    }

    @Override
    public Publisher apply(Flowable upstream) {
        return new FlowableFlatMapSync(upstream, mapper, maxConcurrency, bufferSize, depthFirst);
    }

    interface FlatMapInnerSubscriberSupport {

        void innerNext(FlatMapInnerSubscriber inner, R value);

        void innerError(FlatMapInnerSubscriber inner, Throwable ex);

        void innerComplete(FlatMapInnerSubscriber inner);

        void drain();
    }

    abstract static class BaseFlatMapOuterSubscriber extends AtomicInteger
    implements Subscriber, Subscription, FlatMapInnerSubscriberSupport {

        private static final long serialVersionUID = -208456984819517117L;

        final Subscriber actual;

        final Function> mapper;

        final int maxConcurrency;

        final int bufferSize;

        final AtomicLong requested;

        final AtomicReferenceArray> subscribers;

        final AtomicIntegerArray freelist;

        final AtomicThrowable error;

        final boolean depthFirst;

        final AtomicLong active;

        volatile boolean done;

        volatile boolean cancelled;

        Subscription upstream;

        long emitted;

        long finished;

        static final int PRODUCER_INDEX = 16;
        static final int CONSUMER_INDEX = 32;

        BaseFlatMapOuterSubscriber(Subscriber actual,
                Function> mapper,
                        int maxConcurrency, int bufferSize,
                        boolean depthFirst) {
            this.actual = actual;
            this.mapper = mapper;
            this.maxConcurrency = maxConcurrency;
            this.bufferSize = bufferSize;
            this.requested = new AtomicLong();
            this.error = new AtomicThrowable();
            this.depthFirst = depthFirst;
            this.active = new AtomicLong();

            int c = Pow2.roundToPowerOfTwo(maxConcurrency);
            this.subscribers = new AtomicReferenceArray>(c);
            this.freelist = new AtomicIntegerArray(c + CONSUMER_INDEX + 16);
        }

        @Override
        public final void onSubscribe(Subscription s) {
            if (SubscriptionHelper.validate(this.upstream, s)) {
                this.upstream = s;

                actual.onSubscribe(this);

                s.request(maxConcurrency);
            }
        }

        @Override
        public final void onNext(T t) {

            Publisher p;

            try {
                p = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null value");
            } catch (Throwable ex) {
                Exceptions.throwIfFatal(ex);
                upstream.cancel();
                cancelInners();
                if (error.addThrowable(ex)) {
                    done = true;
                    drain();
                } else {
                    RxJavaPlugins.onError(ex);
                }
                return;
            }

            if (!cancelled) {
                AtomicIntegerArray fl = freelist;
                AtomicReferenceArray> s = subscribers;
                int m = s.length();

                int ci = fl.get(m + CONSUMER_INDEX);
                int idx = fl.get(ci);
                if (idx == 0) {
                    idx = ci + 1;
                }

                FlatMapInnerSubscriber inner = new FlatMapInnerSubscriber(this, bufferSize, idx);
                s.lazySet(idx - 1, inner);
                fl.lazySet(m + CONSUMER_INDEX, (ci + 1) & (m - 1));

                AtomicLong act = active;
                act.lazySet(act.get() + 1);

                if (cancelled) {
                    s.lazySet(idx - 1, null);
                } else {
                    p.subscribe(inner);
                }
            }
        }

        @Override
        public final void onError(Throwable t) {
            if (error.addThrowable(t)) {
                done = true;
                drain();
            } else {
                RxJavaPlugins.onError(t);
            }
        }

        @Override
        public final void onComplete() {
            done = true;
            drain();
        }

        @Override
        public final void request(long n) {
            if (SubscriptionHelper.validate(n)) {
                BackpressureHelper.add(requested, n);
                drain();
            }
        }

        @Override
        public final void cancel() {
            if (!cancelled) {
                cancelled = true;
                upstream.cancel();
                cancelInners();
                cleanupAfter();
            }
        }

        abstract void cleanupAfter();

        final void depthFirst() {
            int missed = 1;
            long e = emitted;
            AtomicReferenceArray> s = subscribers;
            int m = s.length();
            Subscriber a = actual;
            AtomicLong act = active;

            for (;;) {

                long r = requested.get();

                while (e != r) {
                    if (cancelled) {
                        return;
                    }

                    boolean d = done;

                    if (d) {
                        Throwable ex = error.get();
                        if (ex != null) {
                            a.onError(error.terminate());
                            cleanupAfter();
                            return;
                        }
                    }

                    long n = act.get();
                    long f = finished;
                    int innerEmpty = 0;

                    for (int i = 0, j = 0; i < m && j + f < n; i++) {
                        FlatMapInnerSubscriber inner = s.get(i);
                        if (inner != null) {
                            j++;
                            boolean innerDone = inner.done;
                            SimpleQueue q = inner.queue;

                            if (innerDone && (q == null || q.isEmpty())) {
                                remove(inner);
                                finished++;
                                innerEmpty++;
                                upstream.request(1);
                            } else
                            if (q != null) {
                                while (e != r) {
                                    if (cancelled) {
                                        return;
                                    }

                                    if (d) {
                                        Throwable ex = error.get();
                                        if (ex != null) {
                                            a.onError(error.terminate());
                                            cleanupAfter();
                                            return;
                                        }
                                    }

                                    R v;

                                    try {
                                        v = q.poll();
                                    } catch (Throwable ex) {
                                        Exceptions.throwIfFatal(ex);
                                        error.addThrowable(ex);
                                        upstream.cancel();
                                        cancelInners();
                                        a.onError(error.terminate());
                                        cleanupAfter();
                                        return;
                                    }

                                    boolean empty = v == null;

                                    if (innerDone && empty) {
                                        remove(inner);
                                        finished++;
                                        innerEmpty++;
                                        upstream.request(1);
                                        break;
                                    }

                                    if (empty) {
                                        innerEmpty++;
                                        break;
                                    }

                                    a.onNext(v);

                                    e++;

                                    inner.producedOne();
                                }
                            } else {
                                innerEmpty++;
                            }
                        }
                    }

                    n = act.get();
                    f = finished;
                    if (d) {
                        if (n == f) {
                            a.onComplete();
                            cleanupAfter();
                            return;
                        }
                    }

                    if (innerEmpty + f == n) {
                        break;
                    }
                }

                if (e == r) {
                    if (cancelled) {
                        return;
                    }

                    boolean d = done;

                    if (d) {

                        Throwable ex = error.get();
                        if (ex != null) {
                            a.onError(error.terminate());
                            cleanupAfter();
                            return;
                        }

                        long n = act.get();

                        if (n == finished) {
                            a.onComplete();
                            cleanupAfter();
                            return;
                        }
                    }
                }

                int w = get();
                if (w == missed) {
                    emitted = e;
                    missed = addAndGet(-missed);
                    if (missed == 0) {
                        break;
                    }
                } else {
                    missed = w;
                }
            }
        }

        final void breadthFirst() {
            int missed = 1;
            long e = emitted;
            AtomicReferenceArray> s = subscribers;
            int m = s.length();
            Subscriber a = actual;
            AtomicLong act = active;

            for (;;) {

                for (;;) {

                    long r = requested.get();
                    long alive = act.get() - finished;
                    int innerEmpty = 0;

                    int j = 0;
                    for (int i = 0; i < m && j < alive; i++) {
                        if (cancelled) {
                            return;
                        }

                        if (done) {
                            Throwable ex = error.get();
                            if (ex != null) {
                                a.onError(error.terminate());
                                cleanupAfter();
                                return;
                            }
                        }

                        FlatMapInnerSubscriber inner = s.get(i);
                        if (inner != null) {
                            j++;
                            boolean innerDone = inner.done;
                            SimpleQueue q = inner.queue;

                            if (innerDone && (q == null || q.isEmpty())) {
                                remove(inner);
                                finished++;
                                innerEmpty++;
                                upstream.request(1);
                            } else
                            if (q != null) {
                                if (e != r) {
                                    R v;

                                    try {
                                        v = q.poll();
                                    } catch (Throwable ex) {
                                        Exceptions.throwIfFatal(ex);
                                        error.addThrowable(ex);
                                        upstream.cancel();
                                        cancelInners();
                                        a.onError(error.terminate());
                                        cleanupAfter();
                                        return;
                                    }

                                    if (v == null) {
                                        innerEmpty++;
                                    } else {
                                        a.onNext(v);
                                        e++;
                                        inner.producedOne();
                                    }
                                }
                            } else {
                                innerEmpty++;
                            }
                        }
                    }

                    if (e == r) {
                        if (cancelled) {
                            return;
                        }

                        if (done) {
                            Throwable ex = error.get();
                            if (ex != null) {
                                a.onError(error.terminate());
                                cleanupAfter();
                                return;
                            }

                            if (finished == act.get()) {
                                a.onComplete();
                                cleanupAfter();
                                return;
                            }
                        }
                        break;
                    }

                    long f = finished;

                    long in2 = act.get();
                    if (done && in2 == f) {
                        a.onComplete();
                        cleanupAfter();
                        return;
                    }

                    if (innerEmpty == alive) {
                        break;
                    }
                }

                int w = get();
                if (w == missed) {
                    emitted = e;
                    missed = addAndGet(-missed);
                    if (missed == 0) {
                        break;
                    }
                } else {
                    missed = w;
                }
            }
        }

        final void remove(FlatMapInnerSubscriber inner) {
            AtomicIntegerArray fl = freelist;
            AtomicReferenceArray> s = subscribers;
            int m = s.length();
            int idx = inner.index;
            int pi = fl.get(m + PRODUCER_INDEX);
            s.lazySet(idx - 1, null);
            fl.lazySet(pi, idx);
            fl.lazySet(m + PRODUCER_INDEX, (pi + 1) & (m - 1));
        }

        final void cancelInners() {
            AtomicReferenceArray> s = subscribers;
            int m = s.length();
            for (int i = 0; i < m; i++) {
                FlatMapInnerSubscriber inner = s.get(i);
                if (inner != null) {
                    s.lazySet(i, null);
                    inner.cancel();
                }
            }
        }
    }

    static final class FlatMapOuterSubscriber extends BaseFlatMapOuterSubscriber {
        private static final long serialVersionUID = -5109342841608286301L;

        FlatMapOuterSubscriber(Subscriber actual,
                Function> mapper, int maxConcurrency, int bufferSize,
                boolean depthFirst) {
            super(actual, mapper, maxConcurrency, bufferSize, depthFirst);
        }

        @Override
        public void drain() {
            if (getAndIncrement() == 0) {
                drainLoop();
            }
        }

        void drainLoop() {
            if (depthFirst) {
                depthFirst();
            } else {
                breadthFirst();
            }
        }

        @Override
        void cleanupAfter() {
        }

        @Override
        public void innerNext(FlatMapInnerSubscriber inner, R item) {
            if (get() == 0 && compareAndSet(0, 1)) {
                long r = requested.get();
                long e = emitted;
                if (e != r) {
                    actual.onNext(item);
                    emitted = e + 1;
                    inner.producedOne();
                } else {
                    SimpleQueue q = inner.queue();
                    q.offer(item);
                }
                if (decrementAndGet() == 0) {
                    return;
                }
            } else {
                SimpleQueue q = inner.queue();
                q.offer(item);
                if (getAndIncrement() != 0) {
                    return;
                }
            }
            drainLoop();
        }

        @Override
        public void innerError(FlatMapInnerSubscriber inner, Throwable ex) {
            remove(inner);
            if (error.addThrowable(ex)) {
                inner.done = true;
                done = true;
                upstream.cancel();
                cancelInners();
                drain();
            } else {
                RxJavaPlugins.onError(ex);
            }
        }

        @Override
        public void innerComplete(FlatMapInnerSubscriber inner) {
            inner.done = true;
            drain();
        }
    }

    static final class FlatMapInnerSubscriber extends AtomicReference
    implements Subscriber {

        private static final long serialVersionUID = -4991009168975207961L;

        final FlatMapInnerSubscriberSupport parent;

        final int bufferSize;

        final int limit;

        final int index;

        int produced;

        int fusionMode;

        volatile boolean done;

        volatile SimpleQueue queue;

        FlatMapInnerSubscriber(FlatMapInnerSubscriberSupport parent, int bufferSize, int index) {
            this.parent = parent;
            this.bufferSize = bufferSize;
            this.limit = bufferSize - (bufferSize >> 2);
            this.index = index;
        }

        @Override
        public void onSubscribe(Subscription s) {
            if (SubscriptionHelper.setOnce(this, s)) {
                if (s instanceof QueueSubscription) {
                    @SuppressWarnings("unchecked")
                    QueueSubscription qs = (QueueSubscription) s;

                    int m = qs.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY);

                    if (m == QueueSubscription.SYNC) {
                        fusionMode = m;
                        queue = qs;
                        done = true;
                        parent.drain();
                        return;
                    }
                    if (m == QueueSubscription.ASYNC) {
                        fusionMode = m;
                        queue = qs;
                        s.request(bufferSize);
                        return;
                    }
                }
                s.request(bufferSize);
            }
        }

        @Override
        public void onNext(R t) {
            if (fusionMode == QueueSubscription.NONE) {
                parent.innerNext(this, t);
            } else {
                parent.drain();
            }
        }

        @Override
        public void onError(Throwable t) {
            parent.innerError(this, t);
        }

        @Override
        public void onComplete() {
            parent.innerComplete(this);
        }

        void cancel() {
            SubscriptionHelper.cancel(this);
        }

        void producedOne() {
            if (fusionMode != QueueSubscription.SYNC) {
                int p = produced + 1;
                if (p == limit) {
                    produced = 0;
                    get().request(p);
                } else {
                    produced = p;
                }
            }
        }

        SimpleQueue queue() {
            SimpleQueue q = queue;
            if (q == null) {
                q = new SpscArrayQueue(bufferSize);
                queue = q;
            }
            return q;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy