MySqlPlus/MySqlPlus/MySqlSessionCache.cs

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);
}
}
}
}