using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using VNLib.Utils;
using VNLib.Utils.Memory.Caching;
using VNLib.Net.Http;
using VNLib.Net.Messaging.FBM.Client;
using VNLib.Plugins.Essentials.Sessions;
#nullable enable
namespace VNLib.Plugins.Sessions.Cache.Client
{
///
/// A client that allows access to sessions located on external servers
///
public abstract class SessionCacheClient : VnDisposeable, ICacheHolder
{
public class LRUSessionStore : LRUCache where T : ISession, ICacheable
{
public override bool IsReadOnly => false;
protected override int MaxCapacity { get; }
public LRUSessionStore(int maxCapacity) : base(StringComparer.Ordinal) => MaxCapacity = maxCapacity;
protected override bool CacheMiss(string key, [NotNullWhen(true)] out T? value)
{
value = default;
return false;
}
protected override void Evicted(KeyValuePair evicted)
{
//Evice record
evicted.Value.Evicted();
}
}
protected readonly LRUSessionStore CacheTable;
protected readonly object CacheLock;
protected readonly int MaxLoadedEntires;
protected FBMClient Client { get; }
///
/// Initializes a new
///
///
/// The maximum number of sessions to keep in memory
public SessionCacheClient(FBMClient client, int maxCacheItems)
{
MaxLoadedEntires = maxCacheItems;
CacheLock = new();
CacheTable = new(maxCacheItems);
Client = client;
//Listen for close events
Client.ConnectionClosed += Client_ConnectionClosed;
}
private void Client_ConnectionClosed(object? sender, EventArgs e) => CacheHardClear();
///
/// Attempts to get a session from the cache identified by its sessionId asynchronously
///
/// The connection/request to attach the session to
/// The ID of the session to retrieve
/// A token to cancel the operation
/// A that resolves the remote session
///
public virtual async ValueTask GetSessionAsync(IHttpEvent entity, string sessionId, CancellationToken cancellationToken)
{
Check();
try
{
RemoteSession? session;
//Aquire lock on cache
lock (CacheLock)
{
//See if session is loaded into cache
if (!CacheTable.TryGetValue(sessionId, out session))
{
//Init new record
session = SessionCtor(sessionId);
//Add to cache
CacheTable.Add(session.SessionID, session);
}
//Valid entry found in cache
}
try
{
//Load session-data
await session.WaitAndLoadAsync(entity, cancellationToken);
return session;
}
catch
{
//Remove the invalid cached session
lock (CacheLock)
{
_ = CacheTable.Remove(sessionId);
}
throw;
}
}
catch (SessionException)
{
throw;
}
catch (OperationCanceledException)
{
throw;
}
//Wrap exceptions
catch (Exception ex)
{
throw new SessionException("An unhandled exception was raised", ex);
}
}
///
/// Gets a new instances for the given sessionId,
/// and places it a the head of internal cache
///
/// The session identifier
/// The new session for the given ID
protected abstract RemoteSession SessionCtor(string sessionId);
///
public void CacheClear()
{
}
///
public void CacheHardClear()
{
//Cleanup cache when disconnected
lock (CacheLock)
{
CacheTable.Clear();
foreach (RemoteSession session in (IEnumerable)CacheTable)
{
session.Evicted();
}
CacheTable.Clear();
}
}
protected override void Free()
{
//Unsub from events
Client.ConnectionClosed -= Client_ConnectionClosed;
//Clear all cached sessions
CacheHardClear();
}
}
}