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

com.sun.grizzly.TCPSelectorHandler Maven / Gradle / Ivy

/*
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2007-2008 Sun Microsystems, Inc. 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.html
 * or glassfish/bootstrap/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 glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [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.sun.grizzly;

import com.sun.grizzly.util.LogMessages;
import com.sun.grizzly.async.AsyncQueueReader;
import com.sun.grizzly.async.AsyncQueueWriter;
import com.sun.grizzly.async.TCPAsyncQueueWriter;
import com.sun.grizzly.async.TCPAsyncQueueReader;
import com.sun.grizzly.util.Utils;
import com.sun.grizzly.util.Cloner;
import com.sun.grizzly.util.Copyable;
import com.sun.grizzly.util.DataStructures;
import com.sun.grizzly.util.SelectionKeyAttachment;
import com.sun.grizzly.util.State;
import com.sun.grizzly.util.StateHolder;
import java.io.IOException;
import java.net.BindException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A SelectorHandler handles all java.nio.channels.Selector operations.
 * One or more instance of a Selector are handled by SelectorHandler.
 * The logic for processing of SelectionKey interest (OP_ACCEPT,OP_READ, etc.)
 * is usually defined using an instance of SelectorHandler.
 *
 * This class represents a TCP implementation of a SelectorHandler.
 * This class first bind a ServerSocketChannel to a TCP port and then start
 * waiting for NIO events.
 *
 * @author Jeanfrancois Arcand
 */
public class TCPSelectorHandler implements SelectorHandler, LinuxSpinningWorkaround {
    private static final Object NULL_ATTACHMENT = new Object();

    private int maxAcceptRetries = 5;

    /**
     * The ConnectorInstanceHandler used to return a new or pooled
     * ConnectorHandler
     */
    protected ConnectorInstanceHandler connectorInstanceHandler;


    /**
     * The list of {@link SelectionKeyOP} to register next time the
     * Selector.select is invoked.
     * can be combined read+write interest or Connect
     */
    protected final Queue selectorHandlerTasks =
            DataStructures.getCLQinstance(SelectorHandlerTask.class);

    protected final Queue postponedTasks =
            new LinkedList();

    /**
     * True if selector thread should execute the pendingIO events.
     */
    private boolean executePendingIOUsingSelectorThread = false;


    /**
     * The socket tcpDelay.
     *
     * Default value for tcpNoDelay is disabled (set to true).
     */
    protected boolean tcpNoDelay = true;


    /**
     * The socket reuseAddress
     */
    protected boolean reuseAddress = true;


    /**
     * The socket keepAlive mode.
     */
    protected boolean isKeepAlive = false;

    
    /**
     * The socket linger.
     */
    protected int linger = -1;


    /**
     * The socket time out
     */
    protected int socketTimeout = -1;


    protected Logger logger;


    /**
     * The server socket time out
     */
    protected int serverTimeout = 0;


    /**
     * The inet address to use when binding.
     */
    protected InetAddress inet;

    /**
     * Port to witch serverSocket is bound.
     */
     int port = -1;

    /**
     * The default port range.
     */
    protected PortRange portRange = new PortRange(18888);

    /**
     * The ServerSocket instance.
     */
    protected ServerSocket serverSocket;


    /**
     * The ServerSocketChannel.
     */
    protected ServerSocketChannel serverSocketChannel;


    /**
     * The single Selector.
     */
    protected Selector selector;


    /**
     * The Selector time out.
     */
    protected long selectTimeout = 1000;


    /**
     * Server socket backlog.
     */
    protected int ssBackLog = 4096;


    /**
     * Is this used for client only or client/server operation.
     */
    protected Role role = Role.CLIENT_SERVER;


    /**
     * The SelectionKeyHandler associated with this SelectorHandler.
     */
    protected SelectionKeyHandler selectionKeyHandler;


    /**
     * The ProtocolChainInstanceHandler used by this instance. If not set, and instance
     * of the DefaultInstanceHandler will be created.
     */
    protected ProtocolChainInstanceHandler instanceHandler;


    /**
     * The {@link ExecutorService} used by this instance. If null -
     * {@link Controller}'s {@link ExecutorService} will be used
     */
    protected ExecutorService threadPool;


    /**
     * {@link AsyncQueueWriter}
     */
    protected AsyncQueueWriter asyncQueueWriter;


    /**
     * {@link AsyncQueueWriter}
     */
    protected AsyncQueueReader asyncQueueReader;


    /**
     * Attributes, associated with the {@link SelectorHandler} instance
     */
    protected Map attributes;

    /**
     * This {@link SelectorHandler} StateHolder, which is shared among
     * SelectorHandler and its clones
     */
    protected StateHolder stateHolder = new StateHolder(true);

    /**
     * Flag, which shows whether shutdown was called for this {@link SelectorHandler}
     */
    protected final AtomicBoolean isShutDown = new AtomicBoolean(false);

    private long lastSpinTimestamp;
    private int emptySpinCounter;
    private final WeakHashMap spinnedSelectorsHistory;
    private final Object spinSync;

    /**
     * The size to which to set the send buffer
     * If this value is not greater than 0, it is not used.
     */
    protected int sendBufferSize = -1;

    /**
     * The size to which to set the receive buffer
     * If this value is not greater than 0, it is not used.
     */
    protected int receiveBufferSize = -1;

    public TCPSelectorHandler(){
        this(Role.CLIENT_SERVER);
    }


    /**
     * Create a TCPSelectorHandler only used with ConnectorHandler.
     *
     * @param isClient true if this SelectorHandler is only used
     * to handle ConnectorHandler.
     */
    public TCPSelectorHandler(boolean isClient) {
        this(boolean2Role(isClient));
    }


    /**
     * Create a TCPSelectorHandler only used with ConnectorHandler.
     *
     * @param role the TCPSelectorHandler {@link Role}
     */
    public TCPSelectorHandler(Role role) {
        this.role = role;
        logger = Controller.logger();
        if (Controller.isLinux) {
            spinnedSelectorsHistory = new WeakHashMap();
            spinSync = new Object();
        } else {
            spinnedSelectorsHistory = null;
            spinSync = null;
        }
    }

    public void copyTo(Copyable copy) {
        TCPSelectorHandler copyHandler = (TCPSelectorHandler) copy;
        copyHandler.selector = selector;
        if (selectionKeyHandler != null) {
            copyHandler.setSelectionKeyHandler(Cloner.clone(selectionKeyHandler));
        }

        copyHandler.instanceHandler = instanceHandler;
        copyHandler.attributes = attributes;
        copyHandler.selectTimeout = selectTimeout;
        copyHandler.serverTimeout = serverTimeout;
        copyHandler.inet = inet;
        copyHandler.portRange = portRange;
        copyHandler.ssBackLog = ssBackLog;
        copyHandler.tcpNoDelay = tcpNoDelay;
        copyHandler.linger = linger;
        copyHandler.isKeepAlive = isKeepAlive;
        copyHandler.socketTimeout = socketTimeout;
        copyHandler.logger = logger;
        copyHandler.reuseAddress = reuseAddress;
        copyHandler.connectorInstanceHandler = connectorInstanceHandler;
        copyHandler.stateHolder = stateHolder;
        copyHandler.executePendingIOUsingSelectorThread = executePendingIOUsingSelectorThread;
    }


    /**
     * Return the set of SelectionKey registered on this Selector.
     */
    public Set keys(){
        if (selector != null){
            return selector.keys();
        } else {
            throw new IllegalStateException("Selector is not created!");
        }
    }


    /**
     * Is the Selector open.
     */
    public boolean isOpen(){
        if (selector != null){
            return selector.isOpen();
        } else {
            return false;
        }
    }


    /**
     * Before invoking {@link Selector#select}, make sure the {@link ServerSocketChannel}
     * has been created. If true, then register all {@link SelectionKey} to the {@link Selector}.
     * @param ctx {@link Context}
     */
    public void preSelect(Context ctx) throws IOException {
        if (asyncQueueReader == null) {
            asyncQueueReader = new TCPAsyncQueueReader(this);
        }

        if (asyncQueueWriter == null) {
            asyncQueueWriter = new TCPAsyncQueueWriter(this);
        }

        if (selector == null){
            initSelector(ctx);
        } else {
            processPendingOperations(ctx);
        }
    }

    /**
     * init the Selector
     * @param ctx
     * @throws java.io.IOException
     */
    private final void initSelector(Context ctx) throws IOException{
        try {
            isShutDown.set(false);

            connectorInstanceHandler = new ConnectorInstanceHandler.
                    ConcurrentQueueDelegateCIH(
                    getConnectorInstanceHandlerDelegate());

            // Create the socket listener
            selector = Utils.openSelector();

            if (role != Role.CLIENT){
                serverSocketChannel = ServerSocketChannel.open();
                serverSocket = serverSocketChannel.socket();
                if( receiveBufferSize > 0 ) {
                    try {
                        serverSocket.setReceiveBufferSize( receiveBufferSize );
                    } catch( SocketException se ) {
                        if( logger.isLoggable( Level.FINE ) )
                            logger.log( Level.FINE, "setReceiveBufferSize exception ", se );
                    } catch( IllegalArgumentException iae ) {
                        if( logger.isLoggable( Level.FINE ) )
                            logger.log( Level.FINE, "setReceiveBufferSize exception ", iae );
                    }
                }
                
                if ( inet == null){
                    portRange.bind(serverSocket,ssBackLog );
                } else {
                    portRange.bind(serverSocket,inet, ssBackLog );
                }

                serverSocketChannel.configureBlocking(false);
                serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

                serverSocket.setSoTimeout(serverTimeout);

                // Ephemeral support
                port = serverSocket.getLocalPort();
                inet = serverSocket.getInetAddress();
            }
            ctx.getController().notifyReady();
        } catch (SocketException ex){
            throw new BindException(ex.getMessage() + ": " + port + "=" + this);
        }
    }

    /**
     *
     * @param ctx
     * @throws java.io.IOException
     */
    protected void processPendingOperations(final Context ctx) throws IOException {
        processPendingQueue(ctx, postponedTasks);
        processPendingQueue(ctx, selectorHandlerTasks);
    }

    private void processPendingQueue(final Context ctx,
            final Queue tasks) throws IOException {
        SelectorHandlerTask task;
        while((task = tasks.poll()) != null) {
            if (logger.isLoggable(Level.FINEST)) {
                logger.finest("Processing pending task: " + task);
            }

            task.run(ctx);
        }
    }

    private SelectionKey checkIfSpinnedKey(final SelectionKey key) {
        if (!key.isValid() && key.channel().isOpen() &&
                spinnedSelectorsHistory.containsKey(key.selector())) {
            final SelectionKey newKey = key.channel().keyFor(selector);
            newKey.attach(key.attachment());
            return newKey;
        }

        return key;
    }


    /**
     * Handle new OP_CONNECT ops.
     */
    protected void onConnectOp(Context ctx,
            ConnectChannelOperation selectionKeyOp) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKeyOp.getChannel();
        SocketAddress remoteAddress = selectionKeyOp.getRemoteAddress();
        CallbackHandler callbackHandler = selectionKeyOp.getCallbackHandler();

        CallbackHandlerSelectionKeyAttachment attachment = new
                CallbackHandlerSelectionKeyAttachment(callbackHandler);

        SelectionKey key = socketChannel.register(selector, 0, attachment);
        attachment.associateKey(key);

        boolean isConnected;
        try {
            isConnected = socketChannel.connect(remoteAddress);
        } catch(Exception e) {
            if (logger.isLoggable(Level.FINE)) {
                logger.log(Level.FINE, "Exception occured when tried to connect socket", e);
            }

            // set isConnected to true to let callback handler to know about the problem happened
            isConnected = true;
        }

        // if channel was connected immediately or exception occured
        if (isConnected) {
            onConnectInterest(key, ctx);
        } else {
            key.interestOps(SelectionKey.OP_CONNECT);
        }
    }


    /**
     * Execute the Selector.select(...) operations.
     * @param ctx {@link Context}
     * @return {@link Set} of {@link SelectionKey}
     */
    public Set select(Context ctx) throws IOException{
        if (postponedTasks.isEmpty()) {
            selector.select(selectTimeout);
        } else {
            selector.selectNow();
        }
        return selector.selectedKeys();
    }


    /**
     * Invoked after Selector.select().
     * @param ctx {@link Context}
     */
    public void postSelect(Context ctx) {
        selectionKeyHandler.expire(keys().iterator());
    }

    /**
     * {@inheritDoc}
     */
    public void addPendingIO(Runnable runnable){
        selectorHandlerTasks.add(new RunnableOperation(runnable));
        wakeUp();
    }

    /**
     * {@inheritDoc}
     */
    public void addPendingKeyCancel(SelectionKey key){
        selectorHandlerTasks.add(new SelectionKeyCancelOperation(key));
        wakeUp();
    }


    /**
     * Register a SelectionKey to this Selector.
* Storing each interest type in different queues removes the need of wrapper (SelectionKeyOP) * while lowering thread contention due to the load is spread out on different queues. * * @param key * @param ops */ public void register(SelectionKey key, int ops) { if (key == null) { throw new NullPointerException("SelectionKey parameter is null"); } selectorHandlerTasks.offer(new RegisterKeyOperation(key, ops)); wakeUp(); } public void register(SelectableChannel channel, int ops) { register(channel, ops, NULL_ATTACHMENT); } public void register(SelectableChannel channel, int ops, Object attachment) { if (channel == null) { throw new NullPointerException("SelectableChannel parameter is null"); } selectorHandlerTasks.offer(new RegisterChannelOperation(channel, ops, attachment)); wakeUp(); } /** * Workaround for NIO issue 6524172 */ private void wakeUp(){ try{ selector.wakeup(); } catch (NullPointerException ne){ // Swallow as this is a JDK issue. } } /** * Register a CallBackHandler to this Selector. * * @param remoteAddress remote address to connect * @param localAddress local address to bin * @param callbackHandler {@link CallbackHandler} * @throws java.io.IOException */ protected void connect(SocketAddress remoteAddress, SocketAddress localAddress, CallbackHandler callbackHandler) throws IOException { SelectableChannel selectableChannel = getSelectableChannel( remoteAddress, localAddress ); final ConnectChannelOperation connectKeyOp = new ConnectChannelOperation(selectableChannel, remoteAddress, callbackHandler); selectorHandlerTasks.offer(connectKeyOp); wakeUp(); } protected SelectableChannel getSelectableChannel( SocketAddress remoteAddress, SocketAddress localAddress ) throws IOException { SocketChannel newSocketChannel = SocketChannel.open(); Socket newSocket = newSocketChannel.socket(); if( receiveBufferSize > 0 ) { try { newSocket.setReceiveBufferSize( receiveBufferSize ); } catch( SocketException se ) { if( logger.isLoggable( Level.FINE ) ) logger.log( Level.FINE, "setReceiveBufferSize exception ", se ); } catch( IllegalArgumentException iae ) { if( logger.isLoggable( Level.FINE ) ) logger.log( Level.FINE, "setReceiveBufferSize exception ", iae ); } } if( sendBufferSize > 0 ) { try { newSocket.setSendBufferSize( sendBufferSize ); } catch( SocketException se ) { if( logger.isLoggable( Level.FINE ) ) logger.log( Level.FINE, "setSendBufferSize exception ", se ); } catch( IllegalArgumentException iae ) { if( logger.isLoggable( Level.FINE ) ) logger.log( Level.FINE, "setSendBufferSize exception ", iae ); } } newSocket.setReuseAddress( reuseAddress ); if( localAddress != null ) newSocket.bind( localAddress ); newSocketChannel.configureBlocking( false ); return newSocketChannel; } /** * {@inheritDoc} */ public void pause() { stateHolder.setState(State.PAUSED); } /** * {@inheritDoc} */ public void resume() { if (!State.PAUSED.equals(stateHolder.getState(false))) { throw new IllegalStateException("SelectorHandler is not in PAUSED state, but: " + stateHolder.getState(false)); } stateHolder.setState(State.STARTED); } /** * {@inheritDoc} */ public StateHolder getStateHolder() { return stateHolder; } /** * Shuntdown this instance by closing its Selector and associated channels. */ public void shutdown() { // If shutdown was called for this SelectorHandler if (isShutDown.getAndSet(true)) return; stateHolder.setState(State.STOPPED); if (selector != null) { try { boolean isContinue = true; while(isContinue) { try { for(SelectionKey selectionKey : selector.keys()) { selectionKeyHandler.close(selectionKey); } isContinue = false; } catch (ConcurrentModificationException e) { // ignore } } } catch (ClosedSelectorException e) { // If Selector is already closed - OK } } try{ if (serverSocket != null) { serverSocket.close(); serverSocket = null; } } catch (Throwable ex){ Controller.logger().log(Level.SEVERE, "serverSocket.close",ex); } try{ if (serverSocketChannel != null) { serverSocketChannel.close(); serverSocketChannel = null; } } catch (Throwable ex){ Controller.logger().log(Level.SEVERE, "serverSocketChannel.close",ex); } try{ if (selector != null) selector.close(); } catch (Throwable ex){ Controller.logger().log(Level.SEVERE, "selector.close",ex); } if (asyncQueueReader != null) { asyncQueueReader.close(); asyncQueueReader = null; } if (asyncQueueWriter != null) { asyncQueueWriter.close(); asyncQueueWriter = null; } selectorHandlerTasks.clear(); attributes = null; } /** * {@inheritDoc} */ public SelectableChannel acceptWithoutRegistration(SelectionKey key) throws IOException { boolean isAccepted; int retryNum = 0; do { try { isAccepted = true; return ((ServerSocketChannel) key.channel()).accept(); } catch (IOException ex) { if(!key.isValid()) throw ex; isAccepted = false; try { // Let's try to recover here from too many open file Thread.sleep(1000); } catch (InterruptedException ex1) { throw new IOException(ex1.getMessage()); } logger.log(Level.WARNING, LogMessages.WARNING_GRIZZLY_TCPSELECTOR_HANDLER_ACCEPTCHANNEL_EXCEPTION(), ex); } } while (!isAccepted && retryNum++ < maxAcceptRetries); throw new IOException("Accept retries exceeded"); } /** * Handle OP_ACCEPT. * @param ctx {@link Context} * @return always returns false */ public boolean onAcceptInterest(SelectionKey key, Context ctx) throws IOException{ SelectableChannel channel = acceptWithoutRegistration(key); if (channel != null) { configureChannel(channel); SelectionKey readKey = channel.register(selector, SelectionKey.OP_READ); readKey.attach(System.currentTimeMillis()); } return false; } /** * Handle OP_READ. * @param ctx {@link Context} * @param key {@link SelectionKey} * @return false if handled by a {@link CallbackHandler}, otherwise true */ public boolean onReadInterest(final SelectionKey key,final Context ctx) throws IOException{ // disable OP_READ on key before doing anything else key.interestOps(key.interestOps() & (~SelectionKey.OP_READ)); if (asyncQueueReader.isReady(key)) { invokeAsyncQueueReader(pollContext(ctx, key, Context.OpType.OP_READ)); return false; } Object attach = SelectionKeyAttachment.getAttachment(key); if (attach instanceof CallbackHandler){ NIOContext context = pollContext(ctx, key, Context.OpType.OP_READ); invokeCallbackHandler((CallbackHandler) attach, context); return false; } return true; } /** * Handle OP_WRITE. * * @param key {@link SelectionKey} * @param ctx {@link Context} */ public boolean onWriteInterest(final SelectionKey key,final Context ctx) throws IOException{ // disable OP_WRITE on key before doing anything else key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE)); if (asyncQueueWriter.isReady(key)) { invokeAsyncQueueWriter(pollContext(ctx, key, Context.OpType.OP_WRITE)); return false; } Object attach = SelectionKeyAttachment.getAttachment(key); if (attach instanceof CallbackHandler){ NIOContext context = pollContext(ctx, key, Context.OpType.OP_WRITE); invokeCallbackHandler((CallbackHandler) attach, context); return false; } return true; } /** * Handle OP_CONNECT. * @param key {@link SelectionKey} * @param ctx {@link Context} */ public boolean onConnectInterest(final SelectionKey key, Context ctx) throws IOException{ try { //logical replacement of 3 interest changes with 1 that is the complement to them. //if opaccept is known to not be of interest we could 0 the interest instead. key.interestOps(key.interestOps() & (SelectionKey.OP_ACCEPT) ); } catch(CancelledKeyException e) { // Even if key was cancelled - we need to notify CallBackHandler if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "CancelledKeyException occured when tried to change key interests", e); } } Object attach = SelectionKeyAttachment.getAttachment(key); if (attach instanceof CallbackHandler){ NIOContext context = pollContext(ctx, key, Context.OpType.OP_CONNECT); invokeCallbackHandler((CallbackHandler) attach, context); } return false; } /** * Invoke a CallbackHandler via a Context instance. * @param context {@link Context} * @throws java.io.IOException */ protected void invokeCallbackHandler(CallbackHandler callbackHandler, NIOContext context) throws IOException { IOEventioEvent = new IOEvent.DefaultIOEvent(context); context.setIOEvent(ioEvent); // Added because of incompatibility with Grizzly 1.6.0 context.setSelectorHandler(this); CallbackHandlerContextTask task = context.getCallbackHandlerContextTask(callbackHandler); boolean isRunInSeparateThread = true; if (callbackHandler instanceof CallbackHandlerDescriptor) { isRunInSeparateThread = ((CallbackHandlerDescriptor) callbackHandler). isRunInSeparateThread(context.getCurrentOpType()); } context.execute(task, isRunInSeparateThread); } /** * Invoke a {@link AsyncQueueReader} * @param context {@link Context} * @throws java.io.IOException */ protected void invokeAsyncQueueReader(NIOContext context) throws IOException { context.execute(context.getAsyncQueueReaderContextTask(asyncQueueReader)); } /** * Invoke a {@link AsyncQueueWriter} * @param context {@link Context} * @throws java.io.IOException */ protected void invokeAsyncQueueWriter(NIOContext context) throws IOException { context.execute(context.getAsyncQueueWriterContextTask(asyncQueueWriter)); } /** * Return an instance of the default {@link ConnectorHandler}, * which is the {@link TCPConnectorHandler} * @return {@link ConnectorHandler} */ public ConnectorHandler acquireConnectorHandler(){ if (selector == null || !selector.isOpen()){ throw new IllegalStateException("SelectorHandler not yet started"); } ConnectorHandler connectorHandler = connectorInstanceHandler.acquire(); connectorHandler.setController(Controller.getHandlerController(this)); return connectorHandler; } /** * Release a ConnectorHandler. */ public void releaseConnectorHandler(ConnectorHandler connectorHandler){ connectorInstanceHandler.release(connectorHandler); } /** * A token decribing the protocol supported by an implementation of this * interface */ public Controller.Protocol protocol(){ return Controller.Protocol.TCP; } // ------------------------------------------------------ Utils ----------// /** * {@inheritDoc} */ public void configureChannel(SelectableChannel channel) throws IOException{ Socket socket = ((SocketChannel) channel).socket(); channel.configureBlocking(false); if (!channel.isOpen()){ return; } try{ if(socketTimeout >= 0 ) { socket.setSoTimeout(socketTimeout); } } catch (SocketException ex){ if (logger.isLoggable(Level.FINE)){ logger.log(Level.FINE, "setSoTimeout exception ",ex); } } try{ if(linger >= 0 ) { socket.setSoLinger( true, linger); } } catch (SocketException ex){ if (logger.isLoggable(Level.FINE)){ logger.log(Level.FINE, "setSoLinger exception ",ex); } } try{ socket.setKeepAlive(isKeepAlive); } catch (SocketException ex){ if (logger.isLoggable(Level.FINE)){ logger.log(Level.FINE, "setKeepAlive exception ",ex); } } try{ socket.setTcpNoDelay(tcpNoDelay); } catch (SocketException ex){ if (logger.isLoggable(Level.FINE)){ logger.log(Level.FINE, "setTcpNoDelay exception ",ex); } } if( receiveBufferSize > 0 ) { try { socket.setReceiveBufferSize( receiveBufferSize ); } catch( SocketException se ) { if( logger.isLoggable( Level.FINE ) ) logger.log( Level.FINE, "setReceiveBufferSize exception ", se ); } catch( IllegalArgumentException iae ) { if( logger.isLoggable( Level.FINE ) ) logger.log( Level.FINE, "setReceiveBufferSize exception ", iae ); } } if( sendBufferSize > 0 ) { try { socket.setSendBufferSize( sendBufferSize ); } catch( SocketException se ) { if( logger.isLoggable( Level.FINE ) ) logger.log( Level.FINE, "setSendBufferSize exception ", se ); } catch( IllegalArgumentException iae ) { if( logger.isLoggable( Level.FINE ) ) logger.log( Level.FINE, "setSendBufferSize exception ", iae ); } } try{ socket.setReuseAddress(reuseAddress); } catch (SocketException ex){ if (logger.isLoggable(Level.FINE)){ logger.log(Level.FINE, "setReuseAddress exception ",ex); } } } // ------------------------------------------------------ Properties -----// /** * Max number of pendingIO tasks that will be executed per worker thread. * @return */ // public int getPendingIOlimitPerThread() { // return pendingIOlimitPerThread; // } // // /** // * Max number of pendingIO tasks that will be executed per worker thread. // * @param pendingIOlimitPerThread // */ // public void setPendingIOlimitPerThread(int pendingIOlimitPerThread) { // this.pendingIOlimitPerThread = pendingIOlimitPerThread; // } public final Selector getSelector() { return selector; } public final void setSelector(Selector selector) { this.selector = selector; } /** * {@inheritDoc} */ public AsyncQueueReader getAsyncQueueReader() { return asyncQueueReader; } /** * {@inheritDoc} */ public AsyncQueueWriter getAsyncQueueWriter() { return asyncQueueWriter; } public long getSelectTimeout() { return selectTimeout; } public void setSelectTimeout(long selectTimeout) { this.selectTimeout = selectTimeout; } public int getServerTimeout() { return serverTimeout; } public void setServerTimeout(int serverTimeout) { this.serverTimeout = serverTimeout; } public InetAddress getInet() { return inet; } public void setInet(InetAddress inet) { this.inet = inet; } /** * Gets this {@link SelectorHandler} current role. * TCPSelectorHandler could act as client, which corresponds to * {@link Role#CLIENT} or client-server, which corresponds * to the {@link Role#CLIENT_SERVER} * * @return the {@link Role} */ public Role getRole() { return role; } /** * Sets this {@link SelectorHandler} current role. * TCPSelectorHandler could act as client, which corresponds to * {@link Role#CLIENT} or client-server, which corresponds * to the {@link Role#CLIENT_SERVER} * * @param role the {@link Role} */ public void setRole(Role role) { this.role = role; } /** * Returns port number {@link SelectorHandler} is listening on * Similar to getPort(), but getting port number directly from * connection ({@link ServerSocket}, {@link DatagramSocket}). * So if default port number 0 was set during initialization, then getPort() * will return 0, but getPortLowLevel() will * return port number assigned by OS. * * @return port number or -1 if {@link SelectorHandler} was not initialized for accepting connections. * @deprecated Use {@link getPort} */ public int getPortLowLevel() { if (serverSocket != null) { return serverSocket.getLocalPort(); } return -1; } public int getPort() { return port; } public void setPort(int port) { this.port = port; this.portRange = new PortRange(port); } public PortRange getPortRange() { return portRange; } public void setPortRange(PortRange portRange) { this.portRange = portRange; } public int getSsBackLog() { return ssBackLog; } public void setSsBackLog(int ssBackLog) { this.ssBackLog = ssBackLog; } /** * Return the tcpNoDelay value used by the underlying accepted Sockets. * * Also see setTcpNoDelay(boolean tcpNoDelay) */ public boolean isTcpNoDelay() { return tcpNoDelay; } /** * Enable (true) or disable (false) the underlying Socket's * tcpNoDelay. * * Default value for tcpNoDelay is enabled (set to true), as according to * the performance tests, it performs better for most cases. * * The Connector side should also set tcpNoDelay the same as it is set here * whenever possible. */ public void setTcpNoDelay(boolean tcpNoDelay) { this.tcpNoDelay = tcpNoDelay; } public int getLinger() { return linger; } public void setLinger(int linger) { this.linger = linger; } public boolean isKeepAlive() { return isKeepAlive; } public void setKeepAlive(boolean isKeepAlive) { this.isKeepAlive = isKeepAlive; } public int getSocketTimeout() { return socketTimeout; } public void setSocketTimeout(int socketTimeout) { this.socketTimeout = socketTimeout; } public Logger getLogger() { return logger; } public void setLogger(Logger logger) { this.logger = logger; } public boolean isReuseAddress() { return reuseAddress; } public void setReuseAddress(boolean reuseAddress) { this.reuseAddress = reuseAddress; } /** * {@inheritDoc} */ public ExecutorService getThreadPool(){ return threadPool; } /** * {@inheritDoc} */ public void setThreadPool(ExecutorService threadPool){ this.threadPool = threadPool; } /** * {@inheritDoc} */ public Class getPreferredSelectionKeyHandler() { return DefaultSelectionKeyHandler.class; } /** * Get the SelectionKeyHandler associated with this SelectorHandler. */ public SelectionKeyHandler getSelectionKeyHandler() { return selectionKeyHandler; } /** * Set SelectionKeyHandler associated with this SelectorHandler. */ public void setSelectionKeyHandler(SelectionKeyHandler selectionKeyHandler) { this.selectionKeyHandler = selectionKeyHandler; this.selectionKeyHandler.setSelectorHandler(this); } /** * Set the {@link ProtocolChainInstanceHandler} to use for * creating instance of {@link ProtocolChain}. */ public void setProtocolChainInstanceHandler(ProtocolChainInstanceHandler instanceHandler){ this.instanceHandler = instanceHandler; } /** * Return the {@link ProtocolChainInstanceHandler} */ public ProtocolChainInstanceHandler getProtocolChainInstanceHandler(){ return instanceHandler; } /** * {@inheritDoc} */ public void closeChannel(SelectableChannel channel) { // channel could be either SocketChannel or ServerSocketChannel if (channel instanceof SocketChannel) { Socket socket = ((SocketChannel) channel).socket(); try { if (!socket.isInputShutdown()) socket.shutdownInput(); } catch (IOException e) { logger.log(Level.FINEST, "Unexpected exception during channel inputShutdown", e); } try { if (!socket.isOutputShutdown()) socket.shutdownOutput(); } catch (IOException e) { logger.log(Level.FINEST, "Unexpected exception during channel outputShutdown", e); } try{ socket.close(); } catch (IOException e) { logger.log(Level.FINEST, "Unexpected exception during socket close", e); } } try{ channel.close(); } catch (IOException e){ logger.log(Level.FINEST, "Unexpected exception during channel close", e); } if (asyncQueueReader != null) { asyncQueueReader.onClose(channel); } if (asyncQueueWriter != null) { asyncQueueWriter.onClose(channel); } } /** * Polls {@link Context} from pool and initializes it. * * @param serverContext {@link Controller} context * @param key {@link SelectionKey} * @return {@link Context} */ protected NIOContext pollContext(final Context serverContext, final SelectionKey key, final Context.OpType opType) { Controller c = serverContext.getController(); ProtocolChain protocolChain = instanceHandler != null ? instanceHandler.poll() : c.getProtocolChainInstanceHandler().poll(); final NIOContext context = (NIOContext)c.pollContext(); c.configureContext(key, opType, context, this); context.setProtocolChain(protocolChain); return context; } //--------------- ConnectorInstanceHandler ----------------------------- /** * Return factory object, which knows how * to create {@link ConnectorInstanceHandler} corresponding to the protocol * @return factory
*/ protected Callable getConnectorInstanceHandlerDelegate() { return new Callable() { public ConnectorHandler call() throws Exception { return new TCPConnectorHandler(); } }; } // ----------- AttributeHolder interface implementation ----------- // /** * Remove a key/value object. * Method is not thread safe * * @param key - name of an attribute * @return attribute which has been removed */ public Object removeAttribute(String key) { if (attributes == null) return null; return attributes.remove(key); } /** * Set a key/value object. * Method is not thread safe * * @param key - name of an attribute * @param value - value of named attribute */ public void setAttribute(String key, Object value) { if (attributes == null) { attributes = new HashMap(); } attributes.put(key, value); } /** * Return an object based on a key. * Method is not thread safe * * @param key - name of an attribute * @return - attribute value for the key, null if key * does not exist in attributes */ public Object getAttribute(String key) { if (attributes == null) return null; return attributes.get(key); } /** * Set a {@link Map} of attribute name/value pairs. * Old {@link AttributeHolder} values will not be available. * Later changes of this {@link Map} will lead to changes to the current * {@link AttributeHolder}. * * @param attributes - map of name/value pairs */ public void setAttributes(Map attributes) { this.attributes = attributes; } /** * Return a {@link Map} of attribute name/value pairs. * Updates, performed on the returned {@link Map} will be reflected in * this {@link AttributeHolder} * * @return - {@link Map} of attribute name/value pairs */ public Map getAttributes() { return attributes; } /** * Returns the {@link Role}, depending on isClient value * @param isClient true>tt>, if this SelectorHandler works in * the client mode, or false otherwise. * @return {@link Role} */ protected static Role boolean2Role(boolean isClient) { if (isClient) return Role.CLIENT; return Role.CLIENT_SERVER; } /** * {@inheritDoc} */ public void resetSpinCounter(){ emptySpinCounter = 0; } /** * {@inheritDoc} */ public int getSpinRate(){ if (emptySpinCounter++ == 0){ lastSpinTimestamp = System.nanoTime(); } else if (emptySpinCounter == 1000) { long deltatime = System.nanoTime() - lastSpinTimestamp; int contspinspersec = (int) (1000 * 1000000000L / deltatime); emptySpinCounter = 0; return contspinspersec; } return 0; } /** * {@inheritDoc} */ public void workaroundSelectorSpin() throws IOException { synchronized(spinSync) { spinnedSelectorsHistory.put(selector, System.currentTimeMillis()); SelectorHandlerRunner.switchToNewSelector(this); } } /** * {@inheritDoc} */ public SelectionKey keyFor(SelectableChannel channel) { if (Controller.isLinux) { synchronized(spinSync) { return channel.keyFor(selector); } } else { return channel.keyFor(selector); } } /** * Sets the sendBufferSize to the specified value * * @param size the size to which to set the send buffer. This value should be greater than 0. */ public void setSendBufferSize( int size ) { this.sendBufferSize = size; } /** * Sets the receiveBufferSize to the specified value * * @param size the size to which to set the receive buffer. This value should be greater than 0. */ public void setReceiveBufferSize( int size ) { this.receiveBufferSize = size; } /** * Return true, if selector thread has to be applied to execute I/O * operation, or false (by default), meaning that I/O operation could * be executed in the current thread. * * @return the executePendingIOUsingSelectorThread */ public boolean isExecutePendingIOUsingSelectorThread() { return executePendingIOUsingSelectorThread; } /** * Set true, if selector thread has to be applied to execute I/O * operation, or false (by default), meaning that I/O operation could * be executed in the current thread. * It's not safe to change this value, when TCPSelectorHandler has been already started. * * @param executePendingIOUsingSelectorThread the executePendingIOUsingSelectorThread to set */ public void setExecutePendingIOUsingSelectorThread(boolean executePendingIOUsingSelectorThread) { this.executePendingIOUsingSelectorThread = executePendingIOUsingSelectorThread; } /** * Max number of accept() failures before abording. * @param maxAcceptRetries */ public void setMaxAcceptRetries(int maxAcceptRetries){ this.maxAcceptRetries = maxAcceptRetries; } protected final class RegisterKeyOperation implements SelectorHandlerTask { private final SelectionKey selectionKey; private final int interest; public RegisterKeyOperation(SelectionKey selectionKey, int interest) { this.selectionKey = selectionKey; this.interest = interest; } public void run(Context context) throws IOException { SelectionKey localSelectionKey = selectionKey; if (Controller.isLinux) { localSelectionKey = checkIfSpinnedKey(selectionKey); } selectionKeyHandler.register(localSelectionKey, interest); } } protected final class RegisterChannelOperation implements SelectorHandlerTask { private final SelectableChannel channel; private final int interest; private final Object attachment; public RegisterChannelOperation(SelectableChannel channel, int interest, Object attachment) { this.channel = channel; this.interest = interest; this.attachment = attachment; } public void run(Context context) throws IOException { if (channel.isOpen()) { final SelectionKey key = channel.keyFor(selector); boolean isKeyValid = true; if (key == null || (isKeyValid = key.isValid())) { if (attachment == NULL_ATTACHMENT) { selectionKeyHandler.register(channel, interest); } else { selectionKeyHandler.register(channel, interest, attachment); } return; } if (!isKeyValid) { postponedTasks.add(this); } } } } protected final class ConnectChannelOperation implements SelectorHandlerTask { private final SelectableChannel channel; private final SocketAddress remoteAddress; private final CallbackHandler callbackHandler; private ConnectChannelOperation(SelectableChannel channel, SocketAddress remoteAddress, CallbackHandler callbackHandler) { this.channel = channel; this.remoteAddress = remoteAddress; this.callbackHandler = callbackHandler; } public void run(Context context) throws IOException { onConnectOp(context, this); } public CallbackHandler getCallbackHandler() { return callbackHandler; } public SelectableChannel getChannel() { return channel; } public SocketAddress getRemoteAddress() { return remoteAddress; } } protected final class RunnableOperation implements SelectorHandlerTask { private final Runnable task; public RunnableOperation(Runnable task) { this.task = task; } public void run(Context context) throws IOException { try { task.run(); } catch (Throwable t) { logger.log(Level.FINEST, "doExecutePendiongIO failed.", t); } } } protected final class SelectionKeyCancelOperation implements SelectorHandlerTask { private final SelectionKey selectionKey; public SelectionKeyCancelOperation(SelectionKey selectionKey) { this.selectionKey = selectionKey; } public void run(Context context) throws IOException { try { selectionKeyHandler.close(selectionKey); } catch (Throwable t) { logger.log(Level.FINEST, "doExecutePendiongIO failed.", t); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy