package mireka.pop; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.HashSet; 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; /** * The POP3 server thread accepts connections on the server port. */ class ServerThread extends Thread { private final Logger logger = LoggerFactory.getLogger(ServerThread.class); private final ServerSocket serverSocket; private final PopServer server; private final Semaphore connectionPermits; private volatile boolean shuttingDown; @GuardedBy("this") private final Set<SessionThread> sessionThreads = new HashSet<SessionThread>(200); ServerThread(ServerSocket serverSocket, PopServer server) { super(ServerThread.class.getName() + " " + server.getDisplayableLocalSocketAddress()); this.serverSocket = serverSocket; this.server = server; // reserve a few places for graceful disconnects with informative // messages this.connectionPermits = new Semaphore(server.getMaximumConnections() + 10); } @Override public void run() { MDC.put("localServerSocketAddress", server.getDisplayableLocalSocketAddress()); logger.info("POP server {} started", server.getDisplayableLocalSocketAddress()); while (!shuttingDown) { try { connectionPermits.acquire(); } catch (InterruptedException e) { if (!shuttingDown) logger.debug("Server socket thread was interrupted " + "unexpectedly", e); Thread.currentThread().interrupt(); break; } SessionThread sessionThread; try { Socket socket = serverSocket.accept(); sessionThread = new SessionThread(server, this, socket); } catch (IOException e) { connectionPermits.release(); // it also happens during shutdown, when the socket is closed if (!shuttingDown) { logger.error("Error accepting connection", e); } continue; } // add thread before starting it, // because it will check the count of sessions synchronized (this) { sessionThreads.add(sessionThread); } sessionThread.start(); } closeServerSocket(); logger.info("POP server {} stopped accepting connections", server.getDisplayableLocalSocketAddress()); MDC.remove("localServerSocketAddress"); } public void shutdown() { shutdownServerSocket(); shutdownSessions(); try { awaitTermination(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } private void shutdownServerSocket() { shuttingDown = true; this.interrupt(); closeServerSocket(); try { this.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } private void shutdownSessions() { synchronized (this) { for (SessionThread sessionThread : sessionThreads) { sessionThread.shutdown(); } } } private void awaitTermination() throws InterruptedException { Set<SessionThread> stillRunningSessionThreads; synchronized (this) { stillRunningSessionThreads = new HashSet<>(this.sessionThreads); } for (SessionThread sessionThread : stillRunningSessionThreads) { sessionThread.join(); } } public boolean hasTooManyConnections() { synchronized (this) { return sessionThreads.size() > server.getMaximumConnections(); } } public int getNumberOfConnections() { synchronized (this) { return sessionThreads.size(); } } public void sessionEnded(SessionThread sessionThread) { synchronized (this) { sessionThreads.remove(sessionThread); } connectionPermits.release(); } /** * Closes the serverSocket in an orderly way */ private void closeServerSocket() { try { if (!serverSocket.isClosed()) serverSocket.close(); logger.debug("POP server socket shut down"); } catch (IOException e) { logger.error("Failed to close server socket.", e); } } }