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

oracle.kv.impl.async.AsyncIterationHandleImpl Maven / Gradle / Ivy

Go to download

NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.

The newest version!
/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.async;

import static oracle.kv.impl.util.ObjectUtil.checkNull;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import oracle.kv.IterationSubscription;
import oracle.kv.stats.DetailedMetrics;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

/**
 * A class that generates subscriptions, to implement {@link Publisher} for
 * async iterations.
 *
 * @param  the type of the iteration element
 */
public class AsyncIterationHandleImpl implements IterationHandleNotifier {

    /** The number of currently open subscriptions. */
    private static final AtomicInteger openSubscriptions = new AtomicInteger();

    /**
     * A thread local variable that records whether a call to the request
     * method is already underway in the current thread.
     */
    private final ThreadLocal inRequest = new ThreadLocal();

    /**
     * The logger.
     */
    private final Logger logger;

    /**
     * The object used for locking, to avoid interference from application code
     * that could lock this instance.  Non-final fields should only be accessed
     * by using synchronization on this lock, either from within a synchronized
     * block, in a code block that is running exclusively because it set
     * notifyingSubscriber while synchronized, or, for fields that are only set
     * once to a non-null value, after checking for a non-null value while
     * synchronized.  The newNotify field is special since it is set by
     * non-notifying threads: that field should only be read while in a
     * synchronized block.
     */
    private final Object lock = new Object();

    /**
     * The async iterator that supplies locally available values and transmits
     * close requests, or null if not initialized.  Set only once.
     */
    private AsyncTableIterator asyncIterator;

    /** The subscriber, or null if either not initialized or complete. */
    private Subscriber subscriber;

    /**
     * If the subscriber is newly supplied, meaning that onSubscribe should be
     * called.
     */
    private boolean newSubscriber;

    /** The number of iteration items requested. */
    private long requests;

    /**
     * Used to make sure only one thread makes calls to the subscriber at a
     * time.
     */
    private boolean notifyingSubscriber;

    /**
     * Set when notifyNext is called while another thread is notifying the
     * subscriber.  If true, the notifying thread will check again for a next
     * element or whether the iteration is closed before exiting.
     */
    private boolean newNotify;

    /** Whether Subscriber.onComplete or onError has been called. */
    private boolean complete;

    /** Whether cancel has been called.  Set only once. */
    private boolean cancelCalled;

    /** If non-null, a throwable to deliver to Subscriber.onError. */
    private Throwable deliverException;

    /**
     * Creates an instance of this class.
     */
    public AsyncIterationHandleImpl(Logger logger) {
        this.logger = checkNull("logger", logger);
    }

    /**
     * Sets the iterator that the handle should use to obtain elements.  This
     * method is typically called in the iterator constructor, and must be
     * called before the iterate method is called.
     *
     * @param asyncIterator the iterator
     * @throws IllegalStateException if the iterator has already been specified
     */
    public void setIterator(AsyncTableIterator asyncIterator) {
        synchronized (lock) {
            if (this.asyncIterator != null) {
                throw new AssertionError(
                    "Iterator has already been specified");
            }
            this.asyncIterator = checkNull("asyncIterator", asyncIterator);
        }
    }

    public void subscribe(Subscriber s) {
        checkNull("s", s);
        synchronized (lock) {
            if (asyncIterator == null) {
                throw new AssertionError("No iterator");
            }
            if (this.subscriber != null) {
                throw new AssertionError("Subscribe has already been called");
            }
            subscriber = s;
            newSubscriber = true;
        }
        notifyNext();
    }

    void request(long n) {
        synchronized (lock) {
            if (asyncIterator == null) {
                throw new AssertionError("No iterator");
            }

            if (n < 1) {
                deliverException = new IllegalArgumentException(
                    "Request value must be greater than zero");
            } else {
                requests += n;

                /* Check for overflow */
                if (requests <= 0) {
                    requests = Long.MAX_VALUE;
                }
            }
        }

        /*
         * If we are already within a nested call to the request method, then
         * the top level call will deliver the results.  A recursive call can
         * happen if the application calls request, request calls notifyNext,
         * that calls the subscriber's onNext method, and that method calls
         * request again.
         */
        if (inRequest.get() != null) {
            return;
        }
        inRequest.set(Boolean.TRUE);
        try {
            notifyNext();
        } finally {
            inRequest.remove();
        }
    }

    void cancel() {
        synchronized (lock) {
            if (asyncIterator == null) {
                throw new AssertionError("No iterator");
            }
            if (cancelCalled) {
                return;
            }
            cancelCalled = true;
        }
        notifyNext();
    }

    List getPartitionMetrics() {
        synchronized (lock) {
            if (asyncIterator == null) {
                return Collections.emptyList();
            }
        }
        return asyncIterator.getPartitionMetrics();
    }

    List getShardMetrics() {
        synchronized (lock) {
            if (asyncIterator == null) {
                return Collections.emptyList();
            }
        }
        return asyncIterator.getShardMetrics();
    }

    /* -- Implement IterationHandleNotifier -- */

    @Override
    public void notifyNext() {
        if (Thread.holdsLock(lock)) {
            throw new AssertionError(
                "Already holding lock in call to notifyNext");
        }
        final boolean doOnSubscribe;
        synchronized (lock) {

            /* Note new notify so notify thread checks again */
            if (notifyingSubscriber) {
                newNotify = true;
                logger.finest("notifyNext newNotify=true");
                return;
            }

            /* Subscriber isn't initialized yet */
            if (subscriber == null) {
                return;
            }

            /* Subscriber was already notified that the iteration is done */
            if (complete) {
                return;
            }

            /* Note if we should call onSubscribe, then clear */
            doOnSubscribe = newSubscriber;
            newSubscriber = false;

            /*
             * Mark that we are delivering notifications and clear newNotify so
             * that we notice newer changes
             */
            notifyingSubscriber = true;
            newNotify = false;
            logger.finest("notifyNext");
        }

        if (doOnSubscribe) {
            final Subscription subscription = createSubscription();
            try {
                subscriber.onSubscribe(subscription);
            } catch (Throwable e) {

                /*
                 * Reactive Streams Publisher Rule 2.13 suggests that we should
                 * not call onError in this case, since the subscriber is
                 * suspect. Just log.
                 */
                logger.warning("Problem calling onSubscribe on subscriber: " +
                               subscriber + ", exception from subscriber: " +
                               e);
                synchronized (lock) {
                    notifyingSubscriber = false;
                    subscriber = null;
                }
                return;
            }
            noteOpeningSubscription();
        }

        Throwable exception = null;
        try {

            /*
             * Return local results to the app until:
             * - There are no more local results
             * - We have returned all the results requested by the app
             * - The iteration was canceled by the app
             * - The iteration was closed due to an error
             */
            while (true) {
                if (notifyOneNext()) {
                    break;
                }
            }
        } catch (RuntimeException e) {
            exception = e;
            throw e;
        } catch (Error e) {
            exception = e;
            throw e;
        } finally {

            /*
             * Make certain that notifyingSubscriber is cleared if there is an
             * unexpected exception -- it will have already been cleared
             * otherwise
             */
            if (exception != null) {
                synchronized (lock) {
                    notifyingSubscriber = false;
                }
                logger.log(Level.WARNING, "Unexpected exception: " + exception,
                           exception);
            }
        }
    }

    protected Subscription createSubscription() {
        return new IterationSubscriptionImpl();
    }

    protected class IterationSubscriptionImpl
            implements IterationSubscription {
        @Override
        public void cancel() {
            AsyncIterationHandleImpl.this.cancel();
        }
        @Override
        public void request(long n) {
            AsyncIterationHandleImpl.this.request(n);
        }
        @Override
        public List getPartitionMetrics() {
            return AsyncIterationHandleImpl.this.getPartitionMetrics();
        }
        @Override
        public List getShardMetrics() {
            return AsyncIterationHandleImpl.this.getShardMetrics();
        }
    }

    private void noteOpeningSubscription() {
        openSubscriptions.incrementAndGet();
        if (logger.isLoggable(Level.FINEST)) {
            logger.finest("Opening subscription, subscriber: " + subscriber +
                          ", count: " + openSubscriptions.get());
        }
    }

    private void noteClosingSubscription(Subscriber forSubscriber) {
        openSubscriptions.decrementAndGet();
        if (logger.isLoggable(Level.FINEST)) {
            logger.finest("Closing subscription," +
                          " subscriber: " + forSubscriber +
                          ", count: " + openSubscriptions.get());
        }
    }

    /**
     * Check for a single next element, a closed iterator, a cancel call, or an
     * exception to deliver, returning true if notifications are done because
     * there are no more elements or the iterator is closed. This method is
     * called after setting notifyingSubscriber with the lock held, so it can
     * read most fields without synchronization, but still needs to synchronize
     * for updates.
     */
    private boolean notifyOneNext() {

        assert !complete;

        /* Get next element and closed status */
        E next = null;
        Throwable closeException = null;

        if (cancelCalled) {
            asyncIterator.close();
        } else if (deliverException != null) {
            closeException = deliverException;
            asyncIterator.close();
        } else if (requests > 0 && !asyncIterator.isClosed()) {
            /*
             * The isClosed() call above is to cover the case where the iterator
             * was closed due to an exception thrown by the remote request.
             */
            try {
                next = asyncIterator.nextLocal();
            } catch (Throwable e) {
                closeException = e;
            }
        }
        if (next != null) {
            closeException = onNext(next);

            /* Delivering next failed: terminate the iteration */
            if (closeException != null) {
                asyncIterator.close();
                next = null;
            }
        }
        final boolean makeComplete =
            (closeException != null) ? true :
            (next != null) ? false :
            asyncIterator.isClosed();
        final Subscriber savedSubscriber = subscriber;

        final long originalRequests = requests;
        final boolean done;
        synchronized (lock) {

            /* Decrement requests if we delivered a next element */
            if (next != null) {
                assert requests > 0;
                requests--;
            }
            if (makeComplete) {

                /* Iteration is complete */
                complete = true;

                /*
                 * Reactive Streams Subscription Rule 3.13 says that canceling
                 * a subscription must result in dropping references to the
                 * subscriber.
                 */
                subscriber = null;
                notifyingSubscriber = false;
                done = true;
            } else if (newNotify) {

                /* Clear and try again */
                newNotify = false;
                done = false;
            } else if (next == null) {

                /* No next and no new notifications, so we're done */
                notifyingSubscriber = false;
                done = true;
            } else {

                /* Check for more elements */
                done = false;
            }
        }
        if (logger.isLoggable(Level.FINEST)) {
            logger.finest("notifyNext next=" + next +
                          " makeComplete=" + makeComplete +
                          " newNotify=" + newNotify +
                          " originalRequests=" + originalRequests +
                          (closeException != null ?
                           " closeException=" + closeException :
                           "") +
                          " done=" + done);
        }
        if (makeComplete) {
            if (closeException == null) {
                closeException = asyncIterator.getCloseException();
            }
            onComplete(savedSubscriber, closeException);
        }
        return done;
    }

    /**
     * Deliver a next iteration result, returning any exception thrown during
     * delivery, or null if the call completes normally.
     */
    private Throwable onNext(E next) {
        try {
            subscriber.onNext(next);
            return null;
        } catch (Throwable t) {
            if (logger.isLoggable(Level.FINEST)) {
                logger.finest(
                    "Problem delivering result to subscriber: " +
                    subscriber +
                    " result: " + next +
                    " exception from subscriber: " + t);
            }
            return t;
        }
    }

    /**
     * Deliver the completion result.
     */
    private void onComplete(Subscriber forSubscriber, Throwable exception) {
        noteClosingSubscription(forSubscriber);
        try {
            if (exception == null) {
                forSubscriber.onComplete();
            } else {
                forSubscriber.onError(exception);
            }
        } catch (Throwable t) {
            logger.warning("Problem calling " +
                           ((exception != null) ? "onError" : "onComplete") +
                           " on subscriber: " + forSubscriber +
                           ((exception != null) ?
                            ", exception being delivered: " + exception :
                            "") +
                           ", exception from subscriber: " + t);
        }
    }

    /**
     * Close the iteration if the handle is garbage collected without being
     * closed.
     */
    @Override
    protected void finalize() {
        cancel();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy