From 50578770c67b167b140483cf33c3b4737988bde5 Mon Sep 17 00:00:00 2001 From: Flegma Date: Wed, 8 Apr 2026 13:51:14 +0200 Subject: [PATCH] fix: add proper resource disposal for HttpClient, WebSocket, timers, and pending messages Wrap HttpClient in using statement in MatchService.GetMatchFromApi. Dispose _retryTimer and _webSocket in MatchEvents.Disconnect. Dispose old WebSocket before replacement in Connect. Cap _pendingMessages at 1000 entries. Clear entire _disconnectTimers dictionary in SurrenderSystem.Reset. --- src/FiveStack.Services/MatchEvents.cs | 21 ++++++- src/FiveStack.Services/MatchService.cs | 72 ++++++++++++----------- src/FiveStack.Services/SurrenderSystem.cs | 2 +- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/src/FiveStack.Services/MatchEvents.cs b/src/FiveStack.Services/MatchEvents.cs index 3b1406c..4fc81e5 100644 --- a/src/FiveStack.Services/MatchEvents.cs +++ b/src/FiveStack.Services/MatchEvents.cs @@ -206,15 +206,17 @@ await _webSocket.CloseAsync( { _logger.LogWarning($"Error closing existing WebSocket: {ex.Message}"); } + _webSocket.Dispose(); _webSocket = null; } _connectionCts?.Cancel(); _connectionCts = new CancellationTokenSource(); + ClientWebSocket? newWebSocket = null; try { - _webSocket = new ClientWebSocket(); + newWebSocket = new ClientWebSocket(); _environmentService.Load(); @@ -224,10 +226,11 @@ await _webSocket.CloseAsync( if (serverId == null || serverApiPassword == null) { _logger.LogWarning("Cannot connect to WebSocket, Missing Server ID / API Password"); + newWebSocket.Dispose(); return false; } - _webSocket.Options.SetRequestHeader( + newWebSocket.Options.SetRequestHeader( "Authorization", $"Basic {Convert.ToBase64String( System.Text.Encoding.UTF8.GetBytes($"{serverId}:{serverApiPassword}") @@ -235,7 +238,9 @@ await _webSocket.CloseAsync( ); var uri = new Uri($"{_environmentService.GetWsUrl()}/matches"); - await _webSocket.ConnectAsync(uri, _connectionCts.Token); + await newWebSocket.ConnectAsync(uri, _connectionCts.Token); + + _webSocket = newWebSocket; _matchService.GetMatchFromApi(); @@ -244,11 +249,13 @@ await _webSocket.CloseAsync( catch (WebSocketException ex) { _logger.LogWarning("Failed to connect to WebSocket server: " + ex.Message); + newWebSocket?.Dispose(); return false; } catch (Exception ex) { _logger.LogCritical("An error occurred: " + ex.Message); + newWebSocket?.Dispose(); return false; } } @@ -259,6 +266,7 @@ public async Task Disconnect() _connectionCts?.Cancel(); _isMonitoring = false; _retryTimer.Stop(); + _retryTimer?.Dispose(); if (_webSocket != null && _webSocket.State == WebSocketState.Open) { @@ -267,6 +275,7 @@ await _webSocket.CloseAsync( "Disconnecting", CancellationToken.None ); + _webSocket.Dispose(); _webSocket = null; } } @@ -324,6 +333,12 @@ private async Task Publish(Guid matchId, Guid mapId, EventData data) if (data is EventData> typedData) { _pendingMessages[data.messageId] = (typedData, DateTime.UtcNow); + + if (_pendingMessages.Count > 1000) + { + var oldest = _pendingMessages.OrderBy(p => p.Value.Timestamp).First(); + _pendingMessages.Remove(oldest.Key); + } } try diff --git a/src/FiveStack.Services/MatchService.cs b/src/FiveStack.Services/MatchService.cs index 8adf701..61c50ce 100644 --- a/src/FiveStack.Services/MatchService.cs +++ b/src/FiveStack.Services/MatchService.cs @@ -79,8 +79,6 @@ public async void GetMatchFromApi() return; } - HttpClient httpClient = new HttpClient(); - string? serverId = _environmentService.GetServerId(); string? apiPassword = _environmentService.GetServerApiPassword(); @@ -97,53 +95,57 @@ public async void GetMatchFromApi() try { - string matchUri = $"{_environmentService.GetApiUrl()}/matches/current-match/{serverId}"; + using (var httpClient = new HttpClient()) + { + string matchUri = + $"{_environmentService.GetApiUrl()}/matches/current-match/{serverId}"; - _logger.LogInformation($"Fetching Match Info for server : {serverId}"); + _logger.LogInformation($"Fetching Match Info for server : {serverId}"); - httpClient.DefaultRequestHeaders.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", apiPassword); + httpClient.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", apiPassword); - string? response = await httpClient.GetStringAsync(matchUri); + string? response = await httpClient.GetStringAsync(matchUri); - Server.NextFrame(() => - { - if (response.Length == 0) + Server.NextFrame(() => { - if (_currentMatch != null) + if (response.Length == 0) { - _currentMatch.Reset(); - } + if (_currentMatch != null) + { + _currentMatch.Reset(); + } - _currentMatch = null; + _currentMatch = null; - _logger.LogWarning("currenlty no match assigned to server"); - return; - } + _logger.LogWarning("currenlty no match assigned to server"); + return; + } - MatchData? matchData = JsonSerializer.Deserialize(response); + MatchData? matchData = JsonSerializer.Deserialize(response); - if (matchData == null) - { - return; - } + if (matchData == null) + { + return; + } - if (_currentMatch?.GetMatchData()?.id == matchData.id) - { - _currentMatch.SetupMatch(matchData); - return; - } + if (_currentMatch?.GetMatchData()?.id == matchData.id) + { + _currentMatch.SetupMatch(matchData); + return; + } - if (_currentMatch != null) - { - _currentMatch.Reset(); - } + if (_currentMatch != null) + { + _currentMatch.Reset(); + } - _currentMatch = - _serviceProvider.GetRequiredService(typeof(MatchManager)) as MatchManager; + _currentMatch = + _serviceProvider.GetRequiredService(typeof(MatchManager)) as MatchManager; - _currentMatch!.SetupMatch(matchData); - }); + _currentMatch!.SetupMatch(matchData); + }); + } } catch (HttpRequestException ex) { diff --git a/src/FiveStack.Services/SurrenderSystem.cs b/src/FiveStack.Services/SurrenderSystem.cs index ad28f80..2cb9fe8 100644 --- a/src/FiveStack.Services/SurrenderSystem.cs +++ b/src/FiveStack.Services/SurrenderSystem.cs @@ -155,8 +155,8 @@ public void Reset() { timer?.Kill(); } - _disconnectTimers[team].Clear(); } + _disconnectTimers.Clear(); } public bool IsSurrendering()