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