From 23feb78f051334fc24e116b15d5551e9225c7eda Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Mon, 21 Aug 2023 22:51:33 +0800 Subject: [PATCH] fix user collection initialization race condition --- .../Snap.Hutao/Control/BoxedValues.cs | 15 -------- .../Snap.Hutao/Core/RuntimeOptions.cs | 2 -- .../Snap.Hutao/Core/Threading/Throttler.cs | 19 ++++++++++ src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs | 11 ++++++ .../Snap.Hutao/Service/User/UserService.cs | 28 ++++++++------- .../LaunchGamePackageConvertDialog.xaml | 5 +-- .../Web/Hutao/HomaGachaLogClient.cs | 36 ++++++++++--------- .../Web/Hutao/HomaPassportClient.cs | 4 +-- 8 files changed, 68 insertions(+), 52 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Threading/Throttler.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Control/BoxedValues.cs b/src/Snap.Hutao/Snap.Hutao/Control/BoxedValues.cs index f705497a..15e7d2ba 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/BoxedValues.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/BoxedValues.cs @@ -9,21 +9,6 @@ namespace Snap.Hutao.Control; [HighQuality] internal static class BoxedValues { - /// - /// 0 - /// - public static readonly object DoubleZero = 0D; - - /// - /// 1 - /// - public static readonly object DoubleOne = 1D; - - /// - /// 0 - /// - public static readonly object Int32Zero = 0; - /// /// /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptions.cs b/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptions.cs index 30aa3f95..3ae76131 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptions.cs @@ -131,8 +131,6 @@ internal sealed class RuntimeOptions : IOptions private static bool GetElevated() { - return true; - using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) { WindowsPrincipal principal = new(identity); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/Throttler.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/Throttler.cs new file mode 100644 index 00000000..1b12e79e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/Throttler.cs @@ -0,0 +1,19 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; + +namespace Snap.Hutao.Core.Threading; + +internal sealed class Throttler +{ + private readonly ConcurrentDictionary methodSemaphoreMap = new(); + + public ValueTask ThrottleAsync(CancellationToken token = default, [CallerMemberName] string callerName = default!, [CallerLineNumber] int callerLine = 0) + { + string key = $"{callerName}L{callerLine}"; + SemaphoreSlim semaphore = methodSemaphoreMap.GetOrAdd(key, name => new SemaphoreSlim(1)); + return semaphore.EnterAsync(token); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs index 35186dab..a2be57a4 100644 --- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs @@ -6,6 +6,7 @@ using Microsoft.UI.Xaml; using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Windowing; using Snap.Hutao.Message; +using Windows.Foundation; using Windows.Win32.UI.WindowsAndMessaging; namespace Snap.Hutao; @@ -22,6 +23,8 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IRecipi private const int MinHeight = 524; private readonly WindowOptions windowOptions; + private readonly ILogger logger; + private readonly TypedEventHandler closedEventHander; /// /// 构造一个新的主窗体 @@ -33,9 +36,12 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IRecipi windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true); ExtendedWindow.Initialize(this, serviceProvider); serviceProvider.GetRequiredService().Register(this); + logger = serviceProvider.GetRequiredService>(); // If not complete we should present the welcome view. ContentSwitchPresenter.Value = StaticResource.IsAnyUnfulfilledContractPresent(); + closedEventHander = OnClosed; + Closed += closedEventHander; } /// @@ -53,4 +59,9 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource, IRecipi { ContentSwitchPresenter.Value = false; } + + private void OnClosed(object sender, WindowEventArgs args) + { + logger.LogInformation("MainWindow Closed"); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index d2b81738..2d05c00f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -25,6 +25,8 @@ namespace Snap.Hutao.Service.User; [Injection(InjectAs.Singleton, typeof(IUserService))] internal sealed partial class UserService : IUserService, IUserServiceUnsafe { + private readonly Throttler throttler = new(); + private readonly ScopedDbCurrent dbCurrent; private readonly IUserInitializationService userInitializationService; private readonly IServiceProvider serviceProvider; @@ -67,20 +69,22 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe /// public async ValueTask> GetUserCollectionAsync() { - await taskContext.SwitchToBackgroundAsync(); - if (userCollection is null) + using (await throttler.ThrottleAsync().ConfigureAwait(false)) { - List entities = await userDbService.GetUserListAsync().ConfigureAwait(false); - List users = await entities.SelectListAsync(userInitializationService.ResumeUserAsync, default).ConfigureAwait(false); - userCollection = users.ToObservableCollection(); + if (userCollection is null) + { + List entities = await userDbService.GetUserListAsync().ConfigureAwait(false); + List users = await entities.SelectListAsync(userInitializationService.ResumeUserAsync, default).ConfigureAwait(false); + userCollection = users.ToObservableCollection(); - try - { - Current = users.SelectedOrDefault(); - } - catch (InvalidOperationException ex) - { - ThrowHelper.UserdataCorrupted(SH.ServiceUserCurrentMultiMatched, ex); + try + { + Current = users.SelectedOrDefault(); + } + catch (InvalidOperationException ex) + { + ThrowHelper.UserdataCorrupted(SH.ServiceUserCurrentMultiMatched, ex); + } } } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml index 9118449b..38fcd88d 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/LaunchGamePackageConvertDialog.xaml @@ -1,7 +1,4 @@ - - - - /// 胡桃祈愿记录API客户端 /// +[ConstructorGenerated(ResolveHttpClient = true)] [HttpClient(HttpClientConfiguration.Default)] -internal sealed class HomaGachaLogClient +internal sealed partial class HomaGachaLogClient { private readonly HttpClient httpClient; private readonly JsonSerializerOptions options; private readonly ILogger logger; - - /// - /// 构造一个新的胡桃祈愿记录API客户端 - /// - /// http客户端 - /// 服务提供器 - public HomaGachaLogClient(HttpClient httpClient, IServiceProvider serviceProvider) - { - options = serviceProvider.GetRequiredService(); - logger = serviceProvider.GetRequiredService>(); - - this.httpClient = httpClient; - - HutaoUserOptions hutaoUserOptions = serviceProvider.GetRequiredService(); - httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token); - } + private readonly HutaoUserOptions hutaoUserOptions; /// /// 异步获取祈愿统计信息 @@ -42,6 +28,8 @@ internal sealed class HomaGachaLogClient /// 祈愿统计信息 public async ValueTask> GetGachaEventStatisticsAsync(CancellationToken token = default) { + httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token); + Response? resp = await httpClient .TryCatchGetFromJsonAsync>(HutaoEndpoints.GachaLogStatisticsCurrentEvents, options, logger, token) .ConfigureAwait(false); @@ -57,6 +45,8 @@ internal sealed class HomaGachaLogClient /// 祈愿分布 public async ValueTask> GetGachaDistributionAsync(GachaDistributionType distributionType, CancellationToken token = default) { + httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token); + Response? resp = await httpClient .TryCatchGetFromJsonAsync>(HutaoEndpoints.GachaLogStatisticsDistribution(distributionType), options, logger, token) .ConfigureAwait(false); @@ -72,6 +62,8 @@ internal sealed class HomaGachaLogClient [Obsolete("Use GetGachaEntriesAsync instead")] public async ValueTask>> GetUidsAsync(CancellationToken token = default) { + httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token); + Response>? resp = await httpClient .TryCatchGetFromJsonAsync>>(HutaoEndpoints.GachaLogUids, options, logger, token) .ConfigureAwait(false); @@ -86,6 +78,8 @@ internal sealed class HomaGachaLogClient /// Uid 列表 public async ValueTask>> GetGachaEntriesAsync(CancellationToken token = default) { + httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token); + Response>? resp = await httpClient .TryCatchGetFromJsonAsync>>(HutaoEndpoints.GachaLogEntries, options, logger, token) .ConfigureAwait(false); @@ -101,6 +95,8 @@ internal sealed class HomaGachaLogClient /// 末尾Id public async ValueTask> GetEndIdsAsync(string uid, CancellationToken token = default) { + httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token); + Response? resp = await httpClient .TryCatchGetFromJsonAsync>(HutaoEndpoints.GachaLogEndIds(uid), options, logger, token) .ConfigureAwait(false); @@ -117,6 +113,8 @@ internal sealed class HomaGachaLogClient /// 云端祈愿记录 public async ValueTask>> RetrieveGachaItemsAsync(string uid, EndIds endIds, CancellationToken token = default) { + httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token); + UidAndEndIds uidAndEndIds = new(uid, endIds); Response>? resp = await httpClient @@ -135,6 +133,8 @@ internal sealed class HomaGachaLogClient /// 响应 public async ValueTask UploadGachaItemsAsync(string uid, List gachaItems, CancellationToken token = default) { + httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token); + UidAndItems uidAndItems = new(uid, gachaItems); Response.Response? resp = await httpClient @@ -152,6 +152,8 @@ internal sealed class HomaGachaLogClient /// 响应 public async ValueTask DeleteGachaItemsAsync(string uid, CancellationToken token = default) { + httpClient.DefaultRequestHeaders.Authorization = new("Bearer", hutaoUserOptions.Token); + Response.Response? resp = await httpClient .TryCatchGetFromJsonAsync>>(HutaoEndpoints.GachaLogDelete(uid), options, logger, token) .ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaPassportClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaPassportClient.cs index d572bc6c..91ba4658 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaPassportClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaPassportClient.cs @@ -151,10 +151,10 @@ internal sealed partial class HomaPassportClient private static string Encrypt(string text) { byte[] plaintextBytes = Encoding.UTF8.GetBytes(text); - using (RSACryptoServiceProvider rsa = new(2048)) + using (RSA rsa = RSA.Create(2048)) { rsa.ImportFromPem(PublicKey); - byte[] encryptedBytes = rsa.Encrypt(plaintextBytes, true); + byte[] encryptedBytes = rsa.Encrypt(plaintextBytes, RSAEncryptionPadding.OaepSHA1); return Convert.ToBase64String(encryptedBytes); } }