1 //
2 // Mono.Remoting.Channels.Unix.UnixConnectionPool.cs
3 //
4 // Author: Lluis Sanchez Gual (lluis@ideary.com)
5 //
6 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
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 using System.Runtime.Remoting;
36 using Mono.Unix;
37 
38 namespace Mono.Remoting.Channels.Unix
39 {
40 	// This is a pool of Unix connections. Connections requested
41 	// by the TCP channel are pooled after their use, and can
42 	// be reused later. Connections are automaticaly closed
43 	// if not used after some time, specified in KeepAliveSeconds.
44 	// The number of allowed open connections can also be specified
45 	// in MaxOpenConnections. The limit is per host.
46 	// If a thread requests a connection and the limit has been
47 	// reached, the thread is suspended until one is released.
48 
49 	internal class UnixConnectionPool
50 	{
51 		// Table of pools. There is a HostConnectionPool
52 		// instance for each host
53 		static Hashtable _pools = new Hashtable();
54 
55 		static int _maxOpenConnections = int.MaxValue;
56 		static int _keepAliveSeconds = 15;
57 
58 		static Thread _poolThread;
59 
UnixConnectionPool()60 		static UnixConnectionPool()
61 		{
62 			// This thread will close unused connections
63 			_poolThread = new Thread (new ThreadStart (ConnectionCollector));
64 			_poolThread.Start();
65 			_poolThread.IsBackground = true;
66 		}
67 
Shutdown()68 		public static void Shutdown ()
69 		{
70 			if (_poolThread != null)
71 				_poolThread.Abort();
72 		}
73 
74 		public static int MaxOpenConnections
75 		{
76 			get { return _maxOpenConnections; }
77 			set
78 			{
79 				if (value < 1) throw new RemotingException ("MaxOpenConnections must be greater than zero");
80 				_maxOpenConnections = value;
81 			}
82 		}
83 
84 		public static int KeepAliveSeconds
85 		{
86 			get { return _keepAliveSeconds; }
87 			set { _keepAliveSeconds = value; }
88 		}
89 
GetConnection(string path)90 		public static UnixConnection GetConnection (string path)
91 		{
92 			HostConnectionPool hostPool;
93 
94 			lock (_pools)
95 			{
96 				hostPool = (HostConnectionPool) _pools[path];
97 				if (hostPool == null)
98 				{
99 					hostPool = new HostConnectionPool(path);
100 					_pools[path] = hostPool;
101 				}
102 			}
103 
104 			return hostPool.GetConnection();
105 		}
106 
ConnectionCollector()107 		private static void ConnectionCollector ()
108 		{
109 			while (true)
110 			{
111 				Thread.Sleep(3000);
112 				lock (_pools)
113 				{
114 					ICollection values = _pools.Values;
115 					foreach (HostConnectionPool pool in values)
116 						pool.PurgeConnections();
117 				}
118 			}
119 		}
120 	}
121 
122 	internal class ReusableUnixClient : UnixClient
123 	{
ReusableUnixClient(string path)124 		public ReusableUnixClient (string path): base (path)
125 		{
126 		}
127 
128 		public bool IsAlive
129 		{
130 			get
131 			{
132 				// This Poll will return true if there is data pending to
133 				// be read. It prob. means that a client object using this
134 				// connection got an exception and did not finish to read
135 				// the data. It can also mean that the connection has been
136 				// closed in the server. In both cases, the connection cannot
137 				// be reused.
138 				return !Client.Poll (0, SelectMode.SelectRead);
139 			}
140 		}
141 	}
142 
143 	internal class UnixConnection
144 	{
145 		DateTime _controlTime;
146 		Stream _stream;
147 		ReusableUnixClient _client;
148 		HostConnectionPool _pool;
149 		byte[] _buffer;
150 
UnixConnection(HostConnectionPool pool, ReusableUnixClient client)151 		public UnixConnection (HostConnectionPool pool, ReusableUnixClient client)
152 		{
153 			_pool = pool;
154 			_client = client;
155 			_stream = new BufferedStream (client.GetStream());
156 			_controlTime = DateTime.UtcNow;
157 			_buffer = new byte[UnixMessageIO.DefaultStreamBufferSize];
158 		}
159 
160 		public Stream Stream
161 		{
162 			get { return _stream; }
163 		}
164 
165 		public DateTime ControlTime
166 		{
167 			get { return _controlTime; }
168 			set { _controlTime = value; }
169 		}
170 
171 		public bool IsAlive
172 		{
173 			get { return _client.IsAlive; }
174 		}
175 
176 		// This is a "thread safe" buffer that can be used by
177 		// UnixClientTransportSink to read or send data to the stream.
178 		// The buffer is "thread safe" since only one thread can
179 		// use a connection at a given time.
180 		public byte[] Buffer
181 		{
182 			get { return _buffer; }
183 		}
184 
185 		// Returns the connection to the pool
Release()186 		public void Release()
187 		{
188 			_pool.ReleaseConnection (this);
189 		}
190 
Close()191 		public void Close()
192 		{
193 			_client.Close();
194 		}
195 	}
196 
197 	internal class HostConnectionPool
198 	{
199 		ArrayList _pool = new ArrayList();
200 		int _activeConnections = 0;
201 
202 		string _path;
203 
HostConnectionPool(string path)204 		public HostConnectionPool (string path)
205 		{
206 			_path = path;
207 		}
208 
GetConnection()209 		public UnixConnection GetConnection ()
210 		{
211 			UnixConnection connection = null;
212 			lock (_pool)
213 			{
214 				do
215 				{
216 					if (_pool.Count > 0)
217 					{
218 						// There are available connections
219 
220 						connection = (UnixConnection)_pool[_pool.Count - 1];
221 						_pool.RemoveAt(_pool.Count - 1);
222 						if (!connection.IsAlive) {
223 							CancelConnection (connection);
224 							connection = null;
225 							continue;
226 						}
227 					}
228 
229 					if (connection == null && _activeConnections < UnixConnectionPool.MaxOpenConnections)
230 					{
231 						// No connections available, but the max connections
232 						// has not been reached yet, so a new one can be created
233 						// Create the connection outside the lock
234 						break;
235 					}
236 
237 					// No available connections in the pool
238 					// Wait for somewone to release one.
239 
240 					if (connection == null)
241 					{
242 						Monitor.Wait(_pool);
243 					}
244 				}
245 				while (connection == null);
246 			}
247 
248 			if (connection == null)
249 				return CreateConnection ();
250 			else
251 				return connection;
252 		}
253 
CreateConnection()254 		private UnixConnection CreateConnection()
255 		{
256 			try
257 			{
258 				ReusableUnixClient client = new ReusableUnixClient (_path);
259 				UnixConnection entry = new UnixConnection(this, client);
260 				_activeConnections++;
261 				return entry;
262 			}
263 			catch (Exception ex)
264 			{
265 				throw new RemotingException (ex.Message);
266 			}
267 		}
268 
ReleaseConnection(UnixConnection entry)269 		public void ReleaseConnection (UnixConnection entry)
270 		{
271 			lock (_pool)
272 			{
273 				entry.ControlTime = DateTime.UtcNow;	// Initialize timeout
274 				_pool.Add (entry);
275 				Monitor.Pulse (_pool);
276 			}
277 		}
278 
CancelConnection(UnixConnection entry)279 		private void CancelConnection(UnixConnection entry)
280 		{
281 			try
282 			{
283 				entry.Stream.Close();
284 				_activeConnections--;
285 			}
286 			catch
287 			{
288 			}
289 		}
290 
PurgeConnections()291 		public void PurgeConnections()
292 		{
293 			lock (_pool)
294 			{
295 				for (int n=0; n < _pool.Count; n++)
296 				{
297 					UnixConnection entry = (UnixConnection)_pool[n];
298 					if ( (DateTime.UtcNow - entry.ControlTime).TotalSeconds > UnixConnectionPool.KeepAliveSeconds)
299 					{
300 						CancelConnection (entry);
301 						_pool.RemoveAt(n);
302 						n--;
303 					}
304 				}
305 			}
306 		}
307 
308 	}
309 
310 
311 }
312