From c98e2b2dd636d862e1b57e6658dbdfbf74a04b19 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Thu, 13 Oct 2022 16:55:23 +0200 Subject: [PATCH] Switch Authentication to asynchronous streaming calls (#16) * add base grpc service and swap auth service to streaming * remove Authorize from hub itself * remove unused usings * heave files server to net 7, add exception handling in grpc auth stream Co-authored-by: rootdarkarchon --- .../Hubs/MareHub.Functions.cs | 4 - .../MareSynchronosServer/Hubs/MareHub.User.cs | 3 +- .../MareSynchronosServer/Hubs/MareHub.cs | 2 - .../GrpcClientIdentificationService.cs | 139 +++++------------- .../MareSynchronosServer/Startup.cs | 3 + .../Authentication/FailedAuthorization.cs | 2 +- .../SecretKeyAuthenticationHandler.cs | 12 +- .../MareSynchronosServices/CleanupService.cs | 2 +- .../Identity/IdentityHandler.cs | 11 +- .../Services/AuthenticationService.cs | 12 +- .../SecretKeyGrpcAuthenticationHandler.cs | 12 +- .../Data/MareDbContext.cs | 1 - .../Migrations/20221006122618_groupbans.cs | 3 +- .../MareSynchronosShared/Models/GroupBan.cs | 8 +- .../Protos/mareservices.proto | 10 +- .../Services/GrpcAuthenticationService.cs | 109 ++++++++++++++ .../Services/GrpcBaseService.cs | 129 ++++++++++++++++ .../Utils/SharedDbFunctions.cs | 5 - .../MareSynchronosStaticFilesServer.csproj | 2 +- .../Startup.cs | 3 + 20 files changed, 313 insertions(+), 159 deletions(-) create mode 100644 MareSynchronosServer/MareSynchronosShared/Services/GrpcAuthenticationService.cs create mode 100644 MareSynchronosServer/MareSynchronosShared/Services/GrpcBaseService.cs diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Functions.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Functions.cs index 363f522..55eda08 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Functions.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Functions.cs @@ -4,12 +4,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System; -using Microsoft.AspNetCore.SignalR; -using System.Globalization; -using MareSynchronos.API; using MareSynchronosServer.Utils; using System.Security.Claims; -using Microsoft.Extensions.Logging; namespace MareSynchronosServer.Hubs; diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs index f0f38b3..a2d5189 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.User.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Threading.Tasks; using MareSynchronos.API; using MareSynchronosServer.Utils; -using MareSynchronosShared.Authentication; using MareSynchronosShared.Metrics; using MareSynchronosShared.Models; using MareSynchronosShared.Protos; @@ -38,7 +37,7 @@ public partial class MareHub await Task.Delay(1000).ConfigureAwait(false); } - await _authServiceClient.RemoveAuthAsync(new RemoveAuthRequest() { Uid = userid }).ConfigureAwait(false); + await _authServiceClient.RemoveAuthAsync(new UidMessage() { Uid = userid }).ConfigureAwait(false); _dbContext.RemoveRange(ownPairData); await _dbContext.SaveChangesAsync().ConfigureAwait(false); diff --git a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.cs b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.cs index 4ddfc17..fca9a1c 100644 --- a/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.cs +++ b/MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using MareSynchronos.API; using MareSynchronosServer.Services; using MareSynchronosServer.Utils; -using MareSynchronosShared.Authentication; using MareSynchronosShared.Data; using MareSynchronosShared.Metrics; using MareSynchronosShared.Protos; @@ -18,7 +17,6 @@ using Microsoft.Extensions.Logging; namespace MareSynchronosServer.Hubs; -[Authorize] public partial class MareHub : Hub, IMareHub { private readonly MareMetrics _mareMetrics; diff --git a/MareSynchronosServer/MareSynchronosServer/Services/GrpcClientIdentificationService.cs b/MareSynchronosServer/MareSynchronosServer/Services/GrpcClientIdentificationService.cs index 690c402..591dfbb 100644 --- a/MareSynchronosServer/MareSynchronosServer/Services/GrpcClientIdentificationService.cs +++ b/MareSynchronosServer/MareSynchronosServer/Services/GrpcClientIdentificationService.cs @@ -1,8 +1,8 @@ using Grpc.Core; using MareSynchronosShared.Metrics; using MareSynchronosShared.Protos; +using MareSynchronosShared.Services; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Concurrent; @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace MareSynchronosServer.Services; -public class GrpcClientIdentificationService : IHostedService +public class GrpcClientIdentificationService : GrpcBaseService { private readonly string _shardName; private readonly ILogger _logger; @@ -22,14 +22,11 @@ public class GrpcClientIdentificationService : IHostedService private readonly MareMetrics _metrics; protected readonly ConcurrentDictionary OnlineClients = new(StringComparer.Ordinal); private readonly ConcurrentDictionary RemoteCachedIdents = new(StringComparer.Ordinal); - private bool _grpcIsFaulty = false; private ConcurrentQueue _identChangeQueue = new(); - private CancellationTokenSource _streamCts = new(); - private CancellationTokenSource _faultCheckCts = new(); public GrpcClientIdentificationService(ILogger logger, IdentificationService.IdentificationServiceClient gprcIdentClient, IdentificationService.IdentificationServiceClient gprcIdentClientStreamOut, - IdentificationService.IdentificationServiceClient gprcIdentClientStreamIn, MareMetrics metrics, IConfiguration configuration) + IdentificationService.IdentificationServiceClient gprcIdentClientStreamIn, MareMetrics metrics, IConfiguration configuration) : base(logger) { var config = configuration.GetSection("MareSynchronos"); _shardName = config.GetValue("ShardName", "Main"); @@ -135,62 +132,6 @@ public class GrpcClientIdentificationService : IHostedService }); } - public async Task StartAsync(CancellationToken cancellationToken) - { - _ = RestartStreams(); - _ = CheckGrpcFaults(_faultCheckCts.Token); - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - _faultCheckCts.Cancel(); - _streamCts.Cancel(); - await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName })).ConfigureAwait(false); - } - - private async Task CheckGrpcFaults(CancellationToken ct) - { - while (!ct.IsCancellationRequested) - { - try - { - await CheckFaultStateAndResend().ConfigureAwait(false); - } - catch { SetGrpcFaulty(); } - await Task.Delay(250).ConfigureAwait(false); - } - } - - private async Task RestartStreams() - { - _streamCts?.Cancel(); - _streamCts?.Dispose(); - _streamCts = new(); - if (!_grpcIsFaulty) - { - try - { - await _grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName }).ConfigureAwait(false); - - RemoteCachedIdents.Clear(); - _ = StreamOnlineClientData(_streamCts.Token); - _ = ReceiveOnlineClientData(_streamCts.Token); - var remoteOnlineClients = await _grpcIdentClient.GetAllIdentsAsync(new ServerMessage() - { - ServerId = _shardName - }).ConfigureAwait(false); - foreach (var result in remoteOnlineClients.UidWithIdent) - { - RemoteCachedIdents[result.Uid.Uid] = result; - } - } - catch - { - SetGrpcFaulty(); - } - } - } - private async Task StreamOnlineClientData(CancellationToken cts) { try @@ -254,59 +195,49 @@ public class GrpcClientIdentificationService : IHostedService } } - private async Task InvokeOnGrpc(AsyncUnaryCall toExecute) + protected override Task StartAsyncInternal(CancellationToken cancellationToken) { - try - { - var result = await toExecute.ConfigureAwait(false); - - await CheckFaultStateAndResend().ConfigureAwait(false); - - return result; - } - catch - { - SetGrpcFaulty(); - - return default; - } + return Task.CompletedTask; } - private async Task ExecuteOnGrpc(AsyncUnaryCall toExecute) + protected override async Task StopAsyncInternal(CancellationToken cancellationToken) { - try - { - await toExecute.ConfigureAwait(false); - await CheckFaultStateAndResend().ConfigureAwait(false); - } - catch - { - SetGrpcFaulty(); - } + await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName })).ConfigureAwait(false); } - private async Task CheckFaultStateAndResend() + protected override async Task OnGrpcRestore() { - if (_grpcIsFaulty) + var msg = new ServerIdentMessage(); + msg.Idents.AddRange(OnlineClients.Select(c => new SetIdentMessage() { - await RestartStreams().ConfigureAwait(false); - var msg = new ServerIdentMessage(); - msg.Idents.AddRange(OnlineClients.Select(c => new SetIdentMessage() - { - UidWithIdent = c.Value - })); - await _grpcIdentClient.RecreateServerIdentsAsync(msg).ConfigureAwait(false); - _logger.LogInformation("GRPC connection is restored"); - _grpcIsFaulty = false; - } + UidWithIdent = c.Value + })); + await _grpcIdentClient.RecreateServerIdentsAsync(msg).ConfigureAwait(false); } - private void SetGrpcFaulty() + protected override async Task PreStartStream() { - if (!_grpcIsFaulty) + await _grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName }).ConfigureAwait(false); + + RemoteCachedIdents.Clear(); + } + + protected override Task StartStream(CancellationToken ct) + { + _ = StreamOnlineClientData(ct); + _ = ReceiveOnlineClientData(ct); + return Task.CompletedTask; + } + + protected override async Task PostStartStream() + { + var remoteOnlineClients = await _grpcIdentClient.GetAllIdentsAsync(new ServerMessage() { - _grpcIsFaulty = true; - _logger.LogWarning("GRPC connection is faulty"); + ServerId = _shardName + }).ConfigureAwait(false); + foreach (var result in remoteOnlineClients.UidWithIdent) + { + RemoteCachedIdents[result.Uid.Uid] = result; } } -} \ No newline at end of file +} diff --git a/MareSynchronosServer/MareSynchronosServer/Startup.cs b/MareSynchronosServer/MareSynchronosServer/Startup.cs index 04e91d3..9059a27 100644 --- a/MareSynchronosServer/MareSynchronosServer/Startup.cs +++ b/MareSynchronosServer/MareSynchronosServer/Startup.cs @@ -23,6 +23,7 @@ using MareSynchronosServer.Services; using System.Net.Http; using MareSynchronosServer.Utils; using MareSynchronosServer.RequirementHandlers; +using MareSynchronosShared.Services; namespace MareSynchronosServer; @@ -119,8 +120,10 @@ public class Startup }; }); + services.AddSingleton(); services.AddSingleton(); services.AddTransient(); + services.AddHostedService(p => p.GetService()); services.AddHostedService(p => p.GetService()); services.AddDbContextPool(options => diff --git a/MareSynchronosServer/MareSynchronosServices/Authentication/FailedAuthorization.cs b/MareSynchronosServer/MareSynchronosServices/Authentication/FailedAuthorization.cs index 404546d..54fd806 100644 --- a/MareSynchronosServer/MareSynchronosServices/Authentication/FailedAuthorization.cs +++ b/MareSynchronosServer/MareSynchronosServices/Authentication/FailedAuthorization.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; namespace MareSynchronosServices.Authentication; -public class FailedAuthorization : IDisposable +internal class FailedAuthorization : IDisposable { private int failedAttempts = 1; public int FailedAttempts => failedAttempts; diff --git a/MareSynchronosServer/MareSynchronosServices/Authentication/SecretKeyAuthenticationHandler.cs b/MareSynchronosServer/MareSynchronosServices/Authentication/SecretKeyAuthenticationHandler.cs index 34f26b9..01b8f95 100644 --- a/MareSynchronosServer/MareSynchronosServices/Authentication/SecretKeyAuthenticationHandler.cs +++ b/MareSynchronosServer/MareSynchronosServices/Authentication/SecretKeyAuthenticationHandler.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace MareSynchronosServices.Authentication; -public class SecretKeyAuthenticationHandler +internal class SecretKeyAuthenticationHandler { private readonly ILogger logger; private readonly MareMetrics metrics; @@ -60,7 +60,7 @@ public class SecretKeyAuthenticationHandler if (string.IsNullOrEmpty(secretKey)) { metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures); - return new AuthReply() { Success = false, Uid = string.Empty }; + return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } }; } lock (failedAuthLock) @@ -86,7 +86,7 @@ public class SecretKeyAuthenticationHandler }, token); logger.LogWarning("TempBan {ip} for authorization spam", ip); - return new AuthReply() { Success = false, Uid = string.Empty }; + return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } }; } } @@ -115,7 +115,7 @@ public class SecretKeyAuthenticationHandler } } - return new AuthReply() { Success = false, Uid = string.Empty }; + return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } }; } metrics.IncCounter(MetricsAPI.CounterAuthenticationCacheHits); @@ -152,7 +152,7 @@ public class SecretKeyAuthenticationHandler } metrics.IncCounter(MetricsAPI.CounterAuthenticationFailures); - return new AuthReply() { Success = false, Uid = string.Empty }; + return new AuthReply() { Success = false, Uid = new UidMessage() { Uid = string.Empty } }; } lock (authDictLock) @@ -163,7 +163,7 @@ public class SecretKeyAuthenticationHandler metrics.IncCounter(MetricsAPI.CounterAuthenticationSuccesses); - return new AuthReply() { Success = true, Uid = uid }; + return new AuthReply() { Success = true, Uid = new UidMessage() { Uid = uid } }; } public SecretKeyAuthenticationHandler(IConfiguration configuration, ILogger logger, MareMetrics metrics) diff --git a/MareSynchronosServer/MareSynchronosServices/CleanupService.cs b/MareSynchronosServer/MareSynchronosServices/CleanupService.cs index 25f1138..c33f149 100644 --- a/MareSynchronosServer/MareSynchronosServices/CleanupService.cs +++ b/MareSynchronosServer/MareSynchronosServices/CleanupService.cs @@ -16,7 +16,7 @@ using System.Threading.Tasks; namespace MareSynchronosServices; -public class CleanupService : IHostedService, IDisposable +internal class CleanupService : IHostedService, IDisposable { private readonly MareMetrics metrics; private readonly SecretKeyAuthenticationHandler _authService; diff --git a/MareSynchronosServer/MareSynchronosServices/Identity/IdentityHandler.cs b/MareSynchronosServer/MareSynchronosServices/Identity/IdentityHandler.cs index f3d6b35..a622d6b 100644 --- a/MareSynchronosServer/MareSynchronosServices/Identity/IdentityHandler.cs +++ b/MareSynchronosServer/MareSynchronosServices/Identity/IdentityHandler.cs @@ -1,6 +1,5 @@ using MareSynchronosShared.Protos; using Microsoft.Extensions.Logging; -using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -101,10 +100,10 @@ internal class IdentityHandler { identChanges[serverId] = new ConcurrentQueue(); } -} -internal record ServerIdentity -{ - public string ServerId { get; set; } = string.Empty; - public string CharacterIdent { get; set; } = string.Empty; + internal record ServerIdentity + { + public string ServerId { get; set; } = string.Empty; + public string CharacterIdent { get; set; } = string.Empty; + } } diff --git a/MareSynchronosServer/MareSynchronosServices/Services/AuthenticationService.cs b/MareSynchronosServer/MareSynchronosServices/Services/AuthenticationService.cs index a6956cf..d82b22e 100644 --- a/MareSynchronosServer/MareSynchronosServices/Services/AuthenticationService.cs +++ b/MareSynchronosServer/MareSynchronosServices/Services/AuthenticationService.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace MareSynchronosServices.Services; -public class AuthenticationService : AuthService.AuthServiceBase +internal class AuthenticationService : AuthService.AuthServiceBase { private readonly ILogger _logger; private readonly MareDbContext _dbContext; @@ -20,12 +20,16 @@ public class AuthenticationService : AuthService.AuthServiceBase _authHandler = authHandler; } - public override async Task Authorize(AuthRequest request, ServerCallContext context) + public override async Task Authorize(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) { - return await _authHandler.AuthenticateAsync(_dbContext, request.Ip, request.SecretKey); + await foreach (var input in requestStream.ReadAllAsync(context.CancellationToken).ConfigureAwait(false)) + { + var response = await _authHandler.AuthenticateAsync(_dbContext, input.Ip, input.SecretKey).ConfigureAwait(false); + await responseStream.WriteAsync(response, context.CancellationToken).ConfigureAwait(false); + } } - public override Task RemoveAuth(RemoveAuthRequest request, ServerCallContext context) + public override Task RemoveAuth(UidMessage request, ServerCallContext context) { _logger.LogInformation("Removing Authentication for {uid}", request.Uid); _authHandler.RemoveAuthentication(request.Uid); diff --git a/MareSynchronosServer/MareSynchronosShared/Authentication/SecretKeyGrpcAuthenticationHandler.cs b/MareSynchronosServer/MareSynchronosShared/Authentication/SecretKeyGrpcAuthenticationHandler.cs index 62f8333..719bccb 100644 --- a/MareSynchronosServer/MareSynchronosShared/Authentication/SecretKeyGrpcAuthenticationHandler.cs +++ b/MareSynchronosServer/MareSynchronosShared/Authentication/SecretKeyGrpcAuthenticationHandler.cs @@ -1,7 +1,7 @@ using System.Security.Claims; using System.Text.Encodings.Web; using MareSynchronosServer; -using MareSynchronosShared.Protos; +using MareSynchronosShared.Services; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -14,13 +14,13 @@ public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { - this._authClient = authClient; + this._grpcAuthService = authClient; _accessor = accessor; } @@ -33,7 +33,7 @@ public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler { - new(ClaimTypes.NameIdentifier, uid), + new(ClaimTypes.NameIdentifier, uid.Uid), new(ClaimTypes.Authentication, authHeader) }; diff --git a/MareSynchronosServer/MareSynchronosShared/Data/MareDbContext.cs b/MareSynchronosServer/MareSynchronosShared/Data/MareDbContext.cs index c15d4c3..d07e1dd 100644 --- a/MareSynchronosServer/MareSynchronosShared/Data/MareDbContext.cs +++ b/MareSynchronosServer/MareSynchronosShared/Data/MareDbContext.cs @@ -1,5 +1,4 @@ using MareSynchronosShared.Models; -using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace MareSynchronosShared.Data; diff --git a/MareSynchronosServer/MareSynchronosShared/Migrations/20221006122618_groupbans.cs b/MareSynchronosServer/MareSynchronosShared/Migrations/20221006122618_groupbans.cs index 8d0834b..84860eb 100644 --- a/MareSynchronosServer/MareSynchronosShared/Migrations/20221006122618_groupbans.cs +++ b/MareSynchronosServer/MareSynchronosShared/Migrations/20221006122618_groupbans.cs @@ -1,5 +1,4 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/MareSynchronosServer/MareSynchronosShared/Models/GroupBan.cs b/MareSynchronosServer/MareSynchronosShared/Models/GroupBan.cs index 0326bab..ad92530 100644 --- a/MareSynchronosServer/MareSynchronosShared/Models/GroupBan.cs +++ b/MareSynchronosServer/MareSynchronosShared/Models/GroupBan.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MareSynchronosShared.Models; +namespace MareSynchronosShared.Models; public class GroupBan { diff --git a/MareSynchronosServer/MareSynchronosShared/Protos/mareservices.proto b/MareSynchronosServer/MareSynchronosShared/Protos/mareservices.proto index 913a5e3..0f5b156 100644 --- a/MareSynchronosServer/MareSynchronosShared/Protos/mareservices.proto +++ b/MareSynchronosServer/MareSynchronosShared/Protos/mareservices.proto @@ -5,8 +5,8 @@ option csharp_namespace = "MareSynchronosShared.Protos"; package mareservices; service AuthService { - rpc Authorize (AuthRequest) returns (AuthReply); - rpc RemoveAuth (RemoveAuthRequest) returns (Empty); + rpc Authorize (stream AuthRequest) returns (stream AuthReply); + rpc RemoveAuth (UidMessage) returns (Empty); rpc ClearUnauthorized (Empty) returns (Empty); } @@ -101,10 +101,6 @@ message FileSizeResponse { map hashToFileSize = 1; } -message RemoveAuthRequest { - string uid = 1; -} - message AuthRequest { string ip = 1; string secretKey = 2; @@ -112,5 +108,5 @@ message AuthRequest { message AuthReply { bool success = 1; - string uid = 2; + UidMessage uid = 2; } \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosShared/Services/GrpcAuthenticationService.cs b/MareSynchronosServer/MareSynchronosShared/Services/GrpcAuthenticationService.cs new file mode 100644 index 0000000..8631346 --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Services/GrpcAuthenticationService.cs @@ -0,0 +1,109 @@ +using System.Collections.Concurrent; +using System.Security.Cryptography; +using MareSynchronosShared.Protos; +using Microsoft.Extensions.Logging; + +namespace MareSynchronosShared.Services; + +public class GrpcAuthenticationService : GrpcBaseService +{ + private record AuthRequestInternal + { + public AuthRequest Request { get; set; } + public long Id { get; set; } + } + + private readonly AuthService.AuthServiceClient _authClient; + private readonly ConcurrentQueue _requestQueue = new(); + private readonly ConcurrentDictionary _authReplies = new(); + private long _requestId = 0; + + public GrpcAuthenticationService(ILogger logger, AuthService.AuthServiceClient authClient) : base(logger) + { + _authClient = authClient; + } + + public async Task AuthorizeAsync(string ip, string secretKey) + { + using var sha1 = SHA1.Create(); + var id = Interlocked.Increment(ref _requestId); + _requestQueue.Enqueue(new AuthRequestInternal() + { + Id = id, + Request = new AuthRequest() + { + Ip = ip, + SecretKey = secretKey, + } + }); + + using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30)); + AuthReply response = null; + + while (!GrpcIsFaulty && !cts.IsCancellationRequested && !_authReplies.TryRemove(id, out response)) + { + await Task.Delay(10, cts.Token).ConfigureAwait(false); + } + + return response ?? new AuthReply + { + Success = false, + }; + } + + public async Task GrpcAuthStream(CancellationToken token) + { + try + { + using var stream = _authClient.Authorize(cancellationToken: token); + while (!token.IsCancellationRequested) + { + while (_requestQueue.TryDequeue(out var request)) + { + await stream.RequestStream.WriteAsync(request.Request, token).ConfigureAwait(false); + await stream.ResponseStream.MoveNext(token).ConfigureAwait(false); + _authReplies[request.Id] = stream.ResponseStream.Current; + } + + await Task.Delay(10, token).ConfigureAwait(false); + } + } + catch + { + SetGrpcFaulty(); + } + } + + protected override Task OnGrpcRestore() + { + return Task.CompletedTask; + } + + protected override Task PostStartStream() + { + return Task.CompletedTask; + } + + protected override Task PreStartStream() + { + _requestQueue.Clear(); + _authReplies.Clear(); + return Task.CompletedTask; + } + + protected override Task StartAsyncInternal(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + protected override Task StartStream(CancellationToken ct) + { + _ = GrpcAuthStream(ct); + return Task.CompletedTask; + } + + protected override Task StopAsyncInternal(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/MareSynchronosServer/MareSynchronosShared/Services/GrpcBaseService.cs b/MareSynchronosServer/MareSynchronosShared/Services/GrpcBaseService.cs new file mode 100644 index 0000000..cc081ea --- /dev/null +++ b/MareSynchronosServer/MareSynchronosShared/Services/GrpcBaseService.cs @@ -0,0 +1,129 @@ +using Grpc.Core; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace MareSynchronosShared.Services; + +public abstract class GrpcBaseService : IHostedService, IDisposable +{ + protected GrpcBaseService(ILogger logger) + { + _logger = logger; + } + + private CancellationTokenSource _faultCheckCts = new(); + private CancellationTokenSource _streamCts = new(); + private readonly ILogger _logger; + protected bool GrpcIsFaulty { get; private set; } + + protected abstract Task StartAsyncInternal(CancellationToken cancellationToken); + protected abstract Task StopAsyncInternal(CancellationToken cancellationToken); + protected abstract Task OnGrpcRestore(); + protected abstract Task PreStartStream(); + protected abstract Task StartStream(CancellationToken ct); + protected abstract Task PostStartStream(); + + public async Task StartAsync(CancellationToken cancellationToken) + { + _ = RestartStreams(); + _ = CheckGrpcFaults(_faultCheckCts.Token); + await StartAsyncInternal(cancellationToken).ConfigureAwait(false); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + _faultCheckCts.Cancel(); + _streamCts.Cancel(); + await StopAsyncInternal(cancellationToken).ConfigureAwait(false); + } + + private async Task RestartStreams() + { + _streamCts?.Cancel(); + _streamCts?.Dispose(); + _streamCts = new(); + if (!GrpcIsFaulty) + { + try + { + await PreStartStream().ConfigureAwait(false); + await StartStream(_streamCts.Token).ConfigureAwait(false); + await PostStartStream().ConfigureAwait(false); + } + catch + { + SetGrpcFaulty(); + } + } + } + + protected void SetGrpcFaulty() + { + if (!GrpcIsFaulty) + { + GrpcIsFaulty = true; + _logger.LogWarning("GRPC connection is faulty"); + } + } + + private async Task CheckGrpcFaults(CancellationToken ct) + { + while (!ct.IsCancellationRequested) + { + try + { + await CheckFaultStateAndResend().ConfigureAwait(false); + } + catch { SetGrpcFaulty(); } + await Task.Delay(250).ConfigureAwait(false); + } + } + + private async Task CheckFaultStateAndResend() + { + if (GrpcIsFaulty) + { + await RestartStreams().ConfigureAwait(false); + await OnGrpcRestore().ConfigureAwait(false); + _logger.LogInformation("GRPC connection is restored"); + GrpcIsFaulty = false; + } + } + + protected async Task InvokeOnGrpc(AsyncUnaryCall toExecute) + { + try + { + var result = await toExecute.ConfigureAwait(false); + + await CheckFaultStateAndResend().ConfigureAwait(false); + + return result; + } + catch + { + SetGrpcFaulty(); + + return default; + } + } + + protected async Task ExecuteOnGrpc(AsyncUnaryCall toExecute) + { + try + { + await toExecute.ConfigureAwait(false); + await CheckFaultStateAndResend().ConfigureAwait(false); + } + catch + { + SetGrpcFaulty(); + } + } + + public void Dispose() + { + _streamCts?.Dispose(); + _faultCheckCts?.Dispose(); + } +} \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosShared/Utils/SharedDbFunctions.cs b/MareSynchronosServer/MareSynchronosShared/Utils/SharedDbFunctions.cs index 6597fe2..7b90933 100644 --- a/MareSynchronosServer/MareSynchronosShared/Utils/SharedDbFunctions.cs +++ b/MareSynchronosServer/MareSynchronosShared/Utils/SharedDbFunctions.cs @@ -1,11 +1,6 @@ using MareSynchronosShared.Data; using MareSynchronosShared.Models; using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MareSynchronosShared.Utils; diff --git a/MareSynchronosServer/MareSynchronosStaticFilesServer/MareSynchronosStaticFilesServer.csproj b/MareSynchronosServer/MareSynchronosStaticFilesServer/MareSynchronosStaticFilesServer.csproj index 43e5401..9f25820 100644 --- a/MareSynchronosServer/MareSynchronosStaticFilesServer/MareSynchronosStaticFilesServer.csproj +++ b/MareSynchronosServer/MareSynchronosStaticFilesServer/MareSynchronosStaticFilesServer.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 diff --git a/MareSynchronosServer/MareSynchronosStaticFilesServer/Startup.cs b/MareSynchronosServer/MareSynchronosStaticFilesServer/Startup.cs index 5abe282..307da72 100644 --- a/MareSynchronosServer/MareSynchronosStaticFilesServer/Startup.cs +++ b/MareSynchronosServer/MareSynchronosStaticFilesServer/Startup.cs @@ -3,6 +3,7 @@ using MareSynchronosShared.Authentication; using MareSynchronosShared.Data; using MareSynchronosShared.Metrics; using MareSynchronosShared.Protos; +using MareSynchronosShared.Services; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; @@ -47,6 +48,7 @@ public class Startup } }; + services.AddSingleton(); services.AddSingleton(new MareMetrics(new List { }, new List { @@ -72,6 +74,7 @@ public class Startup }, mareSettings.GetValue("DbContextPoolSize", 1024)); services.AddHostedService(); + services.AddHostedService(p => p.GetService()); services.AddAuthentication(options => {