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

org.aksw.commons.rx.op.OperatorLocalOrder Maven / Gradle / Ivy

There is a newer version: 0.9.9
Show newest version
package org.aksw.commons.rx.op;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
import java.util.function.Function;

import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.FlowableOperator;
import io.reactivex.rxjava3.core.FlowableSubscriber;
import io.reactivex.rxjava3.internal.subscriptions.SubscriptionHelper;
import io.reactivex.rxjava3.internal.util.BackpressureHelper;

public final class OperatorLocalOrder
    extends LocalOrderBase
    implements FlowableOperator {

    private static final Logger logger = LoggerFactory.getLogger(OperatorLocalOrder.class);

    protected S initialExpectedSeqId;

    public OperatorLocalOrder(
            S initialExpectedSeqId,
            Function incrementSeqId,
            BiFunction distanceFn,
            Function extractSeqId) {
        super(incrementSeqId, distanceFn, extractSeqId);
        this.initialExpectedSeqId = initialExpectedSeqId;
    }

    public OperatorLocalOrder(
            S initialExpectedSeqId,
            LocalOrderSpec localOrderSpec) {
        super(localOrderSpec);
        this.initialExpectedSeqId = initialExpectedSeqId;
    }



    public class SubscriberImpl
        implements FlowableSubscriber, Subscription
    {
        protected Subscriber downstream;
        protected Subscription upstream;
        protected volatile boolean isUpstreamComplete = false;


        protected S expectedSeqId = initialExpectedSeqId;

        protected ConcurrentNavigableMap seqIdToValue = new ConcurrentSkipListMap<>((a, b) -> distanceFn.apply(a, b).intValue());

        protected AtomicLong pending = new AtomicLong();

        public SubscriberImpl(Subscriber downstream)
        {
           this.downstream = downstream;
        }

        @Override
        public void onSubscribe(Subscription s) {
            if (upstream != null) {
                s.cancel();
            } else {
                upstream = s;
                downstream.onSubscribe(this);
            }
        }

        public void onNext(T value) {
//          if(delegate.isCancelled()) {
//              throw new RuntimeException("Downstream cancelled");
//          }

            S seqId = extractSeqId.apply(value);

//          System.err.println("ENCOUNTERED CONTRIB " + seqId + " WITH QUEUE size " + seqIdToValue.keySet().size());
            // If complete, the seqId must not be higher than the latest seen one
//            if (isUpstreamComplete) {
//                if (seqIdToValue.isEmpty()) {
//                    downstream.onError(new RuntimeException(
//                            "Sanity check failed: Call to onNext encountered after completion."));
//                }
//
//                S highestSeqId = seqIdToValue.descendingKeySet().first();
//
//                if (distanceFn.apply(seqId, highestSeqId).intValue() > 0) {
//                    downstream.onError(new RuntimeException(
//                            "Sequence was marked as complete with id " + highestSeqId
//                            + " but a higher id was encountered " + seqId));
//                }
//            }

            boolean checkForExistingKeys = true;
            if (checkForExistingKeys) {
                if (seqIdToValue.containsKey(seqId)) {
                    downstream.onError(new RuntimeException("Already seen an item with id " + seqId));
                }
            }

            // Add item to the map and drain
            drain(true, () -> seqIdToValue.put(seqId, value));
        }

        protected void drain() {
            drain(false, null);
        }

        protected void drain(boolean isCalledFromOnNext, Runnable action) {
            List buffer = new ArrayList<>();
            synchronized (seqIdToValue) {
                if (action != null) {
                    action.run();
                }
                drainTo(buffer);
            }
            for (T item : buffer) {
                downstream.onNext(item);
            }

            postDrainRequests(isCalledFromOnNext);
        }

        public void postDrainRequests(boolean isCalledFromOnNext) {
            if (pending.get() > 0) {

                // If after a drain the upstream is complete and there is still
                // downstream demand then
                // trigger the onComplete event on downstream
                if (isUpstreamComplete) {
                    downstream.onComplete();

                    // Check for out-of-order items
                    if (!seqIdToValue.isEmpty()) {
                        int size = seqIdToValue.size();
                        String msg = "Upstream completed but " + size + " out of order items still queued";
                        logger.warn(msg);
                        throw new RuntimeException(msg);
                    }
                } else {
                    if (isCalledFromOnNext) {
//                        System.out.println("Requesting another item - demand is " + pending.get() + " queue size is " + seqIdToValue.size());
                        upstream.request(1);
                    }
                }
            }
        }
        protected void drainTo(Collection buffer) {

            Iterator> it = seqIdToValue.entrySet().iterator();
            while (it.hasNext() && pending.get() > 0) {
//                    if(delegate.isCancelled()) {
//                        throw new RuntimeException("Downstream cancelled");
//                    }

                Entry e = it.next();
                S s = e.getKey();
                T v = e.getValue();

                int d = distanceFn.apply(s, expectedSeqId).intValue();
                if (d == 0) {
                    it.remove();
                    pending.decrementAndGet();
                    buffer.add(v);
                    expectedSeqId = incrementSeqId.apply(expectedSeqId);
                    // this.notifyAll();
                    // System.out.println("expecting seq id " + expectedSeqId);
                } else if (d < 0) {
                    // Skip values with a lower id
                    // TODO Add a flag to emit onError event
                    logger.warn("Should not happen: received id " + s + " which is lower than the expected id "
                            + expectedSeqId);
                    it.remove();
                } else { // if d > 0
                    // Wait for the next sequence id
                      logger.trace("Next id in queue is " + s + " but first need to wait for expected id " + expectedSeqId);
//                    System.out.println("Next id in queue is " + s + " but first need to wait for expected id " + expectedSeqId);
                    break;
                }
            }

//            System.out.println("Drain complete - pending " + pending.get() + " queue size is " + seqIdToValue.size());
        }


        @Override
        public void onError(Throwable t) {
            downstream.onError(t);
        }

        public void onComplete() {
            isUpstreamComplete = true;

            // Calling drain after setting isUpstreamComplete to true
            // will trigger a warning / exception if there are
            // out of order items
            drain();
        }

        @Override
        public void request(long n) {
            if (SubscriptionHelper.validate(n)) {
                long before = BackpressureHelper.add(pending, n);

                if (before == 0) {
                    upstream.request(1);
                }
            }

            // If the upstream is already complete then the request may yet
            // be served from our queue
            drain();
        }

        @Override
        public void cancel() {
            upstream.cancel();
        }
    }


    public static  OperatorLocalOrder forLong(long initiallyExpectedId, Function extractSeqId) {
        return new OperatorLocalOrder(
                initiallyExpectedId,
                id -> Long.valueOf(id.longValue() + 1l),
                (a, b) -> a - b, extractSeqId);
    }

    public static > OperatorLocalOrder wrap(S initiallyExpectedId, Function incrementSeqId, BiFunction distanceFn, Function extractSeqId) {
        return new OperatorLocalOrder(initiallyExpectedId, incrementSeqId, distanceFn, extractSeqId);
    }

    public static > FlowableOperator create(
            S initialExpectedSeqId,
            LocalOrderSpec orderSpec) {
        return new OperatorLocalOrder(initialExpectedSeqId, orderSpec);
    }


    public static > FlowableOperator create(
            S initialExpectedSeqId,
            Function incrementSeqId,
            BiFunction distanceFn,
            Function extractSeqId) {
        return new OperatorLocalOrder(initialExpectedSeqId, incrementSeqId, distanceFn, extractSeqId);
    }

    @Override
    public @NonNull Subscriber apply(@NonNull Subscriber downstream)
            throws Throwable {
        return new SubscriberImpl(downstream);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy