1 // 2 // System.Runtime.Remoting.Channels.Tcp.TcpConnectionPool.cs 3 // 4 // Author: Lluis Sanchez Gual (lluis@ideary.com) 5 // 6 // 2002 (C) Lluis Sanchez Gual 7 // 8 9 // 10 // Permission is hereby granted, free of charge, to any person obtaining 11 // a copy of this software and associated documentation files (the 12 // "Software"), to deal in the Software without restriction, including 13 // without limitation the rights to use, copy, modify, merge, publish, 14 // distribute, sublicense, and/or sell copies of the Software, and to 15 // permit persons to whom the Software is furnished to do so, subject to 16 // the following conditions: 17 // 18 // The above copyright notice and this permission notice shall be 19 // included in all copies or substantial portions of the Software. 20 // 21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 // 29 30 using System; 31 using System.Collections; 32 using System.Threading; 33 using System.IO; 34 using System.Net.Sockets; 35 36 namespace System.Runtime.Remoting.Channels.Tcp 37 { 38 // This is a pool of Tcp connections. Connections requested 39 // by the TCP channel are pooled after their use, and can 40 // be reused later. Connections are automaticaly closed 41 // if not used after some time, specified in KeepAliveSeconds. 42 // The number of allowed open connections can also be specified 43 // in MaxOpenConnections. The limit is per host. 44 // If a thread requests a connection and the limit has been 45 // reached, the thread is suspended until one is released. 46 47 internal class TcpConnectionPool 48 { 49 // Table of pools. There is a HostConnectionPool 50 // instance for each host 51 static Hashtable _pools = new Hashtable(); 52 53 static int _maxOpenConnections = int.MaxValue; 54 static int _keepAliveSeconds = 15; 55 56 static Thread _poolThread; 57 TcpConnectionPool()58 static TcpConnectionPool() 59 { 60 // This thread will close unused connections 61 _poolThread = new Thread (new ThreadStart (ConnectionCollector)); 62 _poolThread.IsBackground = true; 63 _poolThread.Start(); 64 } 65 Shutdown()66 public static void Shutdown () 67 { 68 if (_poolThread != null) 69 _poolThread.Abort(); 70 } 71 72 public static int MaxOpenConnections 73 { 74 get { return _maxOpenConnections; } 75 set 76 { 77 if (value < 1) throw new RemotingException ("MaxOpenConnections must be greater than zero"); 78 _maxOpenConnections = value; 79 } 80 } 81 82 public static int KeepAliveSeconds 83 { 84 get { return _keepAliveSeconds; } 85 set { _keepAliveSeconds = value; } 86 } 87 GetConnection(string host, int port)88 public static TcpConnection GetConnection (string host, int port) 89 { 90 HostConnectionPool hostPool; 91 92 lock (_pools) 93 { 94 string key = host + ":" + port; 95 hostPool = (HostConnectionPool) _pools[key]; 96 if (hostPool == null) 97 { 98 hostPool = new HostConnectionPool(host, port); 99 _pools[key] = hostPool; 100 } 101 } 102 103 return hostPool.GetConnection(); 104 } 105 ConnectionCollector()106 private static void ConnectionCollector () 107 { 108 while (true) 109 { 110 Thread.Sleep(3000); 111 lock (_pools) 112 { 113 ICollection values = _pools.Values; 114 foreach (HostConnectionPool pool in values) 115 pool.PurgeConnections(); 116 } 117 } 118 } 119 } 120 121 internal class ReusableTcpClient : TcpClient 122 { ReusableTcpClient(string host, int port)123 public ReusableTcpClient (string host, int port): base (host, port) 124 { 125 // Avoid excessive waiting for data by the tcp stack in linux. 126 // We can't safely use SetSocketOption for both runtimes because 127 // it would break 2.0 TcpClient's property cache. 128 Client.NoDelay = true; 129 } 130 131 public bool IsAlive 132 { 133 get 134 { 135 // This Poll will return true if there is data pending to 136 // be read. It prob. means that a client object using this 137 // connection got an exception and did not finish to read 138 // the data. It can also mean that the connection has been 139 // closed in the server. In both cases, the connection cannot 140 // be reused. 141 return !Client.Poll (0, SelectMode.SelectRead); 142 } 143 } 144 } 145 146 internal class TcpConnection 147 { 148 DateTime _controlTime; 149 Stream _stream; 150 ReusableTcpClient _client; 151 HostConnectionPool _pool; 152 byte[] _buffer; 153 TcpConnection(HostConnectionPool pool, ReusableTcpClient client)154 public TcpConnection (HostConnectionPool pool, ReusableTcpClient client) 155 { 156 _pool = pool; 157 _client = client; 158 _stream = new BufferedStream (client.GetStream()); 159 _controlTime = DateTime.Now; 160 _buffer = new byte[TcpMessageIO.DefaultStreamBufferSize]; 161 } 162 163 public Stream Stream 164 { 165 get { return _stream; } 166 } 167 168 public DateTime ControlTime 169 { 170 get { return _controlTime; } 171 set { _controlTime = value; } 172 } 173 174 public bool IsAlive 175 { 176 get { return _client.IsAlive; } 177 } 178 179 // This is a "thread safe" buffer that can be used by 180 // TcpClientTransportSink to read or send data to the stream. 181 // The buffer is "thread safe" since only one thread can 182 // use a connection at a given time. 183 public byte[] Buffer 184 { 185 get { return _buffer; } 186 } 187 188 // Returns the connection to the pool Release()189 public void Release() 190 { 191 _pool.ReleaseConnection (this); 192 } 193 Close()194 public void Close() 195 { 196 _client.Close(); 197 } 198 } 199 200 internal class HostConnectionPool 201 { 202 ArrayList _pool = new ArrayList(); 203 int _activeConnections = 0; 204 205 string _host; 206 int _port; 207 HostConnectionPool(string host, int port)208 public HostConnectionPool (string host, int port) 209 { 210 _host = host; 211 _port = port; 212 } 213 GetConnection()214 public TcpConnection GetConnection () 215 { 216 TcpConnection connection = null; 217 lock (_pool) 218 { 219 do 220 { 221 if (_pool.Count > 0) 222 { 223 // There are available connections 224 225 connection = (TcpConnection)_pool[_pool.Count - 1]; 226 _pool.RemoveAt(_pool.Count - 1); 227 if (!connection.IsAlive) { 228 CancelConnection (connection); 229 connection = null; 230 continue; 231 } 232 } 233 234 if (connection == null && _activeConnections < TcpConnectionPool.MaxOpenConnections) 235 { 236 // No connections available, but the max connections 237 // has not been reached yet, so a new one can be created 238 // Create the connection outside the lock 239 break; 240 } 241 242 // No available connections in the pool 243 // Wait for somewone to release one. 244 245 if (connection == null) 246 { 247 Monitor.Wait(_pool); 248 } 249 } 250 while (connection == null); 251 } 252 253 if (connection == null) 254 return CreateConnection (); 255 else 256 return connection; 257 } 258 CreateConnection()259 private TcpConnection CreateConnection() 260 { 261 try 262 { 263 ReusableTcpClient client = new ReusableTcpClient(_host, _port); 264 TcpConnection entry = new TcpConnection(this, client); 265 _activeConnections++; 266 return entry; 267 } 268 catch (Exception ex) 269 { 270 throw new RemotingException (ex.Message); 271 } 272 } 273 ReleaseConnection(TcpConnection entry)274 public void ReleaseConnection (TcpConnection entry) 275 { 276 lock (_pool) 277 { 278 entry.ControlTime = DateTime.Now; // Initialize timeout 279 _pool.Add (entry); 280 Monitor.Pulse (_pool); 281 } 282 } 283 CancelConnection(TcpConnection entry)284 private void CancelConnection(TcpConnection entry) 285 { 286 try 287 { 288 entry.Stream.Close(); 289 _activeConnections--; 290 } 291 catch 292 { 293 } 294 } 295 PurgeConnections()296 public void PurgeConnections() 297 { 298 lock (_pool) 299 { 300 for (int n=0; n < _pool.Count; n++) 301 { 302 TcpConnection entry = (TcpConnection)_pool[n]; 303 if ( (DateTime.Now - entry.ControlTime).TotalSeconds > TcpConnectionPool.KeepAliveSeconds) 304 { 305 CancelConnection (entry); 306 _pool.RemoveAt(n); 307 n--; 308 } 309 } 310 } 311 } 312 313 } 314 315 316 } 317