From bef4659387940ef14a6cbd15ad183a431cb5490e Mon Sep 17 00:00:00 2001 From: mcocdaa <2021137961@qq.com> Date: Sat, 7 Nov 2026 04:56:48 +0100 Subject: [PATCH] fix(advisor): use ConcurrentDictionary in ApprovalManager and serialize runtime tests fix(advisor): use ConcurrentDictionary for pending approval requests Replace the static Dictionary with ConcurrentDictionary and use TryRemove to make pending request bookkeeping thread-safe, since callbacks and cancellation paths may run on different threads. Also tag ColonyMacroPerceiverTests with the "Advisor runtime" collection to avoid parallel test interference. --- Source/Advisor/ApprovalManager.cs | 17 +++++++++-------- Tests/Contracts/ColonyMacroPerceiverTests.cs | 1 + 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Source/Advisor/ApprovalManager.cs b/Source/Advisor/ApprovalManager.cs index 0334b16..613a358 100644 --- a/Source/Advisor/ApprovalManager.cs +++ b/Source/Advisor/ApprovalManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Text; using RimMind.Advisor.Settings; @@ -19,7 +20,7 @@ public class CabinetProposal public class ApprovalManager { - private static readonly Dictionary _pendingRequests = new Dictionary(); + private static readonly ConcurrentDictionary _pendingRequests = new ConcurrentDictionary(); private readonly RimMindAdvisorSettings _settings; public ApprovalManager(RimMindAdvisorSettings settings) @@ -68,7 +69,7 @@ public static void CancelPendingForPawn(Pawn? pawn) foreach (var entry in toCancel) { - _pendingRequests.Remove(entry); + _pendingRequests.TryRemove(entry, out _); try { if (!RimMindAPI.DismissPendingRequest(entry)) @@ -124,7 +125,7 @@ public RequestEntry SubmitForApproval( options = new[] { approveLabel, rejectLabel }, callback = choice => { - if (entryRef != null) _pendingRequests.Remove(entryRef); + if (entryRef != null) _pendingRequests.TryRemove(entryRef, out _); if (choice == approveLabel) { onApproved(); @@ -136,7 +137,7 @@ public RequestEntry SubmitForApproval( }, completionCallback = completionReason => { - if (entryRef != null) _pendingRequests.Remove(entryRef); + if (entryRef != null) _pendingRequests.TryRemove(entryRef, out _); if (completionReason == RequestCompletionReason.Selected) return; @@ -159,7 +160,7 @@ public RequestEntry SubmitForApproval( } catch { - _pendingRequests.Remove(entry); + _pendingRequests.TryRemove(entry, out _); try { if (!RimMindAPI.DismissPendingRequest(entry)) @@ -233,7 +234,7 @@ public RequestEntry SubmitCabinetProposal( options = new[] { approveAllLabel, dismissLabel }, callback = choice => { - if (entryRef != null) _pendingRequests.Remove(entryRef); + if (entryRef != null) _pendingRequests.TryRemove(entryRef, out _); if (choice == approveAllLabel) { foreach (var cb in proposal.ExecutionCallbacks) @@ -256,7 +257,7 @@ public RequestEntry SubmitCabinetProposal( }, completionCallback = completionReason => { - if (entryRef != null) _pendingRequests.Remove(entryRef); + if (entryRef != null) _pendingRequests.TryRemove(entryRef, out _); if (completionReason == RequestCompletionReason.Selected) return; @@ -279,7 +280,7 @@ public RequestEntry SubmitCabinetProposal( } catch { - _pendingRequests.Remove(entry); + _pendingRequests.TryRemove(entry, out _); try { if (!RimMindAPI.DismissPendingRequest(entry)) diff --git a/Tests/Contracts/ColonyMacroPerceiverTests.cs b/Tests/Contracts/ColonyMacroPerceiverTests.cs index 6e67a6f..09974b3 100644 --- a/Tests/Contracts/ColonyMacroPerceiverTests.cs +++ b/Tests/Contracts/ColonyMacroPerceiverTests.cs @@ -10,6 +10,7 @@ namespace RimMind.Advisor.Tests.Contracts { + [Collection("Advisor runtime")] public class ColonyMacroPerceiverTests { [Fact]