/*
 * Copyright (C) 2004-2009 Jive Software. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.openfire.net;

/**
 * A SocketSendingTracker keeps track of all the sockets that are currently sending data and
 * checks the health of the sockets to detect hanged connections. If a sending operation takes
 * too much time (i.e. exceeds a time limit) then it is assumed that the connection has been
 * lost and for some reason the JVM has not been notified of the dead connection. Once a dead
 * connection has been detected it will be closed so that the thread that was writing to the
 * socket can resume. Resuming locked threads is important since otherwise a complete system halt
 * may occur.<p>
 *
 * The time limit to wait before considering a connection dead can be configured changing the
 * property <b>xmpp.session.sending-limit</b>. If the property was not defined then a default
 * time limit of 60 seconds will be assumed. This means that by default if a sending operation
 * takes longer than 60 seconds then the connection will be closed and the client disconnected.
 * Therefore, it is important to not set a very low time limit since active clients may be
 * incorrectly considered as dead clients.
 *
 * @author Gaston Dombiak
 */
public class SocketSendingTracker {


    private static SocketSendingTracker instance = new SocketSendingTracker();

    /**
     * Flag that indicates if the tracket should shutdown the tracking process.
     */
    private boolean shutdown = false;

    /**
     * Thread used for checking periodically the health of the sockets involved in sending
     * operations.
     */
    private Thread checkingThread;

    /**
     * Returns the unique instance of this class.
     *
     * @return the unique instance of this class.
     */
    public static SocketSendingTracker getInstance() {
        return instance;
    }

    /**
     * Hide the constructor so that only one instance of this class can exist.
     */
    private SocketSendingTracker() {
    }

    /**
     * Start up the daemon thread that will check for the health of the sockets that are
     * currently sending data.
     */
    public void start() {
        shutdown = false;
        checkingThread = new Thread("SocketSendingTracker") {
            @Override
            public void run() {
                while (!shutdown) {
                    checkHealth();
                    synchronized (this) {
                        try {
                            wait(10000);
                        }
                        catch (InterruptedException e) {
                        }
                    }
                }
            }
        };
        checkingThread.setDaemon(true);
        checkingThread.start();
    }

    /**
     * Indicates that the checking thread should be stoped. The thread will be waked up
     * so that it can be stoped.
     */
    public void shutdown() {
        shutdown = true;
        // Use a wait/notify algorithm to ensure that the thread stops immediately if it
        // was waiting
        synchronized (checkingThread) {
            checkingThread.notify();
        }
    }

    /**
     * Checks if a socket has been trying to send data for a given amount of time. If it has
     * exceded a limit of time then the socket will be closed.<p>
     *
     * It is expected that sending operations will not take too much time so the checking will
     * be very fast since very few sockets will be present in the Map and most or all of them
     * will not exceed the time limit. Therefore, it is expected the overhead of this class to be
     * quite small.
     */
    private void checkHealth() {
        for (SocketConnection connection : SocketConnection.getInstances()) {
            connection.checkHealth();
        }
    }
}
