From 20521bb8c49e99f62863256d9eae13fb61ad3b18 Mon Sep 17 00:00:00 2001 From: Bart Roossien Date: Sat, 15 Aug 2026 20:15:09 +0200 Subject: [PATCH 1/2] hotfix: Move ProcessLobbiesNeedingDestroyed into its own lobby timer to prevent blocking of Tick() actions --- GenOnlineService/LobbyManager.cs | 4 +--- GenOnlineService/Program.cs | 28 ++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/GenOnlineService/LobbyManager.cs b/GenOnlineService/LobbyManager.cs index e19c6ce..78e7aa6 100644 --- a/GenOnlineService/LobbyManager.cs +++ b/GenOnlineService/LobbyManager.cs @@ -1285,7 +1285,7 @@ private void HandleLobbyNeedsDestroyed(Lobby lobby) m_queueLobbiesNeedingDestroyed.Enqueue(lobby); } - private async Task ProcessLobbiesNeedingDestroyed() + public async Task ProcessLobbiesNeedingDestroyed() { while (m_queueLobbiesNeedingDestroyed.TryDequeue(out Lobby? lobbyToDestroy)) { @@ -1365,8 +1365,6 @@ public async Task Tick() { await kvPair.Value.Tick(); } - - await ProcessLobbiesNeedingDestroyed(); } public async Task JoinLobby(AppDbContext _db, Lobby lobby, UserSession playerSession, string strDisplayName, UInt16 userPreferredPort, bool bHasMap) diff --git a/GenOnlineService/Program.cs b/GenOnlineService/Program.cs index 508b202..528433a 100644 --- a/GenOnlineService/Program.cs +++ b/GenOnlineService/Program.cs @@ -1268,8 +1268,32 @@ public static async Task Main(string[] args) timerTick.Start(); } - // tick matchmaking (done at lower frequency) - { + // tick lobby cleanup - this is a separate timer to prevent main lobby tick from being blocked by cleanup + // @hotfix SkyAero 15/08/2026 + { + System.Timers.Timer timerTick = new System.Timers.Timer(5); // 5ms tick + timerTick.AutoReset = false; + timerTick.Elapsed += async (sender, e) => + { + try + { + var lobbyManager = ServiceLocator.Services.GetRequiredService(); + await lobbyManager.ProcessLobbiesNeedingDestroyed(); + } + catch (Exception ex) + { + Console.WriteLine($"[cleanupTick lobby] Exception: {ex}"); + } + finally + { + timerTick.Start(); + } + }; + timerTick.Start(); + } + + // tick matchmaking (done at lower frequency) + { System.Timers.Timer timerTick = new System.Timers.Timer(1000); // 1s tick timerTick.AutoReset = false; timerTick.Elapsed += async (sender, e) => From dc7f0f265b4c9f3381022041a5371c66471cb904 Mon Sep 17 00:00:00 2001 From: Bart Roossien Date: Sat, 15 Aug 2026 20:27:27 +0200 Subject: [PATCH 2/2] hotfix: Prevent unnecessary creation of AppDbContext Move AppDbContext instance to class level instead of method level in Lobby and LobbyManager to prevent unnecessary database connection creation --- GenOnlineService/LobbyManager.cs | 37 +++++++++++++++++--------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/GenOnlineService/LobbyManager.cs b/GenOnlineService/LobbyManager.cs index 78e7aa6..0092805 100644 --- a/GenOnlineService/LobbyManager.cs +++ b/GenOnlineService/LobbyManager.cs @@ -389,9 +389,13 @@ public Lobby(Int64 lobby_id, UserSession owner, string name, ELobbyState state, LobbyMember placeholderMember = new LobbyMember(this, null, -1, String.Empty, String.Empty, 0, -1, -1, -1, i < max_players ? EPlayerType.SLOT_OPEN : EPlayerType.SLOT_CLOSED, i, true); Members[i] = placeholderMember; } - } - public event Action? OnLobbyNeedsDestroyed; + using var scope = ServiceLocator.Services.CreateScope(); + var factory = scope.ServiceProvider.GetRequiredService>(); + _db = factory.CreateDbContext(); + } + + public event Action? OnLobbyNeedsDestroyed; public async Task OnAfterPlayerLeft(Int64 leavingUserID) { @@ -965,9 +969,10 @@ public void ForceReady() private int m_cachedAtStart_numOpen = -1; private int m_cachedAtStart_numClosed = -1; private int m_cachedAtStart_numAI = -1; + private AppDbContext _db; - // TODO: Really, client also shouldnt upload data we arent going to process in this situation, its wasteful - public bool WasPVPAtStart() + // TODO: Really, client also shouldnt upload data we arent going to process in this situation, its wasteful + public bool WasPVPAtStart() { // debug #if DEBUG @@ -1012,10 +1017,7 @@ public async Task UpdateState(ELobbyState state) try { // create placeholder - using var scope = ServiceLocator.Services.CreateScope(); - var factory = scope.ServiceProvider.GetRequiredService>(); - await using var db = await factory.CreateDbContextAsync(); - await Database.MatchHistory.CreatePlaceholderMatchHistory(db, this); + await Database.MatchHistory.CreatePlaceholderMatchHistory(_db, this); } catch (Exception ex) { @@ -1244,13 +1246,18 @@ public class LobbyManager private Int64 m_NextLobbyID = 0; private readonly IServiceProvider _services; + private readonly AppDbContext _db; public LobbyManager(IServiceProvider services) { _services = services; - } - public async Task Cleanup() + var scope = _services.CreateScope(); + var factory = scope.ServiceProvider.GetRequiredService>(); + var _db = factory.CreateDbContext(); + } + + public async Task Cleanup() { // Remove any lobby that has 0 members and has been around for a bit (enough time for host to join) List lstLobbiesToRemove = new List(); @@ -1553,17 +1560,13 @@ public async Task DeleteLobby(Lobby lobby) { try { - using var scope = _services.CreateScope(); - var factory = scope.ServiceProvider.GetRequiredService>(); - await using var db = await factory.CreateDbContextAsync(); - if (lobby.State != ELobbyState.COMPLETE) { // make done await lobby.UpdateState(ELobbyState.COMPLETE); // attempt to commit it - await Database.MatchHistory.CommitLobbyToMatchHistory(db, lobby); + await Database.MatchHistory.CommitLobbyToMatchHistory(_db, lobby); } // delete @@ -1577,11 +1580,11 @@ public async Task DeleteLobby(Lobby lobby) lobby.OnLobbyNeedsDestroyed -= HandleLobbyNeedsDestroyed; // make sure we have a winner - await Database.MatchHistory.DetermineLobbyWinnerIfNotPresent(db, lobby); + await Database.MatchHistory.DetermineLobbyWinnerIfNotPresent(_db, lobby); // Post match result to external leaderboard API for every lobby type. // Only QuickMatch responses are expected to carry a ratings body. - await ExternalLeaderboardsClient.PostMatchResultAsync(db, lobby); + await ExternalLeaderboardsClient.PostMatchResultAsync(_db, lobby); } return bRemoved;