Package org.subethamail.smtp.server

Source Code of org.subethamail.smtp.server.ServerThread

package org.subethamail.smtp.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Semaphore;

import javax.annotation.concurrent.GuardedBy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

/**
* ServerThread accepts TCP connections to the server socket and starts a new
* {@link Session} thread for each connection which will handle the connection.
* On shutdown it terminates not only this thread, but the session threads too.
*/
class ServerThread extends Thread
{
  private final Logger log = LoggerFactory.getLogger(ServerThread.class);
  private final SMTPServer server;
  private final ServerSocket serverSocket;
  /**
   * A semaphore which is used to prevent accepting new connections by
   * blocking this thread if the allowed count of open connections is already
   * reached.
   */
  private final Semaphore connectionPermits;
  /**
   * The list of currently running sessions.
   */
  @GuardedBy("this")
  private final Set<Session> sessionThreads;
  /**
   * A flag which indicates that this SMTP port and all of its open
   * connections are being shut down.
   */
  private volatile boolean shuttingDown;

  public ServerThread(SMTPServer server, ServerSocket serverSocket)
  {
    super(ServerThread.class.getName() + " " + server.getDisplayableLocalSocketAddress());
    this.server = server;
    this.serverSocket = serverSocket;
    // reserve a few places for graceful disconnects with informative
    // messages
    int countOfConnectionPermits = server.getMaxConnections() + 10;
    this.connectionPermits = new Semaphore(countOfConnectionPermits);
    this.sessionThreads = new HashSet<Session>(countOfConnectionPermits * 4 / 3 + 1);
  }

  /**
   * This method is called by this thread when it starts up. To safely cause
   * this to exit, call {@link #shutdown()}.
   */
  @Override
  public void run()
  {
    MDC.put("smtpServerLocalSocketAddress", server.getDisplayableLocalSocketAddress());
    log.info("SMTP server {} started", server.getDisplayableLocalSocketAddress());

    try
    {
      runAcceptLoop();
      log.info("SMTP server {} stopped", server.getDisplayableLocalSocketAddress());
    }
    catch (RuntimeException e)
    {
      log.error("Unexpected exception in server socket thread, server is stopped", e);
      throw e;
    }
    catch (Error e)
    {
      log.error("Unexpected error in server socket thread, server is stopped", e);
      throw e;
    }
    finally
    {
      MDC.remove("smtpServerLocalSocketAddress");
    }
  }

  /**
   * Accept connections and run them in session threads until shutdown.
   */
  private void runAcceptLoop()
  {
    while (!this.shuttingDown)
    {
      try
      {
        // block if too many connections are open
        connectionPermits.acquire();
      }
      catch (InterruptedException consumed)
      {
        continue; // exit or retry
      }

      Socket socket = null;
      try
      {
        socket = this.serverSocket.accept();
      }
      catch (IOException e)
      {
        connectionPermits.release();
        // it also happens during shutdown, when the socket is closed
        if (!this.shuttingDown)
        {
          log.error("Error accepting connection", e);
          // prevent a possible loop causing 100% processor usage
          try
          {
            Thread.sleep(1000);
          }
          catch (InterruptedException consumed)
          {
            // fall through
          }
        }
        continue;
      }

      Session sessionThread;
      try
      {
        sessionThread = new Session(server, this, socket);
      }
      catch (IOException e)
      {
        connectionPermits.release();
        log.error("Error while starting a connection", e);
        try
        {
          socket.close();
        }
        catch (IOException e1)
        {
          log.debug("Cannot close socket after exception", e1);
        }
        continue;
      }
      // add thread before starting it,
      // because it will check the count of sessions
      synchronized (this)
      {
        this.sessionThreads.add(sessionThread);
      }
      sessionThread.start();
    }
  }

  /**
   * Closes the server socket and all client sockets.
   */
  public void shutdown()
  {
    // First make sure we aren't accepting any new connections
    shutdownServerThread();
    // Shut down any open connections.
    shutdownSessions();
  }

  private void shutdownServerThread()
  {
    shuttingDown = true;
    closeServerSocket();
    interrupt();
    try {
      join();
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }

  /**
   * Closes the serverSocket in an orderly way.
   */
  private void closeServerSocket()
  {
    try
    {
      this.serverSocket.close();
      log.debug("SMTP Server socket shut down");
    }
    catch (IOException e)
    {
      log.error("Failed to close server socket.", e);
    }
  }

  private void shutdownSessions()
  {
    // Copy the sessionThreads collection so the guarding lock on this
    // instance can be released before calling the Session.shutdown methods.
    // This is necessary to avoid a deadlock, because the terminating
    // session threads call back the sessionEnded function in this instance,
    // which locks this instance.
    List<Session> sessionsToBeClosed;
    synchronized (this) {
      sessionsToBeClosed = new ArrayList<Session>(sessionThreads);
    }
    for (Session sessionThread : sessionsToBeClosed)
    {
      sessionThread.shutdown();
    }
  }

  public synchronized boolean hasTooManyConnections()
  {
    return sessionThreads.size() > server.getMaxConnections();
  }

  public synchronized int getNumberOfConnections()
  {
    return sessionThreads.size();
  }

  /**
   * Registers that the specified {@link Session} thread ended. Session
   * threads must call this function.
   */
  public void sessionEnded(Session session)
  {
    synchronized (this)
    {
      sessionThreads.remove(session);
    }
    connectionPermits.release();
  }
}
TOP

Related Classes of org.subethamail.smtp.server.ServerThread

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.