namespace BrassAndSigil.Server.Services; /// /// Thin reconnecting wrapper around . The original /// single-connection-with-no-retry pattern caches a dead client whenever the /// initial connect happens before MC has opened the RCON port (which is normal -- /// boot takes ~30 s). This manager lazily connects on first use, retries on /// failure, and drops the client when a send throws so the next call reconnects. /// public sealed class RconManager : IDisposable { private readonly string _host; private readonly int _port; private readonly string _password; private readonly SemaphoreSlim _lock = new(1, 1); private RconClient? _client; public RconManager(string host, int port, string password) { _host = host; _port = port; _password = password; } public async Task SendCommandAsync(string command, CancellationToken ct = default) { var client = await EnsureConnectedAsync(ct); try { return await client.SendCommandAsync(command, ct); } catch { await DropAsync(); throw; } } private async Task EnsureConnectedAsync(CancellationToken ct) { await _lock.WaitAsync(ct); try { if (_client is { Connected: true }) return _client; _client?.Dispose(); _client = new RconClient(); await _client.ConnectAsync(_host, _port, _password, ct); return _client; } finally { _lock.Release(); } } private async Task DropAsync() { await _lock.WaitAsync(); try { _client?.Dispose(); _client = null; } finally { _lock.Release(); } } public void Dispose() { _client?.Dispose(); _lock.Dispose(); } }