panel v0.9.2 — public release
Self-hostable game server control panel: Go controller + agent, 26 game modules, embedded web UI. One-line install via install.sh / install.ps1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user