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