From f60805d27c5dd05ab5ca4331ff1d0513f60702dc Mon Sep 17 00:00:00 2001 From: MpakobecEx Date: Wed, 21 Oct 2026 16:18:18 +0500 Subject: [PATCH] Deactivate completed Orleans grains after TTL --- .../Matches/MatchGrainRegistry.cs | 22 +++++++++++++++---- .../Vanguard.Server/Matches/MatchTicker.cs | 2 +- .../Matches/Orleans/IMatchActor.cs | 3 +++ .../Matches/Orleans/MatchActorGrain.cs | 6 +++++ .../Matches/Orleans/OrleansMatchAdapter.cs | 5 +++++ 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/server/src/Vanguard.Server/Matches/MatchGrainRegistry.cs b/server/src/Vanguard.Server/Matches/MatchGrainRegistry.cs index 24df3fd..4a1e758 100644 --- a/server/src/Vanguard.Server/Matches/MatchGrainRegistry.cs +++ b/server/src/Vanguard.Server/Matches/MatchGrainRegistry.cs @@ -127,18 +127,32 @@ public sealed class MatchGrainRegistry public void Remove(string matchId) => _matches.TryRemove(matchId, out _); - public void RemoveExpired(DateTimeOffset now) + public async Task RemoveExpiredAsync(DateTimeOffset now) { var ttl = TimeSpan.FromSeconds(_options.CompletedMatchTtlSeconds); foreach (var (matchId, match) in _matches) { var summary = match.Summary; - if (summary.IsFinished && summary.FinishedAt is { } finishedAt && now - finishedAt >= ttl) + if (!summary.IsFinished || summary.FinishedAt is not { } finishedAt || now - finishedAt < ttl) + continue; + + if (!_matches.TryRemove(matchId, out _)) + continue; + + if (match is OrleansMatchAdapter adapter) { - _matches.TryRemove(matchId, out _); - _logger.LogInformation("Match {MatchId} removed after completion.", matchId); + try + { + await adapter.DeactivateAsync(); + } + catch (Exception exception) + { + _logger.LogDebug(exception, "Match {MatchId}: Orleans deactivation failed.", matchId); + } } + + _logger.LogInformation("Match {MatchId} removed after completion.", matchId); } } } diff --git a/server/src/Vanguard.Server/Matches/MatchTicker.cs b/server/src/Vanguard.Server/Matches/MatchTicker.cs index 66bcd4f..2086649 100644 --- a/server/src/Vanguard.Server/Matches/MatchTicker.cs +++ b/server/src/Vanguard.Server/Matches/MatchTicker.cs @@ -42,7 +42,7 @@ public sealed class MatchTicker : BackgroundService } } - _registry.RemoveExpired(now); + await _registry.RemoveExpiredAsync(now); } } } diff --git a/server/src/Vanguard.Server/Matches/Orleans/IMatchActor.cs b/server/src/Vanguard.Server/Matches/Orleans/IMatchActor.cs index 3b7927c..9cb24e8 100644 --- a/server/src/Vanguard.Server/Matches/Orleans/IMatchActor.cs +++ b/server/src/Vanguard.Server/Matches/Orleans/IMatchActor.cs @@ -31,6 +31,9 @@ public interface IMatchActor : IGrainWithStringKey Task GetSummaryAsync(); Task HasPlayerAsync(string playerId); + + /// Выгрузить завершённый матч из silo после TTL. + Task DeactivateAsync(); } [GenerateSerializer] diff --git a/server/src/Vanguard.Server/Matches/Orleans/MatchActorGrain.cs b/server/src/Vanguard.Server/Matches/Orleans/MatchActorGrain.cs index 5ee9d48..47b2bfe 100644 --- a/server/src/Vanguard.Server/Matches/Orleans/MatchActorGrain.cs +++ b/server/src/Vanguard.Server/Matches/Orleans/MatchActorGrain.cs @@ -146,6 +146,12 @@ public sealed class MatchActorGrain : Grain, IMatchActor return match.HasPlayer(playerId); } + public Task DeactivateAsync() + { + DeactivateOnIdle(); + return Task.CompletedTask; + } + private async Task RequireAsync() { await EnsureInitializedAsync(); diff --git a/server/src/Vanguard.Server/Matches/Orleans/OrleansMatchAdapter.cs b/server/src/Vanguard.Server/Matches/Orleans/OrleansMatchAdapter.cs index 4d3d396..5a41033 100644 --- a/server/src/Vanguard.Server/Matches/Orleans/OrleansMatchAdapter.cs +++ b/server/src/Vanguard.Server/Matches/Orleans/OrleansMatchAdapter.cs @@ -88,6 +88,11 @@ public sealed class OrleansMatchAdapter : IMatchGrain await RefreshAsync(); } + public async Task DeactivateAsync() + { + await _actor.DeactivateAsync(); + } + private async Task RefreshAsync() { try