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

net.grinder.communication.Acceptor Maven / Gradle / Ivy

The newest version!
// Copyright (C) 2003 - 2012 Philip Aston
// All rights reserved.
//
// This file is part of The Grinder software distribution. Refer to
// the file LICENSE which is part of The Grinder distribution for
// licensing details. The Grinder distribution is available on the
// Internet at http://grinder.sourceforge.net/
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package net.grinder.communication;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;

import net.grinder.common.UncheckedInterruptedException;
import net.grinder.util.ListenerSupport;
import net.grinder.util.TimeAuthority;
import net.grinder.util.thread.ExecutorFactory;
import net.grinder.util.thread.InterruptibleRunnable;
import net.grinder.util.thread.InterruptibleRunnableAdapter;


/**
 * Active object that accepts connections on a ServerSocket.
 *
 * @author Philip Aston
 */
public final class Acceptor {

  private final ServerSocket m_serverSocket;
  private final ExecutorService m_executor;
  private final BlockingQueue m_exceptionQueue =
    new ArrayBlockingQueue(10);

  /**
   * {@link ResourcePool}s indexed by {@link ConnectionType}.
   * Guarded by m_socketSets.
   */
  private final Map m_socketSets =
    new HashMap();

  /**
   * {@link ListenerSupport}s indexed by {@link ConnectionType}.
   * Guarded by m_listenerMap.
   */
  private final Map> m_listenerMap =
    new HashMap>();

  /** Guarded by m_socketSets. */
  private boolean m_isShutdown = false;
  private final TimeAuthority m_timeAuthority;

  /**
   * Constructor.
   *
   * @param addressString The TCP address to listen on. Zero-length
   * string => listen on all interfaces.
   * @param port The TCP port to listen to. 0 => use any free port.
   * @param numberOfThreads Number of acceptor threads.
   * @param timeAuthority Knows the time.
   * @throws CommunicationException If server socket could not be
   * bound.
   */
  public Acceptor(String addressString,
                  int port,
                  int numberOfThreads,
                  TimeAuthority timeAuthority)
    throws CommunicationException {

    m_timeAuthority = timeAuthority;

    if (addressString.length() > 0) {
      try {
        m_serverSocket =
          new ServerSocket(port, 50, InetAddress.getByName(addressString));
      }
      catch (IOException e) {
        UncheckedInterruptedException.ioException(e);
        throw new CommunicationException(
          "Could not bind to address '" + addressString + ':' + port + '\'', e);
      }
    }
    else {
      try {
        m_serverSocket = new ServerSocket(port, 50);
      }
      catch (IOException e) {
        UncheckedInterruptedException.ioException(e);
        throw new CommunicationException(
          "Could not bind to port '" + port + "' on local interfaces", e);
      }
    }

    m_executor = ExecutorFactory.createThreadPool("Acceptor", numberOfThreads);

    for (int i = 0; i < numberOfThreads; ++i) {
      m_executor.submit(
        new InterruptibleRunnableAdapter(new AcceptorRunnable()));
    }
  }

  /**
   * Shut down this acceptor.
   *
   * @throws CommunicationException If an IO exception occurs.
   */
  public void shutdown() throws CommunicationException {

    synchronized (m_socketSets) {
      // Prevent recursion and creation of new ResourcePools.

      if (m_isShutdown) {
        return;
      }

      m_isShutdown = true;
    }

    try {
      m_serverSocket.close();
    }
    catch (IOException e) {
      UncheckedInterruptedException.ioException(e);
      throw new CommunicationException("Error closing socket", e);
    }
    finally {
      // Interrupt the acceptor thread group.
      m_executor.shutdownNow();

      // We clone contents of m_socketSets and don't hold m_socketSets whilst
      // closing the ResourcePools to remove opportunity for dead lock with
      // events triggered by closing resources.
      final ResourcePool[] socketSets = cloneListOfSocketSets();

      for (int i = 0; i < socketSets.length; ++i) {
        socketSets[i].closeCurrentResources();
      }

      m_exceptionQueue.clear();
    }
  }

  private ResourcePool[] cloneListOfSocketSets() {
    final ResourcePool[] resourcePools;

    synchronized (m_socketSets) {
      resourcePools =
        m_socketSets.values().toArray(new ResourcePool[m_socketSets.size()]);
    }
    return resourcePools;
  }

  /**
   * Get the port this Acceptor is listening on.
   *
   * @return The port.
   */
  public int getPort() {
    return m_serverSocket.getLocalPort();
  }

  /**
   * Asynchronous exception handling.
  *
   * @return The exception, or {@code null} if this Acceptor has been shut down.
   */
  public Exception getPendingException() {
    synchronized (m_socketSets) {
      if (m_isShutdown) {
        return null;
      }
    }

    try {
      return m_exceptionQueue.take();
    }
    catch (InterruptedException e) {
      throw new UncheckedInterruptedException(e);
    }
  }

  /**
   * For unit tests.
   */
  Exception peekPendingException() {
    return m_exceptionQueue.peek();
  }


  /**
   * The number of connections that have been accepted and are still active.
   * Used by the unit tests.
   *
   * @return The number of accepted connections.
   */
  public int getNumberOfConnections() {
    final ResourcePool[] socketSets = cloneListOfSocketSets();

    int result = 0;

    for (int i = 0; i < socketSets.length; ++i) {
      result += socketSets[i].countActive();
    }

    return result;
  }

  /**
   * Listener interface.
   */
  public interface Listener {
    /**
     * A connection has been accepted.
     *
     * @param connectionType The type of the connection.
     * @param connection The connection identity.
     */
    void connectionAccepted(ConnectionType connectionType,
                            ConnectionIdentity connection);

    /**
     * A connection has been closed.
     *
     * @param connectionType The type of the connection.
     * @param connection The connection identity.
     */
    void connectionClosed(ConnectionType connectionType,
                          ConnectionIdentity connection);
  }

  /**
   * Add a new listener.
   *
   * @param connectionType The connection type.
   * @param listener The listener.
   */
  public void addListener(ConnectionType connectionType, Listener listener) {
    getListeners(connectionType).add(listener);
  }

  /**
   * Get a set of accepted connections.
   *
   * @param connectionType
   *          Identifies the set of connections to return.
   * @return A set of sockets, each wrapped in an {@link IdleAwareSocketWrapper}
   *         .
   * @throws ShutdownException
   *           If the acceptor is shutdown.
   */
  ResourcePool getSocketSet(final ConnectionType connectionType)
    throws ShutdownException {

    synchronized (m_socketSets) {
      if (m_isShutdown) {
        throw new ShutdownException("Acceptor has been shut down");
      }

      final ResourcePool original = m_socketSets.get(connectionType);

      if (original != null) {
        return original;
      }
      else {
        final ResourcePool newSocketSet = new ResourcePoolImplementation();

        newSocketSet.addListener(
          new ResourcePool.Listener() {
            public void resourceAdded(ResourcePool.Resource resource) {
              final ConnectionIdentity connection =
                ((SocketWrapper)resource).getConnectionIdentity();

              getListeners(connectionType).apply(
                new ListenerSupport.Informer() {
                  public void inform(Listener l) {
                    l.connectionAccepted(connectionType, connection);
                  }
                });
            }

            public void resourceClosed(ResourcePool.Resource resource) {
              final ConnectionIdentity connection =
                ((SocketWrapper)resource).getConnectionIdentity();

              getListeners(connectionType).apply(
                new ListenerSupport.Informer() {
                  public void inform(Listener l) {
                    l.connectionClosed(connectionType, connection);
                  }
                });
            }
          });

        m_socketSets.put(connectionType, newSocketSet);
        return newSocketSet;
      }
    }
  }

  /**
   * Get the listener list for a particular connection type.
   */
  private ListenerSupport getListeners(
    ConnectionType connectionType) {

    synchronized (m_listenerMap) {
      final ListenerSupport original =
        m_listenerMap.get(connectionType);

      if (original != null) {
        return original;
      }
      else {
        final ListenerSupport newList =
          new ListenerSupport();

        m_listenerMap.put(connectionType, newList);
        return newList;
      }
    }
  }

  private void discriminateConnection(Socket localSocket)
    throws IOException, ShutdownException {

    boolean closeSocket = true;

    try {
      final Connector.ConnectDetails connectDetails =
        Connector.read(localSocket.getInputStream());

      final SocketWrapper socketWrapper =
          new IdleAwareSocketWrapper(localSocket, m_timeAuthority);
      socketWrapper.setAddress(connectDetails.getAddress());

      // Possible minor race if the socket is closed between here...
      final ResourcePool.Closeable closeable =
        getSocketSet(connectDetails.getConnectionType()).add(socketWrapper);

      // .. and the time a listener is registered. Will pick up such a zombie
      // the next time we try to use the resource.
      socketWrapper.addClosedListener(new SocketWrapper.ClosedListener() {
          public void socketClosed() {
            closeable.close();
          }
        });

      // We did good.
      closeSocket = false;
    }
    catch (CommunicationException e) {
      try {
        m_exceptionQueue.put(e);
      }
      catch (InterruptedException ie) {
        throw new UncheckedInterruptedException(ie);
      }
    }
    finally {
      if (closeSocket) {
        try {
          localSocket.close();
        }
        catch (IOException ioException) {
          UncheckedInterruptedException.ioException(ioException);
          // Ignore.
        }
      }
    }
  }

  private class AcceptorRunnable implements InterruptibleRunnable {
    public void interruptibleRun() {
      try {
        while (true) {
          final Socket localSocket = m_serverSocket.accept();
          discriminateConnection(localSocket);
        }
      }
      catch (IOException e) {
        // Treat accept socket errors as fatal - we've probably been
        // shutdown. This includes InterruptedIOExceptions.
      }
      catch (ShutdownException e) {
        // Acceptor has been shutdown, exit.
      }
      finally {
        // Best effort to ensure our server socket is closed.
        try {
          shutdown();
        }
        catch (CommunicationException e) {
          // Ignore.
        }
      }
    }
  }

  /**
   * Indicates the Acceptor has been shut down.
   */
  public static final class ShutdownException extends CommunicationException {
    private ShutdownException(String s) {
      super(s);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy