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

org.jsl.collider.AcceptorImpl Maven / Gradle / Ivy

There is a newer version: 0.2.5
Show newest version
/*
 * Copyright (C) 2013 Sergey Zubarev, [email protected]
 *
 * This file is a part of JS-Collider framework.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 */

package org.jsl.collider;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.SocketAddress;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.channels.ServerSocketChannel;
import java.util.HashSet;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;


class AcceptorImpl extends SessionEmitterImpl
        implements ColliderImpl.ChannelHandler
{
    private class ChannelAcceptor extends ThreadPool.Runnable
    {
        public void runInThreadPool()
        {
            for (;;)
            {
                SocketChannel socketChannel;

                try
                {
                    socketChannel = m_serverChannel.accept();
                    if (socketChannel == null)
                        break;
                }
                catch (final IOException ex)
                {
                    if (s_logger.isLoggable(Level.WARNING))
                        s_logger.warning( m_localAddr + ": " + ex.toString() );
                    break;
                }

                try
                {
                    socketChannel.configureBlocking( false );
                }
                catch (final IOException ex)
                {
                    /* Having unblocking mode is critical,
                     * can't work with this socket.
                     */
                    if (s_logger.isLoggable(Level.WARNING))
                        s_logger.warning( m_localAddr + ": " + ex.toString() );

                    try
                    {
                        socketChannel.close();
                    }
                    catch (final IOException ex1)
                    {
                        if (s_logger.isLoggable(Level.WARNING))
                            s_logger.warning( m_localAddr + ": " + ex1.toString() );
                    }
                    continue;
                }

                boolean stop;
                m_lock.lock();
                try
                {
                    stop = m_stop;
                    if (stop)
                        m_stopped = true;
                    else
                        m_pendingOps++;
                }
                finally
                {
                    m_lock.unlock();
                }

                if (stop)
                {
                    /* Acceptor is being closed, socket channel not needed. */
                    try
                    {
                        socketChannel.close();
                    }
                    catch (final IOException ex)
                    {
                        if (s_logger.isLoggable(Level.FINE))
                            s_logger.fine( m_localAddr + ": " + ex.toString() + "." );
                    }
                    return;
                }

                m_collider.executeInSelectorThread( new SessionStarter1(socketChannel) );
            }

            m_collider.executeInSelectorThread( m_starter3 );
        }
    }

    private class SessionStarter1 extends ColliderImpl.SelectorThreadRunnable
    {
        private final SocketChannel m_socketChannel;

        public SessionStarter1( SocketChannel socketChannel )
        {
            m_socketChannel = socketChannel;
        }

        public int runInSelectorThread()
        {
            try
            {
                final SelectionKey selectionKey = m_socketChannel.register( m_selector, 0, null );
                m_collider.executeInThreadPool( new SessionStarter2(m_socketChannel, selectionKey) );
            }
            catch (final IOException ex)
            {
                /* Not necessary a framework problem,
                 * can happen in a case if peer closed connection.
                 */
                if (s_logger.isLoggable(Level.FINE))
                    s_logger.fine( m_localAddr + ": " + ex );
                releaseMonitor();
            }
            return 0;
        }
    }

    private class SessionStarter2 extends ThreadPool.Runnable
    {
        private final SocketChannel m_socketChannel;
        private final SelectionKey m_selectionKey;

        public SessionStarter2( SocketChannel socketChannel, SelectionKey selectionKey )
        {
            m_socketChannel = socketChannel;
            m_selectionKey = selectionKey;
        }

        public void runInThreadPool()
        {
            startSession( m_socketChannel, m_selectionKey );
        }
    }

    private class Starter1 extends ColliderImpl.SelectorThreadRunnable
    {
        public int runInSelectorThread()
        {
            m_lock.lock();
            try
            {
                assert( m_state == STARTING_0 );
                if (m_stop)
                    return 0;
                m_state = STARTING_1;
                m_pendingOps = 1;
            }
            finally
            {
                m_lock.unlock();
            }

            try
            {
                m_selectionKey = m_serverChannel.register( m_selector, 0, AcceptorImpl.this );
                m_collider.executeInThreadPool( new Starter2() );
                return 0;
            }
            catch (final IOException ex)
            {
                /* Any exception here means a bug in Collider framework. */
                if (s_logger.isLoggable(Level.WARNING))
                    s_logger.warning( m_localAddr + ": " + ex + "." );
            }

            try
            {
                m_serverChannel.close();
            }
            catch (final IOException ex1)
            {
                if (s_logger.isLoggable(Level.WARNING))
                    s_logger.warning( m_localAddr + ": " + ex1 + "." );
            }
            m_serverChannel = null;

            m_lock.lock();
            try
            {
                m_pendingOps = 0;
                m_cond.signalAll();
            }
            finally
            {
                m_lock.unlock();
            }

            return 0;
        }
    }

    private class Starter2 extends ThreadPool.Runnable
    {
        private void closeAndSignal()
        {
            /* Some threads waits in stopAndWait(),
             * but we need to close selection key
             * and server socket channel first.
             */
            m_selectionKey.cancel();
            m_selectionKey = null;

            try { m_serverChannel.close(); }
            catch (final IOException ex) { logException(ex); }
            m_serverChannel = null;

            m_lock.lock();
            try
            {
                assert( m_pendingOps == 1 );
                m_pendingOps = 0;
                m_cond.signalAll();
            }
            finally
            {
                m_lock.unlock();
            }
        }

        private boolean setStarting2( Thread thread )
        {
            m_lock.lock();
            try
            {
                assert( m_state == STARTING_1 );
                assert( m_pendingOps == 1 );
                if (!m_stop)
                {
                    m_callbackThreads.add( thread );
                    m_state = STARTING_2;
                    return true;
                }
            }
            finally
            {
                m_lock.unlock();
            }
            closeAndSignal();
            return false;
        }

        private boolean setRunning( Thread thread )
        {
            m_lock.lock();
            try
            {
                assert( m_state == STARTING_2 );
                if (!m_callbackThreads.remove(thread))
                {
                    assert( m_stop );
                    return false;
                }
                else if (!m_stop)
                {
                    m_state = RUNNING;
                    return true;
                }
            }
            finally
            {
                m_lock.unlock();
            }
            closeAndSignal();
            return false;
        }

        public void runInThreadPool()
        {
            final Thread currentThread = Thread.currentThread();
            if (setStarting2(currentThread))
            {
                m_acceptor.onAcceptorStarted( m_collider, m_serverChannel.socket().getLocalPort() );
                if (setRunning(currentThread))
                    m_collider.executeInSelectorThread( m_starter3 );
            }
        }
    }

    private class Starter3 extends ColliderImpl.SelectorThreadRunnable
    {
        public int runInSelectorThread()
        {
            assert( m_selectionKey.interestOps() == 0 );
            m_selectionKey.interestOps( SelectionKey.OP_ACCEPT );
            return 0;
        }
    }

    private class Stopper extends ColliderImpl.SelectorThreadRunnable
    {
        private int m_waits;

        public int runInSelectorThread()
        {
            final int interestOps = m_selectionKey.interestOps();

            if ((interestOps & SelectionKey.OP_ACCEPT) == 0)
            {
                boolean stopped;
                m_lock.lock();
                try
                {
                    stopped = m_stopped;
                }
                finally
                {
                    m_lock.unlock();
                }

                if (!stopped)
                {
                    m_waits++;
                    m_collider.executeInSelectorThreadLater( this );
                    return 0;
                }
            }

            if (s_logger.isLoggable(Level.FINE))
                s_logger.fine( m_localAddr + ": waits=" + m_waits + "." );

            m_selectionKey.cancel();
            m_selectionKey = null;

            try
            {
                m_serverChannel.close();
            }
            catch (final IOException ex)
            {
                if (s_logger.isLoggable(Level.WARNING))
                    s_logger.warning( m_localAddr + ": " + ex.toString() + "." );
            }
            m_serverChannel = null;

            /* It is better to release monitor after ServerSocketChannel close
             * to avoid possible race if caller of stopAndWait() will try
             * to accept the same address again.
             */
            releaseMonitor();
            return 0;
        }
    }

    private void releaseMonitor()
    {
        m_lock.lock();
        try
        {
            assert( m_pendingOps > 0 );
            final int pendingOps = --m_pendingOps;
            if (pendingOps > 0)
                return;
            m_cond.signalAll();
        }
        finally
        {
            m_lock.unlock();
        }
        m_collider.removeEmitterNoWait( m_acceptor );
    }

    protected void addThread( Thread thread )
    {
        if (s_logger.isLoggable(Level.FINE))
            s_logger.log( Level.FINE, m_localAddr.toString() );

        m_lock.lock();
        try
        {
            assert( !m_callbackThreads.contains(thread) );
            m_callbackThreads.add( thread );
        }
        finally
        {
            m_lock.unlock();
        }
    }

    protected void removeThreadAndReleaseMonitor( Thread thread )
    {
        if (s_logger.isLoggable(Level.FINE))
            s_logger.log( Level.FINE, m_localAddr.toString() );

        m_lock.lock();
        try
        {
            if (!m_callbackThreads.remove(thread))
                return;

            assert( m_pendingOps > 0 );
            final int pendingOps = --m_pendingOps;
            if (pendingOps > 0)
                return;

            m_cond.signalAll();
        }
        finally
        {
            m_lock.unlock();
        }
        m_collider.removeEmitterNoWait( m_acceptor );
    }

    protected void logException( Exception ex )
    {
        if (s_logger.isLoggable(Level.WARNING))
        {
            final StringWriter sw = new StringWriter();
            ex.printStackTrace( new PrintWriter(sw) );
            s_logger.log( Level.WARNING, m_localAddr + ":\n" + sw.toString() );
        }
    }

    private static final Logger s_logger = Logger.getLogger( "org.jsl.collider.Acceptor" );

    private final Acceptor m_acceptor;
    private final Selector m_selector;
    private final SocketAddress m_localAddr;
    private ServerSocketChannel m_serverChannel;
    private SelectionKey m_selectionKey;
    private final ChannelAcceptor m_channelAcceptor;
    private final Starter3 m_starter3;

    private final ReentrantLock m_lock;
    private final Condition m_cond;
    private final HashSet m_callbackThreads;
    private int m_pendingOps;
    private boolean m_stop;
    private boolean m_stopped;
    private int m_state;

    private static final int STARTING_0 = 0;
    private static final int STARTING_1 = 1;
    private static final int STARTING_2 = 2;
    private static final int RUNNING    = 3;

    public AcceptorImpl(
            ColliderImpl collider,
            RetainableDataBlockCache inputQueueDataBlockCache,
            Acceptor acceptor,
            int joinMessageMaxSize,
            RetainableByteBufferPool joinPool,
            Selector selector,
            ServerSocketChannel serverChannel )
    {
        super( collider, inputQueueDataBlockCache, acceptor, joinMessageMaxSize, joinPool );

        m_acceptor = acceptor;
        m_selector = selector;
        m_localAddr = serverChannel.socket().getLocalSocketAddress();

        m_serverChannel = serverChannel;
        m_channelAcceptor = new ChannelAcceptor();
        m_starter3 = new Starter3();

        m_lock = new ReentrantLock();
        m_cond = m_lock.newCondition();
        m_callbackThreads = new HashSet();
        m_pendingOps = 0;
        m_stop = false;
        m_stopped = false;
        m_state = STARTING_0;
    }

    public final void start()
    {
        /* Can be called after stopAndWait() */
        if (s_logger.isLoggable(Level.FINE))
            s_logger.fine( m_localAddr.toString() );
        m_collider.executeInSelectorThread( new Starter1() );
    }

    public void stopAndWait() throws InterruptedException
    {
        /* Could be much simpler,
         * but we need a possibility to stop the Acceptor while collider is not started yet,
         * from the onAcceptorStarted() and from the createSessionListener() callbacks as well.
         */
        final Thread currentThread = Thread.currentThread();
        int state;

        m_lock.lock();
        try
        {
            if (s_logger.isLoggable(Level.FINE))
            {
                s_logger.fine( m_localAddr +
                        ": state=" + m_state + " stop=" + m_stop + " pendingOps=" + m_pendingOps );
            }

            if (m_state == STARTING_0)
            {
                if (m_stop)
                {
                    while (m_pendingOps > 0)
                        m_cond.await();
                    return;
                }
                m_pendingOps = 1;
                m_stop = true;
                state = 0;
            }
            else if (m_state == STARTING_1)
            {
                /* Selection key is being created,
                 * will not take much time, let's wait.
                 */
                assert( !m_callbackThreads.contains(currentThread) );
                m_stop = true; /* Does not matter was it already set or not. */
                while (m_pendingOps > 0)
                    m_cond.await();
                return;
            }
            else if (m_state == STARTING_2)
            {
                /* onAcceptorStarted() is being called. */
                if (m_stop)
                {
                    while (m_pendingOps > 0)
                        m_cond.await();
                }
                else if (!m_callbackThreads.remove(currentThread))
                {
                    m_stop = true;
                    while (m_pendingOps > 0)
                        m_cond.await();
                    return;
                }
                m_stop = true;
                state = 1;
            }
            else if (m_stop)
            {
                /* Acceptor already being stopped. */
                if (m_callbackThreads.remove(currentThread))
                {
                    assert( m_pendingOps > 0 );
                    m_pendingOps--;
                }
                while (m_pendingOps > 0)
                    m_cond.await();
                return;
            }
            else
            {
                assert( !m_stopped );
                m_stop = true;
                state = 2;
            }
        }
        finally
        {
            m_lock.unlock();
        }

        if (s_logger.isLoggable(Level.FINE))
            s_logger.log( Level.FINE, m_localAddr + ": state=" + state );

        if (state == 0)
        {
            /* stopAndWait() called while the Acceptor did not started yet,
             * server socket channel is not registered in the selector yet,
             * and will not be registered, m_selectionKey should be null.
             */
            assert( m_selectionKey == null );

            try
            {
                m_serverChannel.close();
            }
            catch (final IOException ex)
            {
                if (s_logger.isLoggable(Level.WARNING))
                    s_logger.warning( m_localAddr + ": " + ex + "." );
            }
            m_serverChannel = null;

            m_collider.removeEmitterNoWait( m_acceptor );

            m_lock.lock();
            try
            {
                m_pendingOps = 0;
                m_cond.signalAll();
            }
            finally
            {
                m_lock.unlock();
            }
        }
        else if (state == 1)
        {
            /* stopAndWait() called while the Acceptor called onAcceptorStarted() */

            m_selectionKey.cancel();
            m_selectionKey = null;

            try
            {
                m_serverChannel.close();
            }
            catch (final IOException ex)
            {
                if (s_logger.isLoggable(Level.WARNING))
                    s_logger.warning( m_localAddr + ": " + ex + "." );
            }
            m_serverChannel = null;

            m_collider.removeEmitterNoWait( m_acceptor );

            m_lock.lock();
            try
            {
                assert( m_pendingOps == 1 );
                m_pendingOps = 0;
                m_cond.signalAll();
            }
            finally
            {
                m_lock.unlock();
            }
        }
        else /* (state == 2) */
        {
            m_collider.executeInSelectorThread( new Stopper() );

            m_lock.lock();
            try
            {
                if (m_callbackThreads.remove(currentThread))
                {
                    assert( m_pendingOps > 0 );
                    m_pendingOps--;
                }
                while (m_pendingOps > 0)
                    m_cond.await();
            }
            finally
            {
                m_lock.unlock();
            }
        }
    }

    public int handleReadyOps( ThreadPool threadPool )
    {
        assert( m_selectionKey.readyOps() == SelectionKey.OP_ACCEPT );
        threadPool.execute( m_channelAcceptor );
        m_selectionKey.interestOps(0);
        return 0;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy