org/sonews/daemon/Connections.java
author cli
Fri Dec 25 15:42:46 2009 +0100 (2009-12-25)
changeset 25 dd05c3f2fa24
parent 15 f2293e8566f5
child 28 15d14b110240
permissions -rw-r--r--
Fix for too early disconnects on slow client connections. (#563)
chris@1
     1
/*
chris@1
     2
 *   SONEWS News Server
chris@1
     3
 *   see AUTHORS for the list of contributors
chris@1
     4
 *
chris@1
     5
 *   This program is free software: you can redistribute it and/or modify
chris@1
     6
 *   it under the terms of the GNU General Public License as published by
chris@1
     7
 *   the Free Software Foundation, either version 3 of the License, or
chris@1
     8
 *   (at your option) any later version.
chris@1
     9
 *
chris@1
    10
 *   This program is distributed in the hope that it will be useful,
chris@1
    11
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
chris@1
    12
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
chris@1
    13
 *   GNU General Public License for more details.
chris@1
    14
 *
chris@1
    15
 *   You should have received a copy of the GNU General Public License
chris@1
    16
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
chris@1
    17
 */
chris@1
    18
chris@1
    19
package org.sonews.daemon;
chris@1
    20
chris@3
    21
import org.sonews.config.Config;
chris@1
    22
import org.sonews.util.Log;
chris@1
    23
import java.io.IOException;
chris@1
    24
import java.net.InetSocketAddress;
chris@1
    25
import java.net.Socket;
chris@1
    26
import java.nio.channels.SocketChannel;
chris@1
    27
import java.util.ArrayList;
chris@1
    28
import java.util.HashMap;
chris@1
    29
import java.util.List;
chris@1
    30
import java.util.ListIterator;
chris@1
    31
import java.util.Map;
chris@1
    32
import org.sonews.util.Stats;
chris@1
    33
chris@1
    34
/**
chris@1
    35
 * Daemon thread collecting all NNTPConnection instances. The thread
chris@1
    36
 * checks periodically if there are stale/timed out connections and
chris@1
    37
 * removes and purges them properly.
chris@1
    38
 * @author Christian Lins
chris@1
    39
 * @since sonews/0.5.0
chris@1
    40
 */
chris@3
    41
public final class Connections extends AbstractDaemon
chris@1
    42
{
chris@1
    43
chris@1
    44
  private static final Connections instance = new Connections();
chris@1
    45
  
chris@1
    46
  /**
chris@1
    47
   * @return Active Connections instance.
chris@1
    48
   */
chris@1
    49
  public static Connections getInstance()
chris@1
    50
  {
chris@1
    51
    return Connections.instance;
chris@1
    52
  }
chris@1
    53
  
chris@1
    54
  private final List<NNTPConnection> connections 
chris@1
    55
    = new ArrayList<NNTPConnection>();
chris@1
    56
  private final Map<SocketChannel, NNTPConnection> connByChannel 
chris@1
    57
    = new HashMap<SocketChannel, NNTPConnection>();
chris@1
    58
  
chris@1
    59
  private Connections()
chris@1
    60
  {
chris@1
    61
    setName("Connections");
chris@1
    62
  }
chris@1
    63
  
chris@1
    64
  /**
chris@1
    65
   * Adds the given NNTPConnection to the Connections management.
chris@1
    66
   * @param conn
chris@1
    67
   * @see org.sonews.daemon.NNTPConnection
chris@1
    68
   */
chris@1
    69
  public void add(final NNTPConnection conn)
chris@1
    70
  {
chris@1
    71
    synchronized(this.connections)
chris@1
    72
    {
chris@1
    73
      this.connections.add(conn);
chris@3
    74
      this.connByChannel.put(conn.getSocketChannel(), conn);
chris@1
    75
    }
chris@1
    76
  }
chris@1
    77
  
chris@1
    78
  /**
chris@1
    79
   * @param channel
chris@1
    80
   * @return NNTPConnection instance that is associated with the given
chris@1
    81
   * SocketChannel.
chris@1
    82
   */
chris@1
    83
  public NNTPConnection get(final SocketChannel channel)
chris@1
    84
  {
chris@1
    85
    synchronized(this.connections)
chris@1
    86
    {
chris@1
    87
      return this.connByChannel.get(channel);
chris@1
    88
    }
chris@1
    89
  }
chris@1
    90
chris@1
    91
  int getConnectionCount(String remote)
chris@1
    92
  {
chris@1
    93
    int cnt = 0;
chris@1
    94
    synchronized(this.connections)
chris@1
    95
    {
chris@1
    96
      for(NNTPConnection conn : this.connections)
chris@1
    97
      {
chris@1
    98
        assert conn != null;
chris@3
    99
        assert conn.getSocketChannel() != null;
chris@1
   100
chris@3
   101
        Socket socket = conn.getSocketChannel().socket();
chris@1
   102
        if(socket != null)
chris@1
   103
        {
chris@1
   104
          InetSocketAddress sockAddr = (InetSocketAddress)socket.getRemoteSocketAddress();
chris@1
   105
          if(sockAddr != null)
chris@1
   106
          {
chris@1
   107
            if(sockAddr.getHostName().equals(remote))
chris@1
   108
            {
chris@1
   109
              cnt++;
chris@1
   110
            }
chris@1
   111
          }
chris@1
   112
        } // if(socket != null)
chris@1
   113
      }
chris@1
   114
    }
chris@1
   115
    return cnt;
chris@1
   116
  }
chris@1
   117
  
chris@1
   118
  /**
chris@1
   119
   * Run loops. Checks periodically for timed out connections and purged them
chris@1
   120
   * from the lists.
chris@1
   121
   */
chris@1
   122
  @Override
chris@1
   123
  public void run()
chris@1
   124
  {
chris@1
   125
    while(isRunning())
chris@1
   126
    {
chris@3
   127
      int timeoutMillis = 1000 * Config.inst().get(Config.TIMEOUT, 180);
chris@1
   128
      
chris@1
   129
      synchronized (this.connections)
chris@1
   130
      {
chris@1
   131
        final ListIterator<NNTPConnection> iter = this.connections.listIterator();
chris@1
   132
        NNTPConnection conn;
chris@1
   133
chris@1
   134
        while (iter.hasNext())
chris@1
   135
        {
chris@1
   136
          conn = iter.next();
cli@25
   137
          if((System.currentTimeMillis() - conn.getLastActivity()) > timeoutMillis
cli@25
   138
              && conn.getBuffers().isOutputBufferEmpty())
chris@1
   139
          {
chris@1
   140
            // A connection timeout has occurred so purge the connection
chris@1
   141
            iter.remove();
chris@1
   142
chris@1
   143
            // Close and remove the channel
chris@3
   144
            SocketChannel channel = conn.getSocketChannel();
chris@1
   145
            connByChannel.remove(channel);
chris@1
   146
            
chris@1
   147
            try
chris@1
   148
            {
chris@1
   149
              // Close the channel; implicitely cancels all selectionkeys
chris@1
   150
              channel.close();
cli@15
   151
              Log.get().info("Disconnected: " + channel.socket().getRemoteSocketAddress() +
cli@15
   152
                " (timeout)");
chris@1
   153
            }
chris@1
   154
            catch(IOException ex)
chris@1
   155
            {
cli@15
   156
              Log.get().warning("Connections.run(): " + ex);
chris@1
   157
            }
chris@1
   158
chris@1
   159
            // Recycle the used buffers
chris@1
   160
            conn.getBuffers().recycleBuffers();
chris@1
   161
            
chris@1
   162
            Stats.getInstance().clientDisconnect();
chris@1
   163
          }
chris@1
   164
        }
chris@1
   165
      }
chris@1
   166
chris@1
   167
      try
chris@1
   168
      {
chris@1
   169
        Thread.sleep(10000); // Sleep ten seconds
chris@1
   170
      }
chris@1
   171
      catch(InterruptedException ex)
chris@1
   172
      {
cli@15
   173
        Log.get().warning("Connections Thread was interrupted: " + ex.getMessage());
chris@1
   174
      }
chris@1
   175
    }
chris@1
   176
  }
chris@1
   177
  
chris@1
   178
}