Package de.netseeker.ejoe.core

Source Code of de.netseeker.ejoe.core.CombinedConnectionProcessor

/*********************************************************************
* CombinedConnectionProcessor.java
* created on 01.03.2005 by netseeker
* $Source: /cvsroot/ejoe/EJOE/src/de/netseeker/ejoe/core/CombinedConnectionProcessor.java,v $
* $Date: 2007/03/22 21:01:28 $
* $Revision: 1.3 $
*
* ====================================================================
*
*  Copyright 2006 netseeker aka Michael Manske
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
* ====================================================================
*
* This file is part of the ejoe framework.
* For more information on the author, please see
* <http://www.manskes.de/>.
*
*********************************************************************/
package de.netseeker.ejoe.core;

import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.logging.Level;
import java.util.logging.Logger;

import de.netseeker.ejoe.ConnectionHeader;
import de.netseeker.ejoe.EJConstants;
import de.netseeker.ejoe.ServerInfo;
import de.netseeker.ejoe.concurrent.ThreadPoolFactory;
import de.netseeker.ejoe.concurrent.ThreadPoolResizer;
import de.netseeker.ejoe.concurrent.ThreadService;
import de.netseeker.ejoe.io.IOUtil;

/**
* The CombinedConnectionProcessor handles the further processing of accepted client connections. It simply does only
* separate the readable connections from the incoming flood of accepted connections and schedules a new
* ConnectionProcessor which will handle read/write operations and adapter invoking for each of the selected
* connections. This is compareable to the *old BIO style* where one thread is used for each connection.
*
* @author netseeker
* @since 0.3.5
*/
public final class CombinedConnectionProcessor extends Thread implements ChannelRegistrar
{
    private static final Logger  logger       = Logger.getLogger( ConnectionAcceptor.class.getName() );

    private static final Integer INT_OP_WRITE = new Integer( SelectionKey.OP_WRITE );

    private static final Integer INT_OP_READ  = new Integer( SelectionKey.OP_READ );

    private ServerInfo           _serverInfo;

    private ThreadGroup          _readGroup, _writeGroup;

    private ThreadService        _readpool, _writePool;

    private Selector             _selector;

    private List                 _aspirants;

    private int                  _load        = 0;

    /**
     * Creates a new CombinedConnectionProcessor instance. Using this constructor will force the newly created instance
     * to use a non-blocking threadpool for invocation of read processor threads.
     *
     * @param serverInfo prefilled ConnectionHeader container server settings, eg. compression setting
     * @throws IOException
     */
    public CombinedConnectionProcessor(ServerInfo serverInfo) throws IOException
    {
        super( "EJOE CombinedConnectionProcessor" );
        this._serverInfo = serverInfo;
        // use our own ThreadGroups to simplify identifying EJOE threads in the
        // java (or systems) process list
        this._readGroup = new ThreadGroup( "EJOE_RP_THREADS" );
        this._writeGroup = new ThreadGroup( "EJOE_W_THREADS" );

        this._aspirants = Collections.synchronizedList( new ArrayList( 64 ) );

        // open a global Selector instance
        this._selector = Selector.open();

        // clean out previously cancelled keys - system might reuse selectors
        this._selector.selectNow();
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Thread#run()
     */
    public void run()
    {
        Timer timer = null;

        this._readpool = ThreadPoolFactory.createFixedThreadPool( this._serverInfo.getMaxReadProcessors(),
                                                                  this._readGroup );
        // create a global ThreadPool for network write operations
        this._writePool = ThreadPoolFactory.createFixedThreadPool( this._serverInfo.getMaxWriteProcessors(),
                                                                   this._writeGroup );

        // shall we monitor the ThreadPools and resize them
        // according to server load (this will also recreate died worker
        // threads)
        if ( this._serverInfo.isAutomaticThreadPoolResize() )
        {
            timer = new Timer();

            // schedule a monitor task for the read-process-thread-pool
            timer.schedule( new ThreadPoolResizer( this._readpool, this._serverInfo.getMaxReadProcessors(),
                                                   EJConstants.EJOE_POOL_RESIZER_SHRINKWAIT ), this._serverInfo
                    .getPoolResizePeriod(), this._serverInfo.getPoolResizePeriod() );
            // schedule a monitor task for the writer-thread-pool
            timer.schedule( new ThreadPoolResizer( this._writePool, this._serverInfo.getMaxWriteProcessors(),
                                                   EJConstants.EJOE_POOL_RESIZER_SHRINKWAIT ), this._serverInfo
                    .getPoolResizePeriod(), this._serverInfo.getPoolResizePeriod() );
        }

        try
        {
            Iterator it;
            SocketChannel cChannel;
            SelectionKey selKey;
            ConnectionHeader clientInfo;
            Set keys;

            while ( !isInterrupted() )
            {
                this._load = 0;

                // (pre)register interested socket channels
                registerAspirants();

                // just try endless new selects until there are interested
                // socket channels
                if ( _selector.select() == 0 ) continue;

                keys = this._selector.selectedKeys();
                this._load = keys.size();
                it = this._selector.selectedKeys().iterator();

                // loop over all selected channels, just take care of thread
                // interruption
                while ( it.hasNext() && !isInterrupted() )
                {
                    selKey = (SelectionKey) it.next();
                    // remove the SelectionKey from the Iterator otherwise it
                    // will be lost
                    it.remove();

                    try
                    {
                        // validate the key
                        if ( !selKey.isValid() ) continue;

                        // at least our ConnectionAcceptor has created a client
                        // ConnectionHeader
                        clientInfo = (ConnectionHeader) selKey.attachment();

                        // first check read-availbility
                        if ( selKey.isReadable() )
                        {
                            // get the underlying socket channel
                            cChannel = (SocketChannel) selKey.channel();
                            // cancel the channels registration with our
                            // Selector
                            selKey.cancel();

                            // little bit paranoia
                            if ( cChannel != null && cChannel.isOpen() )
                            {
                                // don't we support NIO?
                                if ( !this._serverInfo.hasNonBlockingReadWrite() )
                                {
                                    logger.log( Level.FINEST,
                                                "Setting socket to blocking mode for further io operations..." );

                                    // prepare the channel for upcoming blocking
                                    // network operations
                                    cChannel.configureBlocking( true );
                                }

                                // schedule a asynchronious read-process operation
                                this._readpool.invokeLater( new ConnectionReader( this, this._serverInfo, clientInfo ) );
                            }
                        }
                        else if ( selKey.isWritable() )
                        {
                            // get the underlying socket channel
                            cChannel = (SocketChannel) selKey.channel();
                            // cancel the channels registration with our
                            // Selector
                            selKey.cancel();

                            // little bit paranoia
                            if ( cChannel != null && cChannel.isOpen() )
                            {
                                // don't we support NIO?
                                if ( !this._serverInfo.hasNonBlockingReadWrite() )
                                {
                                    if ( !clientInfo.hasAttachment() ) continue;
                                    // prepare the channel for upcoming blocking
                                    // network operations
                                    cChannel.configureBlocking( true );
                                }

                                // schedule a asynchronious socket-write
                                // operation
                                this._writePool
                                        .invokeLater( new ConnectionWriter( this, this._serverInfo, clientInfo ) );
                            }
                        }
                    }
                    catch ( CancelledKeyException cke )
                    {
                        logger.log( Level.WARNING, "Key cancelled!", cke );
                    }
                    finally
                    {
                        this._load--;
                    }
                }
            }
        }
        catch ( IOException e )
        {
            logger.log( Level.SEVERE, "!!! IOException occured !!! ", e );
            throw new RuntimeException( e );
        }
        finally
        {
            // kill our ThreadPool monitors if existent
            if ( timer != null ) timer.cancel();

            try
            {
                // shutdown the Read-Process-ThreadPool if existent
                if ( this._readpool != null )
                {
                    this._readpool.stop();
                }
            }
            catch ( Exception e )
            {
                logger.log( Level.SEVERE, "!!! Error while stopping server !!!", e );
            }
            try
            {
                // shutdown the Write-ThreadPool
                this._writePool.stop();
            }
            catch ( Exception e )
            {
                logger.log( Level.SEVERE, "!!! Error while stopping server !!!", e );
            }

            // and finally close the global Selector
            IOUtil.closeQuiet( this._selector );
        }
    }

    /**
     * Registers all temporalily queued socket channels on the used {@link Selector}
     *
     * @throws IOException
     */
    private void registerAspirants() throws IOException
    {
        int count = 0;
        int size = this._aspirants.size();

        if ( size > 0 )
        {
            // clean out cancelled key list
            this._selector.selectNow();
            Object[] arr = null;
            ConnectionHeader header = null;
            SocketChannel channel = null;
            SelectionKey sk = null;

            for ( ; count < size; count++ )
            {
                arr = (Object[]) this._aspirants.remove( 0 );
                header = (ConnectionHeader) arr[0];
                channel = header.getChannel();
                // set a previously blocking socket channel to non-blocking
                // mode to allow selector operations on it
                if ( channel.isBlocking() )
                {
                    logger.log( Level.FINEST,
                                "Setting socket temporarily to non-blocking until further connection processing..." );
                    channel.configureBlocking( false );
                }

                sk = channel.keyFor( this._selector );
                if ( sk == null )
                {
                    // register with no interest
                    sk = channel.register( this._selector, 0 );
                }

                // attach the client connection header
                sk.attach( header );
                // now set the requested interest
                sk.interestOps( ((Integer) arr[1]).intValue() );
            }
        }

        if ( logger.isLoggable( Level.FINEST ) )
        {
            logger.log( Level.FINEST, System.currentTimeMillis() + " - registered " + count + " aspirants..." );
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see de.netseeker.ejoe.io.ReadWriteChannelRegistrar#register(de.netseeker.ejoe.ConnectionHeader, int)
     */
    public void register( ConnectionHeader clientInfo, int interest )
    {
        // store the connection header with it's contained socket channel in
        // the global queue
        this._aspirants
                .add( new Object[] { clientInfo, (interest == SelectionKey.OP_READ) ? INT_OP_READ : INT_OP_WRITE } );

        // break blocking select operations in the selector to allow
        // registration of the socket channels within the registration queue
        // after processing all currently selected socket channels
        this._selector.wakeup();
    }

    /*
     * (non-Javadoc)
     *
     * @see de.netseeker.ejoe.ChannelRegistrar#isValid()
     */
    public boolean isValid()
    {
        return isAlive() && !isInterrupted();
    }

    /*
     * (non-Javadoc)
     *
     * @see de.netseeker.ejoe.core.ChannelRegistrar#getLoad()
     */
    public int getLoad()
    {
        return this._load;
    }
}
TOP

Related Classes of de.netseeker.ejoe.core.CombinedConnectionProcessor

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.