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

com.github.davidmoten.rx.internal.operators.TransformerOnBackpressureBufferRequestLimiting Maven / Gradle / Ivy

package com.github.davidmoten.rx.internal.operators;

import java.util.concurrent.atomic.AtomicLong;

import com.github.davidmoten.rx.Actions;
import com.github.davidmoten.rx.Transformers;
import com.github.davidmoten.rx.util.BackpressureUtils;

import rx.Observable;
import rx.Observable.Operator;
import rx.Observable.Transformer;
import rx.Subscriber;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.schedulers.Schedulers;

public final class TransformerOnBackpressureBufferRequestLimiting implements Transformer {

    private static final TransformerOnBackpressureBufferRequestLimiting instance = new TransformerOnBackpressureBufferRequestLimiting();

    @SuppressWarnings("unchecked")
    public static final  TransformerOnBackpressureBufferRequestLimiting instance() {
        return (TransformerOnBackpressureBufferRequestLimiting) instance;
    }

    @Override
    public Observable call(final Observable o) {
        return Observable.defer(new Func0>() {
            @Override
            public Observable call() {
                final OperatorPassThroughAdjustedRequest op = new OperatorPassThroughAdjustedRequest();
                return o.lift(op).onBackpressureBuffer().doOnRequest(new Action1() {

                    @Override
                    public void call(Long n) {
                        op.requestMore(n);
                    }
                });
            }
        });
    }

    /**
     * Only used with an immediate downstream operator that requests
     * {@code Long.MAX_VALUE} and should only be subscribed to once (use it
     * within a {@code defer} block}.
     *
     * @param 
     *            stream item type
     */
    private static final class OperatorPassThroughAdjustedRequest implements Operator {

        private volatile ParentSubscriber parent;
        private final AtomicLong requested = new AtomicLong();
        private final Object lock = new Object();

        @Override
        public Subscriber call(Subscriber child) {
            // this method should only be called once for this instance
            // assume child requests MAX_VALUE
            ParentSubscriber p = new ParentSubscriber(child);
            synchronized (lock) {
                parent = p;
            }
            p.requestMore(requested.get());
            child.add(p);
            return p;
        }

        public void requestMore(long n) {
            ParentSubscriber p = parent;
            if (p != null) {
                p.requestMore(n);
            } else {
                synchronized (lock) {
                    ParentSubscriber par = parent;
                    if (par == null) {
                        BackpressureUtils.getAndAddRequest(requested, n);
                    } else {
                        par.requestMore(n);
                    }
                }
            }
        }

    }

    private static final class ParentSubscriber extends Subscriber {

        private final Subscriber child;
        private final AtomicLong expected = new AtomicLong();

        public ParentSubscriber(Subscriber child) {
            this.child = child;
            request(0);
        }

        public void requestMore(long n) {
            if (n <= 0) {
                return;
            }
            long r = expected.get();
            if (r == Long.MAX_VALUE) {
                return;
            } else {
                long u = r;
                while (true) {
                    long sum = u + n;
                    final long v;
                    if (sum < 0) {
                        v = Long.MAX_VALUE;
                    } else {
                        v = sum;
                    }
                    if (expected.compareAndSet(u, v)) {
                        // if v negative (more have arrived than requested)
                        long diff = Math.max(0, v);
                        long req = Math.min(n, diff);
                        if (req > 0) {
                            request(req);
                        }
                        return;
                    } else {
                        u = expected.get();
                    }
                }
            }
        }

        @Override
        public void onCompleted() {
            child.onCompleted();
        }

        @Override
        public void onError(Throwable e) {
            child.onError(e);
        }

        @Override
        public void onNext(T t) {
            expected.decrementAndGet();
            child.onNext(t);
        }

    }

    public static void main(String[] args) throws InterruptedException {
        Observable.range(1, 10000) //
                .doOnRequest(new Action1() {
                    @Override
                    public void call(Long n) {
                        System.out.println("requested " + n);
                    }
                }).doOnUnsubscribe(new Action0() {
                    @Override
                    public void call() {
                        System.out.println("unsubscribed");
                    }
                }) //
                .compose(Transformers. onBackpressureBufferRequestLimiting()) //
                .take(10) //
                .subscribeOn(Schedulers.io()) //
                .doOnNext(Actions.println()) //
                .count().toBlocking().single();
        Thread.sleep(2000);
    }

}