From 52fc12b7a4775e50ce9447c96cc6844c4df58171 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 6 Nov 2023 16:19:24 -0500 Subject: [PATCH 1/7] Fix `JobsHubGroupMapper` not being initialized --- src/Tgstation.Server.Host/Core/Application.cs | 4 +++- src/Tgstation.Server.Host/Jobs/JobsHubGroupMapper.cs | 9 ++++++++- .../Utils/SignalR/ComprehensiveHubContext.cs | 5 +++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index a331feeb9f..a1cdabec96 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -404,7 +404,9 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(provider => provider.GetRequiredService()); services.AddSingleton(provider => provider.GetRequiredService()); services.AddSingleton(x => x.GetRequiredService()); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(provider => provider.GetRequiredService()); + services.AddSingleton(x => x.GetRequiredService()); // bit of a hack, but we need this to load immediated services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); diff --git a/src/Tgstation.Server.Host/Jobs/JobsHubGroupMapper.cs b/src/Tgstation.Server.Host/Jobs/JobsHubGroupMapper.cs index 77ccf99b0b..719138e6ad 100644 --- a/src/Tgstation.Server.Host/Jobs/JobsHubGroupMapper.cs +++ b/src/Tgstation.Server.Host/Jobs/JobsHubGroupMapper.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Tgstation.Server.Api.Hubs; @@ -19,7 +20,7 @@ namespace Tgstation.Server.Host.Jobs /// /// Handles mapping groups for the . /// - sealed class JobsHubGroupMapper : IPermissionsUpdateNotifyee + sealed class JobsHubGroupMapper : IPermissionsUpdateNotifyee, IHostedService { /// /// The for the . @@ -96,6 +97,12 @@ namespace Tgstation.Server.Host.Jobs cancellationToken); } + /// + public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + /// /// Implementation of . /// diff --git a/src/Tgstation.Server.Host/Utils/SignalR/ComprehensiveHubContext.cs b/src/Tgstation.Server.Host/Utils/SignalR/ComprehensiveHubContext.cs index 7999908052..3892aad56c 100644 --- a/src/Tgstation.Server.Host/Utils/SignalR/ComprehensiveHubContext.cs +++ b/src/Tgstation.Server.Host/Utils/SignalR/ComprehensiveHubContext.cs @@ -83,12 +83,13 @@ namespace Tgstation.Server.Host.Utils.SignalR userId, context.ConnectionId); - var mappingTask = OnConnectionMapGroups( + var mappingTask = OnConnectionMapGroups?.Invoke( authenticationContext, mappedGroups => Task.WhenAll( mappedGroups.Select( group => hub.Groups.AddToGroupAsync(context.ConnectionId, group, cancellationToken))), - cancellationToken); + cancellationToken) + ?? ValueTask.CompletedTask; userConnections.AddOrUpdate( userId, _ => new Dictionary From 9bea7686b28b455a69b29f1592300773e498e5e3 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 6 Nov 2023 16:39:23 -0500 Subject: [PATCH 2/7] Add automatic reconnect to client hubs --- build/Version.props | 2 +- src/Tgstation.Server.Client/ApiClient.cs | 4 +- .../ApiClientTokenRefreshRetryPolicy.cs | 54 +++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/Tgstation.Server.Client/ApiClientTokenRefreshRetryPolicy.cs diff --git a/build/Version.props b/build/Version.props index f63dc186b2..99f5a049ee 100644 --- a/build/Version.props +++ b/build/Version.props @@ -8,7 +8,7 @@ 9.13.0 7.0.0 12.0.0 - 14.0.0 + 14.1.0 6.6.2 5.6.2 1.4.0 diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index ed48bc7bc7..f4a328d285 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -372,13 +372,15 @@ namespace Tgstation.Server.Client retryPolicy ??= new InfiniteThirtySecondMaxRetryPolicy(); + var wrappedPolicy = new ApiClientTokenRefreshRetryPolicy(this, retryPolicy); + HubConnection? hubConnection = null; var hubConnectionBuilder = new HubConnectionBuilder() .AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings = SerializerSettings; }) - .WithAutomaticReconnect(retryPolicy) + .WithAutomaticReconnect(wrappedPolicy) .WithUrl( new Uri(Url, Routes.JobsHub), HttpTransportType.ServerSentEvents, diff --git a/src/Tgstation.Server.Client/ApiClientTokenRefreshRetryPolicy.cs b/src/Tgstation.Server.Client/ApiClientTokenRefreshRetryPolicy.cs new file mode 100644 index 0000000000..3ec5c1f6a1 --- /dev/null +++ b/src/Tgstation.Server.Client/ApiClientTokenRefreshRetryPolicy.cs @@ -0,0 +1,54 @@ +using System; +using System.Threading; + +using Microsoft.AspNetCore.SignalR.Client; + +namespace Tgstation.Server.Client +{ + /// + /// A that attempts to refresh a given 's token on the first disconnect. + /// + sealed class ApiClientTokenRefreshRetryPolicy : IRetryPolicy + { + /// + /// The backing . + /// + readonly ApiClient apiClient; + + /// + /// The wrapped . + /// + readonly IRetryPolicy wrappedPolicy; + + /// + /// Initializes a new instance of the class. + /// + /// The value of . + /// The value of . + public ApiClientTokenRefreshRetryPolicy(ApiClient apiClient, IRetryPolicy wrappedPolicy) + { + this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient)); + this.wrappedPolicy = wrappedPolicy ?? throw new ArgumentNullException(nameof(wrappedPolicy)); + } + + /// + public TimeSpan? NextRetryDelay(RetryContext retryContext) + { + if (retryContext == null) + throw new ArgumentNullException(nameof(retryContext)); + + if (retryContext.PreviousRetryCount == 0) + AttemptTokenRefresh(); + + return wrappedPolicy.NextRetryDelay(retryContext); + } + + /// + /// Attempt to refresh the s token asynchronously. + /// + async void AttemptTokenRefresh() + { + await apiClient.RefreshToken(CancellationToken.None); + } + } +} From abd1948d42efcd967ce6bc921735bf5a64aeb6fd Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 6 Nov 2023 16:39:51 -0500 Subject: [PATCH 3/7] Version bump to 5.17.1 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index 99f5a049ee..2ca5699ebf 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ - 5.17.0 + 5.17.1 4.7.1 9.13.0 7.0.0 From 37adb228d03a1e38399f11688898409cbba7f06a Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 6 Nov 2023 16:56:30 -0500 Subject: [PATCH 4/7] Bump webpanel version to latest --- build/ControlPanelVersion.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ControlPanelVersion.props b/build/ControlPanelVersion.props index f6f147afdc..02d75e7142 100644 --- a/build/ControlPanelVersion.props +++ b/build/ControlPanelVersion.props @@ -1,6 +1,6 @@ - 4.26.0 + 4.26.2 From 549b65f03c15e16ca3102b58cff3ef4ed4eef497 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 6 Nov 2023 16:57:24 -0500 Subject: [PATCH 5/7] Update nuget packages for API library --- build/Version.props | 2 +- src/Tgstation.Server.Api/Tgstation.Server.Api.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/Version.props b/build/Version.props index 2ca5699ebf..ae1f43929d 100644 --- a/build/Version.props +++ b/build/Version.props @@ -7,7 +7,7 @@ 4.7.1 9.13.0 7.0.0 - 12.0.0 + 12.0.1 14.1.0 6.6.2 5.6.2 diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 3573822c52..e499846add 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -27,7 +27,7 @@ - + From 72388f8035b3abc9f84eec1c1cdc9e6cc9840a35 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 6 Nov 2023 17:44:45 -0500 Subject: [PATCH 6/7] Add necessary `ObjectDisposedException` to client --- src/Tgstation.Server.Client/ApiClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index f4a328d285..bf0934a48b 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -482,6 +482,9 @@ namespace Tgstation.Server.Client if (content == null && (method == HttpMethod.Post || method == HttpMethod.Put)) throw new InvalidOperationException("content cannot be null for POST or PUT!"); + if (disposed) + throw new ObjectDisposedException(nameof(ApiClient)); + HttpResponseMessage response; var fullUri = new Uri(Url, route); var serializerSettings = SerializerSettings; From e68dc5b307d0436c917e0fbcd7d1dfe493fba1cd Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 6 Nov 2023 17:46:05 -0500 Subject: [PATCH 7/7] An uncaught exception was never a good idea --- .../ApiClientTokenRefreshRetryPolicy.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Client/ApiClientTokenRefreshRetryPolicy.cs b/src/Tgstation.Server.Client/ApiClientTokenRefreshRetryPolicy.cs index 3ec5c1f6a1..34556c24ec 100644 --- a/src/Tgstation.Server.Client/ApiClientTokenRefreshRetryPolicy.cs +++ b/src/Tgstation.Server.Client/ApiClientTokenRefreshRetryPolicy.cs @@ -48,7 +48,14 @@ namespace Tgstation.Server.Client /// async void AttemptTokenRefresh() { - await apiClient.RefreshToken(CancellationToken.None); + try + { + await apiClient.RefreshToken(CancellationToken.None); + } + catch + { + // intentionally ignored + } } } }