Package com.sun.sgs.impl.nio

Source Code of com.sun.sgs.impl.nio.Reactor$TimeoutHandler

/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.impl.nio;

import static java.nio.channels.SelectionKey.OP_ACCEPT;
import static java.nio.channels.SelectionKey.OP_CONNECT;
import static java.nio.channels.SelectionKey.OP_READ;
import static java.nio.channels.SelectionKey.OP_WRITE;

import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.AlreadyConnectedException;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ConnectionPendingException;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sun.sgs.nio.channels.AbortedByTimeoutException;
import com.sun.sgs.nio.channels.AcceptPendingException;
import com.sun.sgs.nio.channels.AsynchronousChannelGroup;
import com.sun.sgs.nio.channels.ClosedAsynchronousChannelException;
import com.sun.sgs.nio.channels.CompletionHandler;
import com.sun.sgs.nio.channels.IoFuture;
import com.sun.sgs.nio.channels.ReadPendingException;
import com.sun.sgs.nio.channels.ShutdownChannelGroupException;
import com.sun.sgs.nio.channels.WritePendingException;
import java.nio.channels.ClosedSelectorException;

/**
* Reactive implementation of the Reactor pattern; an asynchronous IO
* dispatcher.  When an asynchronous IO operation is initiated, the reactor
* enables interest in that operation with a {@link Selector}, returning
* a future that will be completed when the operation becomes ready and the
* IO is performed.
* <p>
* The actual behavior of completing the IO operation is provided by the
* asynchronous channel implementations; the reactor merely signals readiness
* and invokes the completion handler for the operation as the operations
* complete or are canceled.
*/
class Reactor {

    /** The logger for this class. */
    static final Logger log = Logger.getLogger(Reactor.class.getName());

    /**
     * Selector guard.  Any code that accesses selector data structures,
     * (e.g., selection keys and their interest sets), must obtain this
     * lock before waking the selector.  Doing so prevents the selector
     * from blocking on {@code select()} again until the code that awakened
     * it has released this guard.
     * <p>
     * The selector must obtain this lock <strong>and release it</strong>
     * before blocking on {@code select()}.
     * <p>
     * If both the {@code selectorLock} and {@link AsyncKey} need to be
     * locked, the {@code selectorLock} must be locked <em>first</em>.
     */
    final Object selectorLock = new Object();

    /**
     * The lifecycle state of this reactor.  Increases monotonically.
     * It may only be accessed with selectorLock held.
     */
    protected int lifecycleState = RUNNING;
    /** State: open and running */
    protected static final int RUNNING      = 0;
    /** State: graceful shutdown in progress */
    protected static final int SHUTDOWN     = 1;
    /** State: forced shutdown in progress */
    protected static final int SHUTDOWN_NOW = 2;
    /** State: terminated */
    protected static final int DONE         = 3;

    /**
     * The channel group for this reactor, used to obtain completion
     * handler runners.
     */
    final ReactiveChannelGroup group;

    /**
     * The {@code Selector} that waits for available IO operations on
     * registered channels.
     */
    final Selector selector;

    /**
     * The executor for this {@code Reactor}.  Typically an executor is
     * shared by all reactors in a group, but each reactor may have its
     * own logical executor.
     */
    final Executor executor;

    /** Operations that having pending timeouts. */
    final DelayQueue<TimeoutHandler> timeouts =
        new DelayQueue<TimeoutHandler>();

    /**
     * Creates a new reactor instance with the given channel group and
     * executor.
     *
     * @param group the channel group for this reactor
     * @param executor the executor for tasks in this reactor
     *
     * @throws IOException if an I/O error occurs, e.g. while opening
     *         the {@code Selector} for this reactor
     */
    Reactor(ReactiveChannelGroup group, Executor executor) throws IOException {
        this.group = group;
        this.executor = executor;
        this.selector = group.selectorProvider().openSelector();
    }

    /**
     * Notifies this reactor that it should shutdown when it has no registered
     * channels.  If this reactor is already marked for shutdown, this
     * method has no effect.
     *
     * @see AsynchronousChannelGroup#shutdown()
     */
    void shutdown() {
        synchronized (selectorLock) {
            if (lifecycleState < SHUTDOWN) {
                lifecycleState = SHUTDOWN;
               
                selector.wakeup();
            }
        }
    }

    /**
     * Notifies this reactor that it should shutdown immediately, closing
     * any open channels registered with it.  If this reactor is already
     * marked for immediate shutdown, this method has no effect.
     *
     * @throws IOException if an I/O error occurs
     *
     * @see AsynchronousChannelGroup#shutdownNow()
     */
    void shutdownNow() throws IOException {
        synchronized (selectorLock) {
            if (lifecycleState < SHUTDOWN_NOW) {
                lifecycleState = SHUTDOWN_NOW;
            } else {
                return;
            }
        }

        // To avoid deadlock, must not hold  selectorLock when calling close().
        // Selector keys() set may change while we are iterating.
        while (true) {
            try {
                for (SelectionKey key : selector.keys()) {
                    try {
                        Closeable asyncKey = (Closeable) key.attachment();
                        if (asyncKey != null) {
          asyncKey.close();
                        }
                    } catch (IOException ignore) { }
                }
            } catch (ConcurrentModificationException e) {
                continue;
            } catch (ClosedSelectorException e) {
                break;
            }
            break;
        }

        synchronized (selectorLock) {
            if (lifecycleState == SHUTDOWN_NOW) {
                selector.wakeup();
            }
        }
    }

    /**
     * Performs a single iteration of the reactor's event loop, and returns
     * a flag indicating whether the reactor is still running.  Only one
     * call may be active on a Reactor instance at a time.
     *
     * @return {@code false} if this reactor is stopped,
     *         otherwise {@code true}
     * @throws IOException if an I/O error occurs
     */
    boolean performWork() throws IOException {

        if (!selector.isOpen()) {
            log.log(Level.WARNING, "{0} selector is closed", this);
            return false;
        }

        synchronized (selectorLock) {
            // Obtain and release the guard to allow other tasks
            // to run after waking the selector.

            if (log.isLoggable(Level.FINER)) {
                int numKeys = selector.keys().size();
                log.log(Level.FINER, "{0} select on {1} keys",
                    new Object[] { this, numKeys });
                if (numKeys <= 5) {
                    for (SelectionKey key : selector.keys()) {
                        try {
                            log.log(Level.FINER,
                                " - {0} select interestOps {1} on {2}",
                                new Object[] {
                                this,
                                Util.formatOps(key.interestOps()),
                                key.attachment() });
                        } catch (CancelledKeyException e) {
                            log.log(Level.FINER,
                                " - {0} select cancelled key {1}",
                                new Object[] {
                                this,
                                key.attachment() });
                        }
                    }
                }
            }
        }

        int readyCount;

        // If there are any pending timeouts, block no longer than
        // the earliest.  Otherwise, block indefinitely.
        final Delayed nextExpiringTask = timeouts.peek();
        if (nextExpiringTask == null) {
            readyCount = selector.select();
        } else {
            long nextTimeoutMillis =
                nextExpiringTask.getDelay(TimeUnit.MILLISECONDS);
            if (nextTimeoutMillis <= 0) {
                readyCount = selector.selectNow();
            } else {
                readyCount = selector.select(nextTimeoutMillis);
            }
        }

        if (log.isLoggable(Level.FINER)) {
            log.log(Level.FINER, "{0} selected {1} / {2}",
                new Object[] { this, readyCount, selector.keys().size() });
        }

        if (log.isLoggable(Level.FINE)) {
            synchronized (selectorLock) {
                if (lifecycleState != RUNNING) {
                    log.log(Level.FINE,
                        "{0} wants shutdown, {1} keys",
                        new Object[] { this, selector.keys().size() });
                }
            }
        }

        // Check for shutdown *after* calling select(), so that cancelled
        // keys will have been removed from the selector's key set.
        synchronized (selectorLock) {
            if (lifecycleState != RUNNING) {
                if (selector.keys().isEmpty()) {
                    lifecycleState = DONE;
                    selector.close();
                    return false;
                }
            }
        }

        final Iterator<SelectionKey> keys =
            selector.selectedKeys().iterator();

        // Dispatch the ready keys to their handlers
        while (keys.hasNext()) {
            SelectionKey key = keys.next();
            keys.remove();

            ReactiveAsyncKey asyncKey =
                (ReactiveAsyncKey) key.attachment();

            int readyOps;
            synchronized (asyncKey) {
                if (!key.isValid()) {
                    continue;
                }
                try {
                    readyOps = key.readyOps();
                    key.interestOps(key.interestOps() & (~readyOps));
                } catch (CancelledKeyException e) {
                    // swallow exception
                    continue;
                }
            }
            asyncKey.selected(readyOps);
        }

        // Expire timed-out operations
        final List<TimeoutHandler> expiredHandlers =
            new ArrayList<TimeoutHandler>();
        timeouts.drainTo(expiredHandlers);

        for (TimeoutHandler expired : expiredHandlers) {
            expired.run();
        }

        expiredHandlers.clear();

        return true;
    }

    /**
     * Registers the given {@link SelectableChannel} with this reactor,
     * returning an {@link AsyncKey} that can be used to initiate asynchronous
     * operations on that channel.
     *
     * @param ch the {@code SelectableChannel} to register
     * @return an {@link AsyncKey} for the given channel
     *
     * @throws ShutdownChannelGroupException if the reactor is shutdown
     * @throws IOException if an IO error occurs
     */
    ReactiveAsyncKey
    register(SelectableChannel ch) throws IOException {
        synchronized (selectorLock) {
            if (lifecycleState != RUNNING) {
                throw new ShutdownChannelGroupException();
            }

            selector.wakeup();
            SelectionKey key = ch.register(selector, 0);

            ReactiveAsyncKey asyncKey = new ReactiveAsyncKey(key);
            key.attach(asyncKey);
            return asyncKey;
        }
    }

    /**
     * Registers interest in an IO operation on the channel associated with
     * the given {@link AsyncKey}, returning a future representing the
     * result of the operation.
     * <p>
     * When the requested operation becomes ready, the given {@code task}
     * is invoked so that it may perform the IO operation.  The selector's
     * interest in all ready operations is cleared before dispatching to the
     * task.
     * <p>
     * Several checks are performed on the channel at this point to avoid
     * race conditions where the check succeeds but the condition
     * immediately becomes false. We lock both the {@code selectorLock} and
     * the {@code asyncKey} to ensure that we get a proper view of the state
     * when registering the operation, and so that if the state later
     * changes the operation will be terminated properly.
     * <p>
     * If the channel is closed, {@link ClosedAsynchronousChannelException}
     * is thrown.
     * <p>
     * Additional checks are performed on {@code SocketChannel}s:
     * <ul>
     * <li>
     * If the requested operation is {@code OP_READ} or {@code OP_WRITE}
     * and the channel is not connected, {@link NotYetConnectedException}
     * is thrown.
     * <li>
     * If the requested operation is {@code OP_CONNECT} and the channel is
     * already connected, {@link AlreadyConnectedException} is thrown.
     * </ul>
     *
     * @param <R> the result type
     * @param asyncKey the key for async operations on the channel
     * @param op the {@link SelectionKey} operation requested
     * @param task the task to invoke when the operation becomes ready
     *
     * @throws ClosedAsynchronousChannelException if the channel is closed
     * @throws NotYetConnectedException if a read or write operation is
     *         requested on an unconnected {@code SocketChannel}
     * @throws AlreadyConnectedException if a connect operation is requested
     *         on a connected {@code SocketChannel}
     */
    <R> void
    awaitReady(ReactiveAsyncKey asyncKey, int op, AsyncOp<R> task)
    {
        synchronized (selectorLock) {
            selector.wakeup();
            int interestOps;
            synchronized (asyncKey) {
                SelectionKey key = asyncKey.key;
                if (key == null || (!key.isValid())) {
                    throw new ClosedAsynchronousChannelException();
                }

    try {
        interestOps = key.interestOps();
    } catch (CancelledKeyException e) {
        throw new ClosedAsynchronousChannelException();
    }

                SelectableChannel channel = asyncKey.channel();
               
                // These precondition checks don't belong here; they
                // should be refactored to AsyncSocketChannelImpl.
                // However, they need to occur inside the asyncKey
                // lock after we know the interest ops won't change,
                // so here they are.
                // Only SocketChannel has any extra checks to do.
                if (channel instanceof SocketChannel) {
                    switch (op) {
                    case OP_READ:
                    case OP_WRITE:
                        if (!((SocketChannel) channel).isConnected()) {
                            throw new NotYetConnectedException();
                        }
                        break;
                    case OP_CONNECT:
                        if (((SocketChannel) channel).isConnected()) {
                            throw new AlreadyConnectedException();
                        }
                        break;
                    default:
                        break;
                    }
                }

                // Check that op isn't already in the interest set
                assert (interestOps & op) == 0;

                interestOps |= op;
    try {
        key.interestOps(interestOps);
    } catch (CancelledKeyException e) {
        throw new ClosedAsynchronousChannelException();
    }
            }

            if (log.isLoggable(Level.FINEST)) {
                log.log(Level.FINEST,
                    "{0} awaitReady {1} : new {2} : added {3}",
                    new Object[] { this,
                                   task,
                                   Util.formatOps(interestOps),
                                   Util.formatOps(op) });
            }
        }
    }

    /**
     * A FutureTask that can be canceled by a timeout exception.
     *
     * @param <R> the result type
     */
    static class AsyncOp<R> extends FutureTask<R> {

        /**
         * Creates a new instance.
         *
         * @param callable the work to perform when this task is run
         */
        AsyncOp(Callable<R> callable) {
            super(callable);
        }

        /**
         * Completes this future as if its {@code run()} method threw
         * an {@code AbortedByTimeoutException}, unless this future
         * has already completed.
         */
        void timeoutExpired() {
            setException(new AbortedByTimeoutException());
        }
    }

    /**
     * Manages a single asynchronous IO operation for a
     * {@link ReactiveAsyncKey}. Behaves appropriately when no operation is
     * pending, or when a race occurs between, e.g., timeout and user
     * cancellation.
     */
    abstract class PendingOperation {

        /**
         * The continuation of the IO task to perform, if one is pending,
         * or a reference to {@code null} if an operation is not pending.
         */
        protected final AtomicReference<AsyncOp<?>> task =
            new AtomicReference<AsyncOp<?>>();

        /**
         * The timeout action for the pending task, or {@code null} if
         * there is no pending timeout.
         */
        private volatile TimeoutHandler timeoutHandler = null;

        /** The async key. */
        private final ReactiveAsyncKey asyncKey;

        /**  The selectable IO operation managed by this instance. */
        private final int op;

        /**
         * Creates a new instance to manage the given operation for the
         * given key.
         *
         * @param asyncKey the async key
         * @param op the operation to manage
         */
        PendingOperation(ReactiveAsyncKey asyncKey, int op) {
            this.asyncKey = asyncKey;
            this.op = op;
        }

        /**
         * Overridden by subclasses to take the appropriate action when an
         * attempt is made to invoke this operation while it is already
         * pending.
         * <p>
         * This method <strong>must always</strong> throw an unchecked
         * exception.
         */
        protected abstract void pendingPolicy();

        /**
         * Runs the pending operation, if any.
         *
         * @see AsyncKey#selected(int)
         */
        void selected() {
            Runnable selectedTask = task.getAndSet(null);
            if (selectedTask == null) {
                log.log(Level.FINEST,
                    "selected but nothing to do {0}", this);
                return;
            } else {
                log.log(Level.FINER, "selected {0}", this);
                selectedTask.run();
            }
        }

        /**
         * Returns {@code true} if this operation is pending, otherwise
         * {@code false}.
         *
         * @return {@code true} if this operation is pending, otherwise
         *         {@code false}
         *
         * @see AsyncKey#isOpPending(int)
         */
        boolean isPending() {
            return task.get() != null;
        }

        /**
         * Marks the operation as no-longer-pending, and cancels the timeout
         * expiration action for the task, if any.
         */
        void cleanupTask() {
            if (timeoutHandler != null) {
                timeouts.remove(timeoutHandler);
                timeoutHandler = null;
            }

            task.set(null);
        }

        /**
         * Attempts to initiate an asynchronous operation.  If an operation
         * is already pending, an appropriate exception is thrown by a
         * subclass via its
         * {@link PendingOperation#pendingPolicy() pendingPolicy()}.
         *
         * @param <R> the result type
         * @param <A> the attachment type
         * @param attachment the attachment for the completion handler; may
         *        be {@code null}
         * @param handler the completion handler; may be {@code null}
         * @param timeout the timeout, or {@code 0} indicating no timeout
         * @param unit the unit of the timeout
         * @param callable the IO action to perform when this operation is
         *        ready
         * @return an {@code IoFuture} representing the pending operation
         *
         * @see AsyncKey#execute(int, Object, CompletionHandler, long,
         *      TimeUnit, Callable)
         */
        <R, A> IoFuture<R, A>
        execute(final A attachment,
                final CompletionHandler<R, ? super A> handler,
                long timeout,
                TimeUnit unit,
                Callable<R> callable)
        {
            if (timeout < 0) {
                throw new IllegalArgumentException("Negative timeout");
            }

            AsyncOp<R> opTask = new AsyncOp<R>(callable) {
                @Override
                protected void done() {
                    // Clear the timeout and pending flag
                    cleanupTask();
                    // Invoke the completion handler, if any
                    asyncKey.runCompletion(handler, attachment, this);
                } };

            // Indicate that a task is pending
            if (!task.compareAndSet(null, opTask)) {
                pendingPolicy();
            }

            // Set the timeout handler for the pending task, if any
            if (timeout > 0) {
                timeoutHandler = new TimeoutHandler(opTask, timeout, unit);
                timeouts.add(timeoutHandler);
            }

            try {
                // Schedule this task for execution when it is ready
                Reactor.this.awaitReady(asyncKey, op, opTask);
            } catch (RuntimeException e) {
                // If a problem occurs, cancel the timeout and pending task,
                // and throw the exception to the caller as JSR-203 specs
                cleanupTask();
                throw e;
            }

            return AttachedFuture.wrap(opTask, attachment);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return String.format("PendingOp[key=%s,op=%s]",
                asyncKey, Util.opName(op));
        }
    }

    /**
     * Provides support for initiating asynchronous IO operations on
     * an underlying reactive channel registered with this {@link Reactor}.
     *
     * @see AsyncKey
     */
    class ReactiveAsyncKey implements AsyncKey {

        /**
         * The {@link SelectionKey} representing the underlying channel's
         * registration with this {@code Reactor}'s {@link Selector}.
         */
        final SelectionKey key;

        /** The handler for an asynchronous {@code accept} operation. */
        private final PendingOperation pendingAccept =
            new PendingOperation(this, OP_ACCEPT) {
                protected void pendingPolicy() {
                    throw new AcceptPendingException();
                } };

        /** The handler for an asynchronous {@code connect} operation. */
        private final PendingOperation pendingConnect =
            new PendingOperation(this, OP_CONNECT) {
                protected void pendingPolicy() {
                    throw new ConnectionPendingException();
                } };

        /** The handler for an asynchronous {@code read} operation. */
        private final PendingOperation pendingRead =
            new PendingOperation(this, OP_READ) {
                protected void pendingPolicy() {
                    throw new ReadPendingException();
                } };

        /** The handler for an asynchronous {@code write} operation. */
        private final PendingOperation pendingWrite =
            new PendingOperation(this, OP_WRITE) {
                protected void pendingPolicy() {
                    throw new WritePendingException();
                } };

        /**
         * Creates a new instance that wraps the given selector key.
         *
         * @param key the selection key that represents the underlying
         *        channel's registration with this reactor's selector
         */
        ReactiveAsyncKey(SelectionKey key) {
            this.key = key;
        }

        /**
         * {@inheritDoc}
         * <p>
         * This implementation does the following:
         * <ul>
         * <li>
         * Unregisters the underlying channel with the reactor
         * <li>
         * Closes the channel
         * <li>
         * Awakens any pending asynchronous operations so they can complete
         * with {@link AsynchronousCloseException} when they notice the
         * channel is closed.
         * </ul>
         */
        public void close() throws IOException {
            log.log(Level.FINER, "closing {0}", this);

            try {
    synchronized (this) {
        if (!key.isValid()) {
      log.log(Level.FINE, "key is already invalid {0}", this);
        }
        // Closing a channel does not require the selectorLock,
        // because it does not touch the selector key set directly.
        // (It does so indirectly via the cancelled key set, which
        // is guaranteed to block only briefly at most).
        key.channel().close();
    }
            } finally {
                // Wake up the selector to give it a chance to process our
                // removal, if it's waiting for shutdown.  We don't obtain
                // the selectorLock here because we don't have any work
                // to do that touches the selector's data structures.
                selector.wakeup();

                // Awaken any and all pending operations
    // NOTE: Neither the 'selectorLock' nor this instance's
    // lock should be held when invoking the 'selected'  method
    // below or deadlock can occur.  -- ann (3/17/09)
                selected(OP_ACCEPT | OP_CONNECT | OP_READ | OP_WRITE);
            }
        }

        /**
         * {@inheritDoc}
         */
        public boolean isOpPending(int op) {
            switch (op) {
            case OP_ACCEPT:
                return pendingAccept.isPending();
            case OP_CONNECT:
                return pendingConnect.isPending();
            case OP_READ:
                return pendingRead.isPending();
            case OP_WRITE:
                return pendingWrite.isPending();
            default:
                throw new IllegalArgumentException("bad op " + op);
            }
        }

        /**
         * {@inheritDoc}
         */
        public SelectableChannel channel() {
            return key.channel();
        }

        /**
         * {@inheritDoc}
         */
        public void selected(int readyOps) {
            // Dispatch writes first in hopes of reducing roundtrip latency
            if ((readyOps & OP_WRITE) != 0) {
                pendingWrite.selected();
            }
            if ((readyOps & OP_READ) != 0) {
                pendingRead.selected();
            }
            if ((readyOps & OP_CONNECT) != 0) {
                pendingConnect.selected();
            }
            if ((readyOps & OP_ACCEPT) != 0) {
                pendingAccept.selected();
            }
        }

        /**
         * {@inheritDoc}
         */
        public <R, A> IoFuture<R, A>
        execute(int op, A attachment, CompletionHandler<R, ? super A> handler,
                long timeout, TimeUnit unit, Callable<R> callable)
        {
            switch (op) {
            case OP_WRITE:
                return pendingWrite.execute(
                    attachment, handler, timeout, unit, callable);
            case OP_READ:
                return pendingRead.execute(
                    attachment, handler, timeout, unit, callable);
            case OP_CONNECT:
                return pendingConnect.execute(
                    attachment, handler, timeout, unit, callable);
            case OP_ACCEPT:
                return pendingAccept.execute(
                    attachment, handler, timeout, unit, callable);
            default:
                throw new IllegalArgumentException("bad op " + op);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void execute(Runnable command) {
            executor.execute(command);
        }

        /**
         * {@inheritDoc}
         */
        public <R, A> void
        runCompletion(CompletionHandler<R, A> handler,
                      A attachment,
                      Future<R> future)
        {
            if (handler == null) {
                return;
            }

            // TODO the spec indicates that we can run the
            // completion handler in the current thread, but
            // we should decide whether to run it via the
            // executor anyway.

            // Delegate to the group so that the uncaught exception handler
            // for the group can be used, if one is set.
            group.completionRunner(handler, attachment, future).run();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return String.format(
                "ReactiveAsyncKey[reactor=%s,channel=%s,valid=%b]",
                Reactor.this, key.channel(), key.isValid());
        }
    }

    /**
     * Represents a timeout action for a {@code PendingOperation}.
     * Instances are placed in the {@code timeouts} queue and run when they
     * have expired.
     * <p>
     * Tasks that complete before their timeout expires should remove their
     * {@code TimeoutHandler} from the queue, since timeouts may be much
     * longer than the typical operation and the timeouts queue could
     * become filled with obsolete handlers.
     */
    private static final class TimeoutHandler implements Delayed, Runnable {

        /** The task to notify upon timeout. */
        private final AsyncOp<?> task;

        /**
         * The absolute deadline, in milliseconds, since the epoch.
         *
         * @see System#currentTimeMillis()
         */
        private final long deadlineMillis;

        /**
         * Creates a new instance that will notify the given operation when
         * the (relative) timeout expires.
         *
         * @param task the task to notify
         * @param timeout the timeout
         * @param unit the unit of the timeout
         */
        TimeoutHandler(AsyncOp<?> task, long timeout, TimeUnit unit) {
            this.task = task;
            this.deadlineMillis =
                unit.toMillis(timeout) + System.currentTimeMillis();
        }

        /**
         * {@inheritDoc}
         * <p>
         * Invokes {@code timeoutExpired} on this handler's task object.
         */
        public void run() {
            task.timeoutExpired();
        }

        /** {@inheritDoc} */
        public long getDelay(TimeUnit unit) {
            return unit.convert(
                deadlineMillis - System.currentTimeMillis(),
                TimeUnit.MILLISECONDS);
        }

        /** {@inheritDoc} */
        public int compareTo(Delayed o) {
            if (o == this) {
                return 0;
            }
            if (o instanceof TimeoutHandler) {
                return Long.signum(
                    deadlineMillis - ((TimeoutHandler) o).deadlineMillis);
            } else {
                return Long.signum(getDelay(TimeUnit.MILLISECONDS) -
                                   o.getDelay(TimeUnit.MILLISECONDS));
            }
        }

        /** {@inheritDoc} */
        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }
            if (!(obj instanceof TimeoutHandler)) {
                return false;
            }
            TimeoutHandler other = (TimeoutHandler) obj;
            return (deadlineMillis == other.deadlineMillis) &&
                   task.equals(other.task);
        }

        /** {@inheritDoc} */
        @Override
        public int hashCode() {
            // high-order bits of deadlineMillis aren't useful for hashing
            return task.hashCode() ^ (int) deadlineMillis;
        }
    }
}
TOP

Related Classes of com.sun.sgs.impl.nio.Reactor$TimeoutHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.