<commit_before>/*
 * Copyright (c) 2014 <NAME> / Icegreen Technologies. All Rights Reserved.
 * This software is released under the Apache license 2.0
 */
package com.icegreen.greenmail.server;

import com.icegreen.greenmail.Managers;
import com.icegreen.greenmail.util.DummySSLServerSocketFactory;
import com.icegreen.greenmail.util.ServerSetup;
import com.icegreen.greenmail.util.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Store;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.*;

/**
 * @author <NAME>
 * @version $Id: $
 * @since Feb 2, 2006
 */
public abstract class AbstractServer extends Thread implements Service {
    protected final Logger log = LoggerFactory.getLogger(getClass());
    protected final InetAddress bindTo;
    protected ServerSocket serverSocket = null;
    protected Managers managers;
    protected ServerSetup setup;
    private final List<ProtocolHandler> handlers = Collections.synchronizedList(new ArrayList<ProtocolHandler>());
    private volatile boolean keepRunning = false;
    private volatile boolean running = false;
    private final Object startupMonitor = new Object();

    protected AbstractServer(ServerSetup setup, Managers managers) {
        this.setup = setup;
        try {
            bindTo = (setup.getBindAddress() == null)
                    ? InetAddress.getByName(setup.getDefaultBindAddress())
                    : InetAddress.getByName(setup.getBindAddress());
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
        this.managers = managers;
    }

    /**
     * Create a new, specific protocol handler such as for IMAP.
     *
     * @param clientSocket the client socket to use.
     * @return the new protocol handler.
     */
    protected abstract ProtocolHandler createProtocolHandler(Socket clientSocket);

    protected ServerSocket openServerSocket() throws IOException {
        if (setup.isSecure()) {
            return DummySSLServerSocketFactory.getDefault().createServerSocket(setup.getPort(), 0, bindTo);
        } else {
            return new ServerSocket(setup.getPort(), 0, bindTo);
        }
    }

    @Override
    public void run() {
        initServerSocket();

        // Notify everybody that we're ready to accept connections
        synchronized (startupMonitor) {
            startupMonitor.notifyAll();
        }

        if (log.isDebugEnabled()) {
            log.debug("Started " + toString());
        }

        // Handle connections
        while (keepOn()) {
            try {
                Socket clientSocket = serverSocket.accept();
                if (!keepOn()) {
                    clientSocket.close();
                } else {
                    handleClientSocket(clientSocket);
                }
            } catch (IOException ignored) {
                //ignored
                if (log.isTraceEnabled()) {
                    log.trace("Error while processing client socket for " + toString(), ignored);
                }
            }
        }
    }

    protected synchronized void initServerSocket() {
        try {
            serverSocket = openServerSocket();
            serverSocket.setReuseAddress(true); // Try to fix TIME_WAIT on Linux when quickly starting/stopping server
        } catch (IOException e) {
            final String msg = "Can not open server socket for " + toString();
            log.error(msg, e);
            throw new IllegalStateException(msg, e);
        }
        setRunning(true);
    }

    protected void handleClientSocket(Socket clientSocket) {
        final ProtocolHandler handler = createProtocolHandler(clientSocket);
        addHandler(handler);
        final Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    handler.run(); // NOSONAR
                } finally {
                    // Make sure to de-register, see https://github.com/greenmail-mail-test/greenmail/issues/18
                    removeHandler(handler);
                }
            }
        });
        thread.start();
    }

    /**
     * Adds a protocol handler, for eg. shutting down.
     *
     * @param handler the handler.
     */
    private void addHandler(ProtocolHandler handler) {
        handlers.add(handler);
    }

    /**
     * Removes protocol handler, eg.  when shutting down.
     *
     * @param handler the handler.
     */
    private void removeHandler(ProtocolHandler handler) {
        handlers.remove(handler);
    }

    protected synchronized void quit() {
        if (log.isDebugEnabled()) {
            log.debug("Stopping " + toString());
        }
        try {
            // Close server socket, we do not accept new requests anymore.
            // This also terminates the server thread if blocking on socket.accept.
            if (null != serverSocket) {
                serverSocket.close();
                serverSocket = null;
            }

            // Close all handlers. Handler threads terminate if run loop exits
            synchronized (handlers) {
                for (ProtocolHandler handler : handlers) {
                    handler.close();
                }
                handlers.clear();
            }
            if (log.isDebugEnabled()) {
                log.debug("Stopped " + toString());
            }
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    public String getBindTo() {
        return bindTo.getHostAddress();
    }

    public int getPort() {
        return serverSocket.getLocalPort();
    }

    public String getProtocol() {
        return setup.getProtocol();
    }

    public ServerSetup getServerSetup() {
        return setup;
    }

    @Override
    public String toString() {
        return null != setup ? setup.getProtocol() + ':' + setup.getPort() : super.toString();
    }

    @Override
    public boolean waitTillRunning(long timeoutInMs) throws InterruptedException {
        long t = System.currentTimeMillis();
        synchronized (startupMonitor) {
            // Loop to avoid spurious wakeups, see
            // https://www.securecoding.cert.org/confluence/display/java/THI03-J.+Always+invoke+wait%28%29+and+await%28%29+methods+inside+a+loop
            while (!isRunning() && System.currentTimeMillis() - t < timeoutInMs) {
                startupMonitor.wait(timeoutInMs);
            }
        }
<commit_msg>ssue 69: setReuseAddress for server socket to avoid TIME_WAIT<commit_after>/*
 * Copyright (c) 2014 <NAME> / Icegreen Technologies. All Rights Reserved.
 * This software is released under the Apache license 2.0
 */
package com.icegreen.greenmail.server;

import com.icegreen.greenmail.Managers;
import com.icegreen.greenmail.util.DummySSLServerSocketFactory;
import com.icegreen.greenmail.util.ServerSetup;
import com.icegreen.greenmail.util.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Store;
import java.io.IOException;
import java.net.*;
import java.util.*;

/**
 * @author <NAME>
 * @version $Id: $
 * @since Feb 2, 2006
 */
public abstract class AbstractServer extends Thread implements Service {
    protected final Logger log = LoggerFactory.getLogger(getClass());
    protected final InetAddress bindTo;
    protected ServerSocket serverSocket = null;
    protected Managers managers;
    protected ServerSetup setup;
    private final List<ProtocolHandler> handlers = Collections.synchronizedList(new ArrayList<ProtocolHandler>());
    private volatile boolean keepRunning = false;
    private volatile boolean running = false;
    private final Object startupMonitor = new Object();

    protected AbstractServer(ServerSetup setup, Managers managers) {
        this.setup = setup;
        try {
            bindTo = (setup.getBindAddress() == null)
                    ? InetAddress.getByName(setup.getDefaultBindAddress())
                    : InetAddress.getByName(setup.getBindAddress());
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
        setName(setup.getProtocol() + ':' + setup.getPort());
        this.managers = managers;
    }

    /**
     * Create a new, specific protocol handler such as for IMAP.
     *
     * @param clientSocket the client socket to use.
     * @return the new protocol handler.
     */
    protected abstract ProtocolHandler createProtocolHandler(Socket clientSocket);

    protected ServerSocket openServerSocket() throws IOException {
        final ServerSocket socket;
        if (setup.isSecure()) {
            socket = DummySSLServerSocketFactory.getDefault().createServerSocket();
        } else {
            socket = new ServerSocket();
        }
        socket.setReuseAddress(true); // Try to fix TIME_WAIT on Linux when quickly starting/stopping server
        try {
            socket.bind(new InetSocketAddress(bindTo, setup.getPort()));
        } catch (IOException ex) {
            try {
                socket.close(); // Do close if bind failed!
            } catch (IOException nested) {
                if(log.isTraceEnabled()) {
                    log.trace("Ignoring attempt to close connection", nested);
                }
            }
            throw ex;
        }
        return socket;
    }

    @Override
    public void run() {
        initServerSocket();

        // Notify everybody that we're ready to accept connections
        synchronized (startupMonitor) {
            startupMonitor.notifyAll();
        }

        if (log.isDebugEnabled()) {
            log.debug("Started " + getName());
        }

        // Handle connections
        while (keepOn()) {
            try {
                Socket clientSocket = serverSocket.accept();
                if (!keepOn()) {
                    clientSocket.close();
                } else {
                    handleClientSocket(clientSocket);
                }
            } catch (IOException ignored) {
                //ignored
                if (log.isTraceEnabled()) {
                    log.trace("Error while processing client socket for " + getName(), ignored);
                }
            }
        }
    }

    protected synchronized void initServerSocket() {
        try {
            serverSocket = openServerSocket();
        } catch (IOException e) {
            final String msg = "Can not open server socket for " + getName();
            log.error(msg, e);
            throw new IllegalStateException(msg, e);
        }
        setRunning(true);
    }

    protected void handleClientSocket(Socket clientSocket) {
        final ProtocolHandler handler = createProtocolHandler(clientSocket);
        addHandler(handler);
        String threadName = getName() + "<-" + clientSocket.getInetAddress() + ":" + clientSocket.getPort();
        final Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    handler.run(); // NOSONAR
                } finally {
                    // Make sure to de-register, see https://github.com/greenmail-mail-test/greenmail/issues/18
                    removeHandler(handler);
                }
            }
        });
        thread.setName(threadName);
        thread.start();
    }

    /**
     * Adds a protocol handler, for eg. shutting down.
     *
     * @param handler the handler.
     */
    private void addHandler(ProtocolHandler handler) {
        handlers.add(handler);
    }

    /**
     * Removes protocol handler, eg.  when shutting down.
     *
     * @param handler the handler.
     */
    private void removeHandler(ProtocolHandler handler) {
        handlers.remove(handler);
    }

    protected synchronized void quit() {
        if (log.isDebugEnabled()) {
            log.debug("Stopping " + getName());
        }
        try {
            // Close server socket, we do not accept new requests anymore.
            // This also terminates the server thread if blocking on socket.accept.
            if (null != serverSocket) {
                serverSocket.close();
                serverSocket = null;
            }

            // Close all handlers. Handler threads terminate if run loop exits
            synchronized (handlers) {
                for (ProtocolHandler handler : handlers) {
                    handler.close();
                }
                handlers.clear();
            }
            if (log.isDebugEnabled()) {
                log.debug("Stopped " + getName());
            }
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    public String getBindTo() {
        return bindTo.getHostAddress();
    }

    public int getPort() {
        return serverSocket.getLocalPort();
    }

    public String getProtocol() {
        return setup.getProtocol();
    }

    public ServerSetup getServerSetup() {
        return setup;
    }

    @Override
    public String toString() {
        return getName();
    }

    @Override
    public boolean waitTillRunning(long timeoutInMs) throws InterruptedException {
        long t = System.currentTimeMillis();
        synchronized (startupMonitor) {
            // Loop to avoid spurious wakeups, see
            // https://www.securecoding.cert.org/confluence/display/java/THI03-J.+Always+invoke+wait%28%29+and+await%28%29+methods+inside+a+loop
            while (!isRunning() && System.currentTimeMillis() - t < timeoutInMs) {
                startupMonitor.wait(timeoutInMs);
            }
        }

{'fraction_non_alphanumeric': 0.09655072882089767, 'fraction_numerical': 0.003896666185596767, 'mean_word_length': 2.006942937730527, 'pattern_counts': {'":': 1, '<': 14, '<?xml version=': 0, '>': 11, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2564513', 'n_tokens_mistral': 3545, 'n_tokens_neox': 3414, 'n_words': 913}