275 lines
8.8 KiB
C#
275 lines
8.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace MontoyaTech.MySqlPlus
|
|
{
|
|
/// <summary>
|
|
/// The outline of a MySqlSession Cache which will cache and automatically free MySql Sessions
|
|
/// in order to speed up query times.
|
|
/// </summary>
|
|
public class MySqlSessionCache
|
|
{
|
|
/// <summary>
|
|
/// The outline of a cached MySqlSession.
|
|
/// </summary>
|
|
public class CachedMySqlSession : MySqlSession
|
|
{
|
|
/// <summary>
|
|
/// The Last Time this cached API Session was used.
|
|
/// </summary>
|
|
public DateTime LastUse = DateTime.MinValue;
|
|
|
|
/// <summary>
|
|
/// The method that requested this cache.
|
|
/// </summary>
|
|
public string CallerMethod = "";
|
|
|
|
/// <summary>
|
|
/// The line of code that requested this cache.
|
|
/// </summary>
|
|
public string CallerLine = "";
|
|
|
|
/// <summary>
|
|
/// The Id of this CachedAPISession.
|
|
/// </summary>
|
|
public string Id = "";
|
|
|
|
/// <summary>
|
|
/// Whether or not this Cached API Session is in use.
|
|
/// </summary>
|
|
public bool InUse = false;
|
|
|
|
/// <summary>
|
|
/// Creates a new default CachedAPISession.
|
|
/// </summary>
|
|
public CachedMySqlSession(string connectionString) : base(connectionString)
|
|
{
|
|
this.Id = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dont allow the Session to be killed. Instead set in use to false.
|
|
/// </summary>
|
|
public override void Dispose()
|
|
{
|
|
InUse = false;
|
|
|
|
//Set this again because we just stopped using this cached session.
|
|
this.LastUse = DateTime.UtcNow;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The connection string to use when creating MySqlSessions.
|
|
/// </summary>
|
|
private static string ConnectionString = null;
|
|
|
|
/// <summary>
|
|
/// The list of Cached Sessions currently being maintained by the Cache.
|
|
/// </summary>
|
|
private static List<CachedMySqlSession> Sessions = new List<CachedMySqlSession>();
|
|
|
|
/// <summary>
|
|
/// The Thread that will handle uncaching.
|
|
/// </summary>
|
|
private static Thread UnCacheThread = null;
|
|
|
|
/// <summary>
|
|
/// The amount of time in minutes that a cached api session is allowed to live.
|
|
/// </summary>
|
|
private static int CacheLifeTime = 20;
|
|
|
|
/// <summary>
|
|
/// The amount of time in minutes before a cache is considered to be a zombie
|
|
/// and the result of a code bug somewhere.
|
|
/// </summary>
|
|
private static int ZombieCacheLifeTime = 5;
|
|
|
|
/// <summary>
|
|
/// The amount of time in seconds before we are allowed to attempt to uncache sessions.
|
|
/// </summary>
|
|
private static int UnCacheFrequency = 30;
|
|
|
|
/// <summary>
|
|
/// Whether or not the API Session Cache is running.
|
|
/// </summary>
|
|
private static bool Running = false;
|
|
|
|
/// <summary>
|
|
/// Starts this MySqlSessionCache with a given MySqlConnectionString.
|
|
/// </summary>
|
|
/// <param name="connectionStr"></param>
|
|
public static void Start(string connectionStr)
|
|
{
|
|
ConnectionString = connectionStr;
|
|
|
|
lock (Sessions)
|
|
{
|
|
if (!Running)
|
|
{
|
|
Running = true;
|
|
|
|
if (Sessions == null)
|
|
Sessions = new List<CachedMySqlSession>();
|
|
|
|
UnCacheThread = new Thread(UnCache);
|
|
UnCacheThread.IsBackground = true;
|
|
UnCacheThread.Start();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the APISessionCache Service.
|
|
/// </summary>
|
|
public static void Stop()
|
|
{
|
|
lock (Sessions)
|
|
{
|
|
if (Running)
|
|
{
|
|
UnCacheThread = null;
|
|
|
|
try
|
|
{
|
|
for (int i = 0; i < Sessions.Count; i++)
|
|
Sessions[i].Dispose();
|
|
}
|
|
catch { }
|
|
|
|
Sessions = null;
|
|
|
|
Running = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Kills all the cached api sessions if we can.
|
|
/// </summary>
|
|
public static void KillCache()
|
|
{
|
|
lock (Sessions)
|
|
{
|
|
if (Sessions != null)
|
|
{
|
|
for (int i = 0; i < Sessions.Count; i++)
|
|
{
|
|
var session = Sessions[i];
|
|
|
|
//Remove the sessions not in use and the ones who are zombies.
|
|
|
|
if (session.InUse == false)
|
|
{
|
|
Sessions.RemoveAt(i);
|
|
session.Dispose();
|
|
i--;
|
|
}
|
|
else if (session.InUse && (DateTime.UtcNow - session.LastUse).Minutes >= ZombieCacheLifeTime)
|
|
{
|
|
Sessions.RemoveAt(i);
|
|
session.Dispose();
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a APISession that is either new or cached depending on whats available.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static CachedMySqlSession Get()
|
|
{
|
|
if (!Running)
|
|
throw new Exception("API Session Cache is not running!");
|
|
|
|
lock (Sessions)
|
|
{
|
|
CachedMySqlSession result = null;
|
|
|
|
//See if we can find an existing session to use.
|
|
foreach (var cached in Sessions)
|
|
{
|
|
if (cached.InUse == false)
|
|
{
|
|
result = cached;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Create a new Session if we didnt find one.
|
|
if (result == null)
|
|
{
|
|
result = new CachedMySqlSession(ConnectionString);
|
|
|
|
if (Sessions == null)
|
|
Sessions = new List<CachedMySqlSession>() { result };
|
|
else
|
|
Sessions.Add(result);
|
|
}
|
|
|
|
result.InUse = true;
|
|
result.LastUse = DateTime.UtcNow;
|
|
|
|
//Get the caller information
|
|
var trace = new StackTrace();
|
|
|
|
var method = trace.GetFrame(1).GetMethod();
|
|
var methodName = method.Name;
|
|
var className = method.ReflectedType.Name;
|
|
var currNamespace = method.ReflectedType.Namespace;
|
|
|
|
result.CallerMethod = $"{currNamespace}.{className}.{methodName}()";
|
|
result.CallerLine = trace.GetFrame(1).GetFileLineNumber().ToString();
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The code that handles uncaching api sessions.
|
|
/// </summary>
|
|
private static void UnCache()
|
|
{
|
|
while (Running)
|
|
{
|
|
lock (Sessions)
|
|
{
|
|
if (Sessions != null)
|
|
{
|
|
for (int i = 0; i < Sessions.Count; i++)
|
|
{
|
|
var session = Sessions[i];
|
|
|
|
//If this session is older than it's lifetime and it's not in use, kill it.
|
|
if (session.InUse == false && (DateTime.UtcNow - session.LastUse).Minutes >= CacheLifeTime)
|
|
{
|
|
Sessions.RemoveAt(i);
|
|
session.Dispose();
|
|
i--;
|
|
}
|
|
//If this session is in use but it appears to be a zombie session, kill it.
|
|
else if (session.InUse && (DateTime.UtcNow - session.LastUse).Minutes >= ZombieCacheLifeTime)
|
|
{
|
|
Sessions.RemoveAt(i);
|
|
session.Dispose();
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Thread.Sleep(1000 * UnCacheFrequency);
|
|
}
|
|
}
|
|
}
|
|
}
|