658bda1d24
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
86 lines
2.4 KiB
C#
86 lines
2.4 KiB
C#
using System.Collections.Concurrent;
|
|
|
|
namespace EmpyrionBridge;
|
|
|
|
// Lazy connection pool keyed by host:port. Reuses an EPM TCP connection
|
|
// across HTTP requests; drops it after IdleTtl with no traffic.
|
|
public class BridgePool : IAsyncDisposable
|
|
{
|
|
public TimeSpan ConnectTimeout { get; init; } = TimeSpan.FromSeconds(8);
|
|
public TimeSpan IdleTtl { get; init; } = TimeSpan.FromMinutes(5);
|
|
|
|
readonly ConcurrentDictionary<string, Entry> _entries = new();
|
|
readonly ILoggerFactory _logFactory;
|
|
readonly ILogger<BridgePool> _log;
|
|
readonly Timer _reaper;
|
|
|
|
public BridgePool(ILoggerFactory logFactory)
|
|
{
|
|
_logFactory = logFactory;
|
|
_log = logFactory.CreateLogger<BridgePool>();
|
|
_reaper = new Timer(Reap, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
|
|
}
|
|
|
|
public async Task<BridgeConnection> GetAsync(string host, int port)
|
|
{
|
|
var key = $"{host}:{port}";
|
|
Entry e;
|
|
lock (_entries)
|
|
{
|
|
if (!_entries.TryGetValue(key, out e))
|
|
{
|
|
e = new Entry { Conn = new BridgeConnection(host, port, _logFactory.CreateLogger("EPM")) };
|
|
_entries[key] = e;
|
|
}
|
|
e.LastUsed = DateTime.UtcNow;
|
|
}
|
|
|
|
if (!e.Conn.IsConnected)
|
|
{
|
|
await e.ConnectGate.WaitAsync();
|
|
try
|
|
{
|
|
if (!e.Conn.IsConnected)
|
|
{
|
|
await e.Conn.ConnectAsync(ConnectTimeout);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
e.ConnectGate.Release();
|
|
}
|
|
}
|
|
return e.Conn;
|
|
}
|
|
|
|
void Reap(object _)
|
|
{
|
|
var cutoff = DateTime.UtcNow - IdleTtl;
|
|
foreach (var kv in _entries.ToArray())
|
|
{
|
|
if (kv.Value.LastUsed < cutoff)
|
|
{
|
|
if (_entries.TryRemove(kv.Key, out var e))
|
|
{
|
|
_log.LogInformation("reaping idle connection {Key}", kv.Key);
|
|
e.Conn.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
await _reaper.DisposeAsync();
|
|
foreach (var e in _entries.Values) e.Conn.Dispose();
|
|
_entries.Clear();
|
|
}
|
|
|
|
class Entry
|
|
{
|
|
public BridgeConnection Conn = null!;
|
|
public DateTime LastUsed = DateTime.UtcNow;
|
|
public SemaphoreSlim ConnectGate = new(1, 1);
|
|
}
|
|
}
|