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

com.pdd.pop.ext.glassfish.grizzly.nio.DefaultSelectorHandler Maven / Gradle / Ivy

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2008-2015 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.pdd.pop.ext.glassfish.grizzly.nio;

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.pdd.pop.ext.glassfish.grizzly.CompletionHandler;
import com.pdd.pop.ext.glassfish.grizzly.Grizzly;
import com.pdd.pop.ext.glassfish.grizzly.impl.FutureImpl;
import com.pdd.pop.ext.glassfish.grizzly.impl.SafeFutureImpl;
import com.pdd.pop.ext.glassfish.grizzly.utils.Futures;
import com.pdd.pop.ext.glassfish.grizzly.utils.JdkVersion;

/**
 * Default implementation of NIO SelectorHandler
 * 
 * @author Alexey Stashok
 */
public class DefaultSelectorHandler implements SelectorHandler {
    private static final long DEFAULT_SELECT_TIMEOUT_MILLIS = 30000;

    private static final Logger logger = Grizzly.logger(DefaultSelectorHandler.class);

    public static final boolean IS_WORKAROUND_SELECTOR_SPIN =
            Boolean.getBoolean(DefaultSelectorHandler.class.getName() + ".force-selector-spin-detection") ||
            (System.getProperty("os.name").equalsIgnoreCase("linux") &&
                    JdkVersion.getJdkVersion().compareTo("1.7.0") < 0);
    
    protected final long selectTimeout;

    // Selector spin workaround artifacts

    /**
     * The threshold for detecting selector.select spin on linux,
     * used for enabling workaround to prevent server from hanging.
     */
    private static final int SPIN_RATE_THRESHOLD = 2000;

    // ----------------

    public DefaultSelectorHandler() {
        this(DEFAULT_SELECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    }

    public DefaultSelectorHandler(final long selectTimeout, final TimeUnit timeunit) {
        this.selectTimeout = TimeUnit.MILLISECONDS.convert(selectTimeout, timeunit);
    }

    @Override
    public long getSelectTimeout() {
        return selectTimeout;
    }

    @Override
    public boolean preSelect(final SelectorRunner selectorRunner) throws IOException {
        return processPendingTasks(selectorRunner);
    }

    @Override
    public Set select(final SelectorRunner selectorRunner)
            throws IOException {
        final Selector selector = selectorRunner.getSelector();
        final boolean hasPostponedTasks =
                !selectorRunner.getPostponedTasks().isEmpty();
        
        // The selector.select(...) returns the *new* SelectionKey count,
        // so it may return 0 even in the case, when there are unprocessed, but
        // ready SelectionKeys in the Selector's selected key set.
        if (!hasPostponedTasks) {
            selector.select(selectTimeout);
        } else {
            selector.selectNow();
        }

        final Set selectedKeys = selector.selectedKeys();

        if (IS_WORKAROUND_SELECTOR_SPIN) {
            selectorRunner.checkSelectorSpin(
                    !selectedKeys.isEmpty() || hasPostponedTasks,
                    SPIN_RATE_THRESHOLD);
        }

        return selectedKeys;
    }

    @Override
    public void postSelect(final SelectorRunner selectorRunner) throws IOException {
    }

    @Override
    public void registerKeyInterest(final SelectorRunner selectorRunner,
            final SelectionKey key, final int interest) throws IOException {
        if (isSelectorRunnerThread(selectorRunner)) {
            registerKey0(key, interest);
        } else {
            selectorRunner.addPendingTask(new RegisterKeyTask(key, interest));
        }
    }

    private static void registerKey0(final SelectionKey selectionKey, final int interest) {
        if (selectionKey.isValid()) {
            final int currentOps = selectionKey.interestOps();
            if ((currentOps & interest) != interest) {
                selectionKey.interestOps(currentOps | interest);
            }
        }
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public void deregisterKeyInterest(final SelectorRunner selectorRunner,
            final SelectionKey key, final int interest) throws IOException {
        if (key.isValid()) {
            final int currentOps = key.interestOps();
            if ((currentOps & interest) != 0) {
                key.interestOps(currentOps & (~interest));
            }
        }
    }

    @Override
    public void registerChannel(final SelectorRunner selectorRunner,
            final SelectableChannel channel, final int interest,
            final Object attachment) throws IOException {
        
        final FutureImpl future =
                SafeFutureImpl.create();

        registerChannelAsync(selectorRunner, channel, interest,
                attachment, Futures.toCompletionHandler(future));
        try {
            future.get(selectTimeout, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            throw new IOException(e.getMessage());
        }
    }

    @Override
    public void registerChannelAsync(
            SelectorRunner selectorRunner, SelectableChannel channel,
            int interest, Object attachment,
            CompletionHandler completionHandler) {

        if (isSelectorRunnerThread(selectorRunner)) {
            registerChannel0(selectorRunner, channel, interest, attachment,
                    completionHandler);
        } else {
            addPendingTask(selectorRunner,
                    new RegisterChannelOperation(
                    channel, interest, attachment, completionHandler));
        }
    }

    @Override
    public void deregisterChannel(final SelectorRunner selectorRunner,
                                  final SelectableChannel channel)
            throws IOException {

        final FutureImpl future =
                SafeFutureImpl.create();

        deregisterChannelAsync(selectorRunner, channel,
                Futures.toCompletionHandler(future));
        try {
            future.get(selectTimeout, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            throw new IOException(e.getMessage());
        }
    }

    @Override
    public void deregisterChannelAsync(
            final SelectorRunner selectorRunner, final SelectableChannel channel,
            final CompletionHandler completionHandler) {

        if (isSelectorRunnerThread(selectorRunner)) {
            deregisterChannel0(selectorRunner, channel,
                    completionHandler);
        } else {
            addPendingTask(selectorRunner, new DeregisterChannelOperation(
                    channel, completionHandler));
        }
    }

    @Override
    public void execute(
            final SelectorRunner selectorRunner, final Task task,
            final CompletionHandler completionHandler) {
        if (isSelectorRunnerThread(selectorRunner)) {
            try {
                task.run();
                if (completionHandler != null) {
                    completionHandler.completed(task);
                }
            } catch (Exception e) {
                if (completionHandler != null) {
                    completionHandler.failed(e);
                }
            }            
        } else {
            addPendingTask(selectorRunner, new RunnableTask(task,
                    completionHandler));
        }
    }

    @Override
    public void enque(
            final SelectorRunner selectorRunner, final Task task,
            final CompletionHandler completionHandler) {
        
        if (isSelectorRunnerThread(selectorRunner)) {
            // If this is Selector thread - put the task to postponed queue
            // it's faster than using pending task queue, which is thread-safe
            final Queue postponedTasks =
                    selectorRunner.getPostponedTasks();
            postponedTasks.offer(new RunnableTask(task, completionHandler));
            
        } else {
            addPendingTask(selectorRunner, new RunnableTask(task,
                    completionHandler));
        }
    }
    
    private void addPendingTask(final SelectorRunner selectorRunner,
            final SelectorHandlerTask task) {

        if (selectorRunner == null) {
            task.cancel();
            return;
        }

        selectorRunner.addPendingTask(task);
        
        if (selectorRunner.isStop() &&
                selectorRunner.getPendingTasks().remove(task)) {
            task.cancel();
        }
    }

    private boolean processPendingTasks(final SelectorRunner selectorRunner)
            throws IOException {

        return processPendingTaskQueue(selectorRunner, selectorRunner.obtainPostponedTasks())
                &&
                (!selectorRunner.hasPendingTasks ||
                processPendingTaskQueue(selectorRunner, selectorRunner.getPendingTasks()));
    }

    private boolean processPendingTaskQueue(final SelectorRunner selectorRunner,
            final Queue selectorHandlerTasks)
            throws IOException {
        SelectorHandlerTask selectorHandlerTask;
        while((selectorHandlerTask = selectorHandlerTasks.poll()) != null) {
            if (!selectorHandlerTask.run(selectorRunner)) {
                return false;
            }
        }
        
        return true;
    }

    private static void registerChannel0(final SelectorRunner selectorRunner,
            final SelectableChannel channel, final int interest,
            final Object attachment,
            final CompletionHandler completionHandler) {

        try {
            if (channel.isOpen()) {

                final Selector selector = selectorRunner.getSelector();

                final SelectionKey key = channel.keyFor(selector);

                // Check whether the channel has been registered on a selector
                if (key == null || key.isValid()) {
                    // If channel is not registered or key is valid
                    final SelectionKey registeredSelectionKey =
                            channel.register(selector, interest, attachment);

                    selectorRunner.getTransport().
                            getSelectionKeyHandler().
                            onKeyRegistered(registeredSelectionKey);

                    final RegisterChannelResult result =
                            new RegisterChannelResult(selectorRunner,
                            registeredSelectionKey, channel);

                    if (completionHandler != null) {
                        completionHandler.completed(result);
                    }
                } else {
                    // Channel has been registered already,
                    // but the key is not valid
                    final Queue postponedTasks =
                            selectorRunner.getPostponedTasks();
                    final RegisterChannelOperation operation =
                            new RegisterChannelOperation(channel, interest,
                            attachment, completionHandler);

                    postponedTasks.add(operation);
                }
            } else {
                failChannelRegistration(completionHandler,
                        new ClosedChannelException());
            }
        } catch (IOException e) {
            failChannelRegistration(completionHandler, e);
        }
    }

    private static void failChannelRegistration(
            final CompletionHandler completionHandler,
            final Throwable error) {

        if (completionHandler != null) {
            completionHandler.failed(error);
        }
    }

    private static void deregisterChannel0(final SelectorRunner selectorRunner,
                                    final SelectableChannel channel,
                                    final CompletionHandler completionHandler) {

        final Throwable error;
        try {
            if (channel.isOpen()) {

                final Selector selector = selectorRunner.getSelector();

                final SelectionKey key = channel.keyFor(selector);

                // Check whether the channel has been registered on a selector
                if (key != null) {
                    // If channel is registered
                    selectorRunner.getTransport().getSelectionKeyHandler().cancel(key);

                    selectorRunner.getTransport().
                            getSelectionKeyHandler().
                            onKeyDeregistered(key);

                    final RegisterChannelResult result =
                            new RegisterChannelResult(selectorRunner,
                            key, channel);

                    if (completionHandler != null) {
                        completionHandler.completed(result);
                    }
                    return;
                }

                error = new IllegalStateException("Channel is not registered");
            } else {
                error = new ClosedChannelException();
            }
            
            Futures.notifyFailure(null, completionHandler, error);
        } catch (IOException e) {
            Futures.notifyFailure(null, completionHandler, e);
        }
    }
    
    @Override
    public boolean onSelectorClosed(final SelectorRunner selectorRunner) {
        try {
            selectorRunner.workaroundSelectorSpin();
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean isSelectorRunnerThread(final SelectorRunner selectorRunner) {
        return selectorRunner != null &&
                Thread.currentThread() == selectorRunner.getRunnerThread();
    }

    protected static final class RegisterKeyTask implements SelectorHandlerTask {
        private final SelectionKey selectionKey;
        private final int interest;

        public RegisterKeyTask(final SelectionKey selectionKey, final int interest) {
            this.selectionKey = selectionKey;
            this.interest = interest;
        }

        @Override
        public boolean run(final SelectorRunner selectorRunner) throws IOException {
            SelectionKey localSelectionKey = selectionKey;
            if (IS_WORKAROUND_SELECTOR_SPIN) {
                localSelectionKey = selectorRunner.checkIfSpinnedKey(selectionKey);
            }

            registerKey0(localSelectionKey, interest);
            
            return true;
        }

        @Override
        public void cancel() {
        }
    }

    protected static final class RegisterChannelOperation implements SelectorHandlerTask {
        private final SelectableChannel channel;
        private final int interest;
        private final Object attachment;
        private final CompletionHandler completionHandler;

        private RegisterChannelOperation(final SelectableChannel channel,
                final int interest, final Object attachment,
                final CompletionHandler completionHandler) {
            this.channel = channel;
            this.interest = interest;
            this.attachment = attachment;
            this.completionHandler = completionHandler;
        }

        @Override
        public boolean run(final SelectorRunner selectorRunner) throws IOException {
            registerChannel0(selectorRunner, channel, interest,
                    attachment, completionHandler);
            return true;
        }

        @Override
        public void cancel() {
            if (completionHandler != null) {
                completionHandler.failed(new IOException("Selector is closed"));
            }
        }
    }
    
    protected static final class RunnableTask implements SelectorHandlerTask {
        private final Task task;
        private final CompletionHandler completionHandler;

        private RunnableTask(final Task task,
                final CompletionHandler completionHandler) {
            this.task = task;
            this.completionHandler = completionHandler;
        }

        @Override
        public boolean run(final SelectorRunner selectorRunner) throws IOException {
            boolean continueExecution = true;
            
            try {
                continueExecution = task.run();
                if (completionHandler != null) {
                    completionHandler.completed(task);
                }
            } catch (Throwable t) {
                logger.log(Level.FINEST, "doExecutePendiongIO failed.", t);
                
                if (completionHandler != null) {
                    completionHandler.failed(t);
                }
            }
            
            return continueExecution;
        }

        @Override
        public void cancel() {
            if (completionHandler != null) {
                completionHandler.failed(new IOException("Selector is closed"));
            }
        }
    }
    

    protected static final class DeregisterChannelOperation implements SelectorHandlerTask {
        private final SelectableChannel channel;
        private final CompletionHandler completionHandler;

        private DeregisterChannelOperation(final SelectableChannel channel,
                                           final CompletionHandler completionHandler) {
            this.channel = channel;
            this.completionHandler = completionHandler;
        }

        @Override
        public boolean run(final SelectorRunner selectorRunner) throws IOException {
            deregisterChannel0(selectorRunner, channel, completionHandler);
            return true;
        }

        @Override
        public void cancel() {
            if (completionHandler != null) {
                completionHandler.failed(new IOException("Selector is closed"));
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy