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.

There is a newer version: 18.3.10
Show 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.logging.Level;
import java.util.logging.Logger;

import oracle.kv.AsyncIterationHandle;
import oracle.kv.CompletionHandler;
import oracle.kv.ResultHandler;
import oracle.kv.stats.DetailedMetrics;

/**
 * An implementation of {@link AsyncIterationHandle}.
 *
 * @param  the type of the iteration element
 */
public class AsyncIterationHandleImpl
        implements AsyncIterationHandle, IterationHandleNotifier {

    /**
     * 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
     * notifyingHandler 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 next handler or null if not initialized.  Set only once and only if
     * asyncIterator is also set.
     */
    private ResultHandler nextHandler;

    /**
     * The completion handler or null if not initialized.  Set only once and
     * only if asyncIterator is also set.
     */
    private CompletionHandler completionHandler;

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

    /** Used to make sure only one party notifies handlers at a time. */
    private boolean notifyingHandler;

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

    /** Whether the completion handler has been notified. */
    private boolean calledOnComplete;

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

    /**
     * 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 IllegalStateException(
                    "The iterator has already been specified");
            }
            this.asyncIterator = checkNull("asyncIterator", asyncIterator);
        }
    }

    /* -- Implement AsyncIterationHandle -- */

    @Override
    public void iterate(ResultHandler next, CompletionHandler completion) {
        checkNull("next", next);
        checkNull("completion", completion);
        synchronized (lock) {
            if (asyncIterator == null) {
                throw new IllegalStateException("Internal error: no iterator");
            }
            if (this.nextHandler != null) {
                throw new IllegalStateException(
                    "The iterate method has already been called");
            }
            nextHandler = next;
            completionHandler = completion;
        }

        /*
         * Notify handlers in case something happened before the handlers were
         * set.
         */
        notifyNext();
    }

    @Override
    public void request(long n) {
        if (n < 1) {
            throw new IllegalArgumentException(
                "Request value must be greater than zero");
        }
        synchronized (lock) {
            if (asyncIterator == null) {
                throw new IllegalStateException("Internal error: no iterator");
            }

            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 application's next result handler's onResult method,
         * and that method calls request again.
         */
        if (inRequest.get() != null) {
            return;
        }
        inRequest.set(Boolean.TRUE);
        try {
            notifyNext();
        } finally {
            inRequest.remove();
        }
    }

    @Override
    public void cancel() {
        synchronized (lock) {
            if (asyncIterator == null) {
                throw new IllegalStateException("Internal error: no iterator");
            }
            if (cancelCalled) {
                return;
            }
            cancelCalled = true;
        }
        notifyNext();
    }

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

    @Override
    public 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 IllegalStateException(
                "Already holding lock in call to notifyNext");
        }
        synchronized (lock) {

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

            /* Handler isn't initialized */
            if (nextHandler == null) {
                return;
            }

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

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

        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 notifyingHandler is cleared if there is an
             * unexpected exception -- it will have already been cleared
             * otherwise
             */
            if (exception != null) {
                synchronized (lock) {
                    notifyingHandler = false;
                }
                logger.log(Level.WARNING, "Unexpected exception: " + exception,
                           exception);
            }
        }
    }

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

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

        if (cancelCalled) {
            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) {
                onNext(null, e);
                closeException = e;
            }
        }
        if (next != null) {
            closeException = onNext(next, null);

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

        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 (isClosed) {

                /* Iteration is complete */
                assert !calledOnComplete;
                calledOnComplete = true;
                notifyingHandler = 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 */
                notifyingHandler = false;
                done = true;
            } else {

                /* Check for more elements */
                done = false;
            }
        }
        if (logger.isLoggable(Level.FINEST)) {
            logger.finest("notifyNext next=" + next +
                          " isClosed=" + isClosed +
                          " newNotify=" + newNotify +
                          " originalRequests=" + originalRequests +
                          (closeException != null ?
                           " closeException=" + closeException :
                           "") +
                          " done=" + done);
        }
        if (isClosed) {
            if (closeException == null) {
                closeException = asyncIterator.getCloseException();
            }
            onComplete(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, Throwable exception) {
        try {
            nextHandler.onResult(next, exception);
            return null;
        } catch (Throwable t) {
            if (logger.isLoggable(Level.FINEST)) {
                if (exception == null) {
                    logger.finest(
                        "Problem delivering result to next handler: " +
                        nextHandler +
                        " result: " + next +
                        " exception from handler: " + t);
                } else {
                    logger.finest(
                        "Problem delivering exception to next handler: " +
                        nextHandler +
                        " exception being delivered: " + exception +
                        " exception from handler: " + t);
                }
            }
            return t;
        }
    }

    /**
     * Deliver the completion result.
     */
    private void onComplete(Throwable exception) {
        try {
            completionHandler.onComplete(exception);
        } catch (Throwable t) {
            logger.warning(
                "Problem notifying completion handler: " + completionHandler +
                (exception != null ?
                 " exception being delivered: " + exception :
                 "") +
                " exception from handler: " + 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