2 // System.Runtime.Remoting.Channels.Tcp.TcpConnectionPool.cs
\r
4 // Author: Lluis Sanchez Gual (lluis@ideary.com)
\r
6 // 2002 (C) Lluis Sanchez Gual
\r
10 // Permission is hereby granted, free of charge, to any person obtaining
\r
11 // a copy of this software and associated documentation files (the
\r
12 // "Software"), to deal in the Software without restriction, including
\r
13 // without limitation the rights to use, copy, modify, merge, publish,
\r
14 // distribute, sublicense, and/or sell copies of the Software, and to
\r
15 // permit persons to whom the Software is furnished to do so, subject to
\r
16 // the following conditions:
\r
18 // The above copyright notice and this permission notice shall be
\r
19 // included in all copies or substantial portions of the Software.
\r
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
\r
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
\r
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
\r
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
\r
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
\r
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
\r
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\r
31 using System.Collections;
\r
32 using System.Threading;
\r
34 using System.Net.Sockets;
\r
36 namespace System.Runtime.Remoting.Channels.Tcp
\r
38 // This is a pool of Tcp connections. Connections requested
\r
39 // by the TCP channel are pooled after their use, and can
\r
40 // be reused later. Connections are automaticaly closed
\r
41 // if not used after some time, specified in KeepAliveSeconds.
\r
42 // The number of allowed open connections can also be specified
\r
43 // in MaxOpenConnections. The limit is per host.
\r
44 // If a thread requests a connection and the limit has been
\r
45 // reached, the thread is suspended until one is released.
\r
47 internal class TcpConnectionPool
\r
49 // Table of pools. There is a HostConnectionPool
\r
50 // instance for each host
\r
51 static Hashtable _pools = new Hashtable();
\r
53 static int _maxOpenConnections = int.MaxValue;
\r
54 static int _keepAliveSeconds = 15;
\r
56 static Thread _poolThread;
\r
58 static TcpConnectionPool()
\r
60 // This thread will close unused connections
\r
61 _poolThread = new Thread (new ThreadStart (ConnectionCollector));
\r
62 _poolThread.IsBackground = true;
\r
63 _poolThread.Start();
\r
66 public static void Shutdown ()
\r
68 if (_poolThread != null)
\r
69 _poolThread.Abort();
\r
72 public static int MaxOpenConnections
\r
74 get { return _maxOpenConnections; }
\r
77 if (value < 1) throw new RemotingException ("MaxOpenConnections must be greater than zero");
\r
78 _maxOpenConnections = value;
\r
82 public static int KeepAliveSeconds
\r
84 get { return _keepAliveSeconds; }
\r
85 set { _keepAliveSeconds = value; }
\r
88 public static TcpConnection GetConnection (string host, int port)
\r
90 HostConnectionPool hostPool;
\r
94 string key = host + ":" + port;
\r
95 hostPool = (HostConnectionPool) _pools[key];
\r
96 if (hostPool == null)
\r
98 hostPool = new HostConnectionPool(host, port);
\r
99 _pools[key] = hostPool;
\r
103 return hostPool.GetConnection();
\r
106 private static void ConnectionCollector ()
\r
110 Thread.Sleep(3000);
\r
113 ICollection values = _pools.Values;
\r
114 foreach (HostConnectionPool pool in values)
\r
115 pool.PurgeConnections();
\r
121 internal class ReusableTcpClient : TcpClient
\r
123 public ReusableTcpClient (string host, int port): base (host, port)
\r
125 // Avoid excessive waiting for data by the tcp stack in linux.
\r
126 // We can't safely use SetSocketOption for both runtimes because
\r
127 // it would break 2.0 TcpClient's property cache.
\r
128 Client.NoDelay = true;
\r
131 public bool IsAlive
\r
135 // This Poll will return true if there is data pending to
\r
136 // be read. It prob. means that a client object using this
\r
137 // connection got an exception and did not finish to read
\r
138 // the data. It can also mean that the connection has been
\r
139 // closed in the server. In both cases, the connection cannot
\r
141 return !Client.Poll (0, SelectMode.SelectRead);
\r
146 internal class TcpConnection
\r
148 DateTime _controlTime;
\r
150 ReusableTcpClient _client;
\r
151 HostConnectionPool _pool;
\r
154 public TcpConnection (HostConnectionPool pool, ReusableTcpClient client)
\r
158 _stream = new BufferedStream (client.GetStream());
\r
159 _controlTime = DateTime.Now;
\r
160 _buffer = new byte[TcpMessageIO.DefaultStreamBufferSize];
\r
163 public Stream Stream
\r
165 get { return _stream; }
\r
168 public DateTime ControlTime
\r
170 get { return _controlTime; }
\r
171 set { _controlTime = value; }
\r
174 public bool IsAlive
\r
176 get { return _client.IsAlive; }
\r
179 // This is a "thread safe" buffer that can be used by
\r
180 // TcpClientTransportSink to read or send data to the stream.
\r
181 // The buffer is "thread safe" since only one thread can
\r
182 // use a connection at a given time.
\r
183 public byte[] Buffer
\r
185 get { return _buffer; }
\r
188 // Returns the connection to the pool
\r
189 public void Release()
\r
191 _pool.ReleaseConnection (this);
\r
194 public void Close()
\r
200 internal class HostConnectionPool
\r
202 ArrayList _pool = new ArrayList();
\r
203 int _activeConnections = 0;
\r
208 public HostConnectionPool (string host, int port)
\r
214 public TcpConnection GetConnection ()
\r
216 TcpConnection connection = null;
\r
221 if (_pool.Count > 0)
\r
223 // There are available connections
\r
225 connection = (TcpConnection)_pool[_pool.Count - 1];
\r
226 _pool.RemoveAt(_pool.Count - 1);
\r
227 if (!connection.IsAlive) {
\r
228 CancelConnection (connection);
\r
234 if (connection == null && _activeConnections < TcpConnectionPool.MaxOpenConnections)
\r
236 // No connections available, but the max connections
\r
237 // has not been reached yet, so a new one can be created
\r
238 // Create the connection outside the lock
\r
242 // No available connections in the pool
\r
243 // Wait for somewone to release one.
\r
245 if (connection == null)
\r
247 Monitor.Wait(_pool);
\r
250 while (connection == null);
\r
253 if (connection == null)
\r
254 return CreateConnection ();
\r
259 private TcpConnection CreateConnection()
\r
263 ReusableTcpClient client = new ReusableTcpClient(_host, _port);
\r
264 TcpConnection entry = new TcpConnection(this, client);
\r
265 _activeConnections++;
\r
268 catch (Exception ex)
\r
270 throw new RemotingException (ex.Message);
\r
274 public void ReleaseConnection (TcpConnection entry)
\r
278 entry.ControlTime = DateTime.Now; // Initialize timeout
\r
280 Monitor.Pulse (_pool);
\r
284 private void CancelConnection(TcpConnection entry)
\r
288 entry.Stream.Close();
\r
289 _activeConnections--;
\r
296 public void PurgeConnections()
\r
300 for (int n=0; n < _pool.Count; n++)
\r
302 TcpConnection entry = (TcpConnection)_pool[n];
\r
303 if ( (DateTime.Now - entry.ControlTime).TotalSeconds > TcpConnectionPool.KeepAliveSeconds)
\r
305 CancelConnection (entry);
\r