277 lines
11 KiB
C#
277 lines
11 KiB
C#
|
|
//Copyright (c) 2007-2008 Henrik Schr<68>der, Oliver Kofoed Pedersen
|
|||
|
|
|
|||
|
|
//Permission is hereby granted, free of charge, to any person
|
|||
|
|
//obtaining a copy of this software and associated documentation
|
|||
|
|
//files (the "Software"), to deal in the Software without
|
|||
|
|
//restriction, including without limitation the rights to use,
|
|||
|
|
//copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|||
|
|
//copies of the Software, and to permit persons to whom the
|
|||
|
|
//Software is furnished to do so, subject to the following
|
|||
|
|
//conditions:
|
|||
|
|
|
|||
|
|
//The above copyright notice and this permission notice shall be
|
|||
|
|
//included in all copies or substantial portions of the Software.
|
|||
|
|
|
|||
|
|
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|||
|
|
//EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|||
|
|
//OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|||
|
|
//NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|||
|
|
//HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|||
|
|
//WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|||
|
|
//FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|||
|
|
//OTHER DEALINGS IN THE SOFTWARE.
|
|||
|
|
|
|||
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Diagnostics;
|
|||
|
|
using System.Net;
|
|||
|
|
using System.Threading;
|
|||
|
|
|
|||
|
|
namespace CYQ.Data.Cache
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// The SocketPool encapsulates the list of PooledSockets against one specific host, and contains methods for
|
|||
|
|
/// acquiring or returning PooledSockets.
|
|||
|
|
/// </summary>
|
|||
|
|
[DebuggerDisplay("[ Host: {Host} ]")]
|
|||
|
|
internal class SocketPool
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤Ȩ<D6A4><EFBFBD>ί<EFBFBD><CEAF><EFBFBD>¼<EFBFBD><C2BC><EFBFBD>
|
|||
|
|
internal delegate bool OnAfterSocketCreateDelegate(MSocket socket);
|
|||
|
|
internal event OnAfterSocketCreateDelegate OnAfterSocketCreateEvent;
|
|||
|
|
|
|||
|
|
private static LogAdapter logger = LogAdapter.GetLogger(typeof(SocketPool));
|
|||
|
|
/// <summary>
|
|||
|
|
/// <20><>չ<EFBFBD><D5B9><EFBFBD>ԣ<EFBFBD><D4A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӻ<EFBFBD><D3BA><EFBFBD>Ҫ<EFBFBD><D2AA>֤<EFBFBD><D6A4><EFBFBD>루<EFBFBD><EBA3A8>Redis<69><73><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>룩
|
|||
|
|
/// </summary>
|
|||
|
|
internal string password;
|
|||
|
|
/// <summary>
|
|||
|
|
/// <20><><EFBFBD>ݵ<EFBFBD>Socket<65>أ<EFBFBD><D8A3><EFBFBD><EFBFBD><EFBFBD>ij<EFBFBD><C4B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ˣ<EFBFBD><CBA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˱<EFBFBD><CBB1>ݵ<EFBFBD><DDB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>£<EFBFBD><C2A3><EFBFBD><EFBFBD>ɱ<EFBFBD><C9B1><EFBFBD>Socket<65><74><EFBFBD>ṩ<EFBFBD><E1B9A9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|||
|
|
/// <20><><EFBFBD><EFBFBD><EFBFBD>⣺<EFBFBD><E2A3BA>SocketPool<6F><6C>ô<EFBFBD>ҵ<EFBFBD><D2B5><EFBFBD>Ӧ<EFBFBD>ı<EFBFBD><C4B1>ݣ<EFBFBD><DDA3><EFBFBD>
|
|||
|
|
/// </summary>
|
|||
|
|
internal SocketPool socketPoolBak;
|
|||
|
|
/// <summary>
|
|||
|
|
/// Socket<65>Ĺҿ<C4B9>ʱ<EFBFBD>䡣
|
|||
|
|
/// </summary>
|
|||
|
|
private DateTime socketDeadTime = DateTime.MinValue;
|
|||
|
|
/// <summary>
|
|||
|
|
/// If the host stops responding, we mark it as dead for this amount of seconds,
|
|||
|
|
/// and we double this for each consecutive failed retry. If the host comes alive
|
|||
|
|
/// again, we reset this to 1 again.
|
|||
|
|
/// </summary>
|
|||
|
|
private int deadEndPointSecondsUntilRetry = 1;
|
|||
|
|
private const int maxDeadEndPointSecondsUntilRetry = 60 * 10; //10 minutes
|
|||
|
|
private ServerPool owner;
|
|||
|
|
private IPEndPoint endPoint;
|
|||
|
|
private Queue<MSocket> queue;
|
|||
|
|
|
|||
|
|
//Debug variables and properties
|
|||
|
|
private int newsockets = 0;
|
|||
|
|
private int failednewsockets = 0;
|
|||
|
|
private int reusedsockets = 0;
|
|||
|
|
private int deadsocketsinpool = 0;
|
|||
|
|
private int deadsocketsonreturn = 0;
|
|||
|
|
private int dirtysocketsonreturn = 0;
|
|||
|
|
private int acquired = 0;
|
|||
|
|
public int NewSockets { get { return newsockets; } }
|
|||
|
|
public int FailedNewSockets { get { return failednewsockets; } }
|
|||
|
|
public int ReusedSockets { get { return reusedsockets; } }
|
|||
|
|
public int DeadSocketsInPool { get { return deadsocketsinpool; } }
|
|||
|
|
public int DeadSocketsOnReturn { get { return deadsocketsonreturn; } }
|
|||
|
|
public int DirtySocketsOnReturn { get { return dirtysocketsonreturn; } }
|
|||
|
|
public int Acquired { get { return acquired; } }
|
|||
|
|
public int Poolsize { get { return queue.Count; } }
|
|||
|
|
|
|||
|
|
//Public variables and properties
|
|||
|
|
public readonly string Host;
|
|||
|
|
|
|||
|
|
private bool isEndPointDead = false;
|
|||
|
|
public bool IsEndPointDead { get { return isEndPointDead; } }
|
|||
|
|
|
|||
|
|
private DateTime deadEndPointRetryTime;
|
|||
|
|
public DateTime DeadEndPointRetryTime { get { return deadEndPointRetryTime; } }
|
|||
|
|
|
|||
|
|
internal SocketPool(ServerPool owner, string host)
|
|||
|
|
{
|
|||
|
|
Host = host;
|
|||
|
|
this.owner = owner;
|
|||
|
|
endPoint = getEndPoint(host);
|
|||
|
|
queue = new Queue<MSocket>();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// This method parses the given string into an IPEndPoint.
|
|||
|
|
/// If the string is malformed in some way, or if the host cannot be resolved, this method will throw an exception.
|
|||
|
|
/// </summary>
|
|||
|
|
private static IPEndPoint getEndPoint(string host)
|
|||
|
|
{
|
|||
|
|
//Parse port, default to 11211.
|
|||
|
|
int port = 11211;
|
|||
|
|
if (host.Contains(":"))
|
|||
|
|
{
|
|||
|
|
string[] split = host.Split(new char[] { ':' });
|
|||
|
|
if (!Int32.TryParse(split[1], out port))
|
|||
|
|
{
|
|||
|
|
throw new ArgumentException("Unable to parse host: " + host);
|
|||
|
|
}
|
|||
|
|
host = split[0];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//Parse host string.
|
|||
|
|
IPAddress address;
|
|||
|
|
if (IPAddress.TryParse(host, out address))
|
|||
|
|
{
|
|||
|
|
//host string successfully resolved as an IP address.
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
//See if we can resolve it as a hostname
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
address = Dns.GetHostEntry(host).AddressList[0];
|
|||
|
|
}
|
|||
|
|
catch (Exception e)
|
|||
|
|
{
|
|||
|
|
throw new ArgumentException("Unable to resolve host: " + host, e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new IPEndPoint(address, port);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Gets a socket from the pool.
|
|||
|
|
/// If there are no free sockets, a new one will be created. If something goes
|
|||
|
|
/// wrong while creating the new socket, this pool's endpoint will be marked as dead
|
|||
|
|
/// and all subsequent calls to this method will return null until the retry interval
|
|||
|
|
/// has passed.
|
|||
|
|
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>չ<EFBFBD><D5B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӳأ<D3B3>
|
|||
|
|
/// </summary>
|
|||
|
|
internal MSocket Acquire()
|
|||
|
|
{
|
|||
|
|
//<2F><><EFBFBD>ǰ<E2B5B1>Ƿ<EFBFBD><C7B7>ҿƣ<D2BF><C6A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>(15<31><35><EFBFBD><EFBFBD><EFBFBD><EFBFBD>)<29><><EFBFBD>ɱ<EFBFBD><C9B1>ݷ<EFBFBD><DDB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ṩ<EFBFBD><E1B9A9><EFBFBD><EFBFBD>
|
|||
|
|
if (socketDeadTime.AddMinutes(15) >= DateTime.Now && socketPoolBak != null)
|
|||
|
|
{
|
|||
|
|
return socketPoolBak.Acquire();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//Do we have free sockets in the pool?
|
|||
|
|
//if so - return the first working one.
|
|||
|
|
//if not - create a new one.
|
|||
|
|
Interlocked.Increment(ref acquired);
|
|||
|
|
lock (queue)
|
|||
|
|
{
|
|||
|
|
while (queue.Count > 0)
|
|||
|
|
{
|
|||
|
|
MSocket socket = queue.Dequeue();
|
|||
|
|
if (socket != null && socket.IsAlive)
|
|||
|
|
{
|
|||
|
|
Interlocked.Increment(ref reusedsockets);
|
|||
|
|
return socket;
|
|||
|
|
}
|
|||
|
|
Interlocked.Increment(ref deadsocketsinpool);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Interlocked.Increment(ref newsockets);
|
|||
|
|
//If we know the endpoint is dead, check if it is time for a retry, otherwise return null.
|
|||
|
|
if (isEndPointDead)
|
|||
|
|
{
|
|||
|
|
if (DateTime.Now > deadEndPointRetryTime)
|
|||
|
|
{
|
|||
|
|
//Retry
|
|||
|
|
isEndPointDead = false;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
//Still dead
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//Try to create a new socket. On failure, mark endpoint as dead and return null.
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
MSocket socket = new MSocket(this, endPoint, owner.SendReceiveTimeout, owner.ConnectTimeout);
|
|||
|
|
//Reset retry timer on success.
|
|||
|
|
//<2F><><EFBFBD><EFBFBD><EFBFBD>쳣<EFBFBD><ECB3A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӡ<EFBFBD>
|
|||
|
|
if (OnAfterSocketCreateEvent != null)
|
|||
|
|
{
|
|||
|
|
OnAfterSocketCreateEvent(socket);
|
|||
|
|
}
|
|||
|
|
deadEndPointSecondsUntilRetry = 1;
|
|||
|
|
return socket;
|
|||
|
|
}
|
|||
|
|
catch (Exception e)
|
|||
|
|
{
|
|||
|
|
Interlocked.Increment(ref failednewsockets);
|
|||
|
|
logger.Error("Error connecting to: " + endPoint.Address, e);
|
|||
|
|
//Mark endpoint as dead
|
|||
|
|
isEndPointDead = true;
|
|||
|
|
//Retry in 2 minutes
|
|||
|
|
deadEndPointRetryTime = DateTime.Now.AddSeconds(deadEndPointSecondsUntilRetry);
|
|||
|
|
if (deadEndPointSecondsUntilRetry < maxDeadEndPointSecondsUntilRetry)
|
|||
|
|
{
|
|||
|
|
deadEndPointSecondsUntilRetry = deadEndPointSecondsUntilRetry * 2; //Double retry interval until next time
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
socketDeadTime = DateTime.Now;
|
|||
|
|
//<2F><><EFBFBD>ر<EFBFBD><D8B1>ݵij<DDB5>
|
|||
|
|
if (socketPoolBak != null)
|
|||
|
|
{
|
|||
|
|
return socketPoolBak.Acquire();
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Returns a socket to the pool.
|
|||
|
|
/// If the socket is dead, it will be destroyed.
|
|||
|
|
/// If there are more than MaxPoolSize sockets in the pool, it will be destroyed.
|
|||
|
|
/// If there are less than MinPoolSize sockets in the pool, it will always be put back.
|
|||
|
|
/// If there are something inbetween those values, the age of the socket is checked.
|
|||
|
|
/// If it is older than the SocketRecycleAge, it is destroyed, otherwise it will be
|
|||
|
|
/// put back in the pool.
|
|||
|
|
/// </summary>
|
|||
|
|
internal void Return(MSocket socket)
|
|||
|
|
{
|
|||
|
|
//If the socket is dead, destroy it.
|
|||
|
|
if (!socket.IsAlive)
|
|||
|
|
{
|
|||
|
|
Interlocked.Increment(ref deadsocketsonreturn);
|
|||
|
|
socket.Close();
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
//Clean up socket
|
|||
|
|
if (socket.Reset())
|
|||
|
|
{
|
|||
|
|
Interlocked.Increment(ref dirtysocketsonreturn);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//Check pool size.
|
|||
|
|
if (queue.Count >= owner.MaxPoolSize)
|
|||
|
|
{
|
|||
|
|
//If the pool is full, destroy the socket.
|
|||
|
|
socket.Close();
|
|||
|
|
}
|
|||
|
|
else if (queue.Count > owner.MinPoolSize && DateTime.Now - socket.Created > owner.SocketRecycleAge)
|
|||
|
|
{
|
|||
|
|
//If we have more than the minimum amount of sockets, but less than the max, and the socket is older than the recycle age, we destroy it.
|
|||
|
|
socket.Close();
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
//Put the socket back in the pool.
|
|||
|
|
lock (queue)
|
|||
|
|
{
|
|||
|
|
queue.Enqueue(socket);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|