From f69e6289b538742f4622d6b1bc483f64cb683d19 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Tue, 22 Aug 2023 18:59:48 +0100 Subject: [PATCH] Handle re-entrant RunUnsynced correctly. If nested calls to RunUnsynced are running, then using a bool would cause the flag to be reset once the inner function completes, but an outer function may still be running and not yet ready for the flag to be reset. To correctly handle nested calls, we track a count and only reset the flag once all functions have completed. --- OpenRA.Game/Sync.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/OpenRA.Game/Sync.cs b/OpenRA.Game/Sync.cs index 06e2e9b5fd..b93166fa49 100644 --- a/OpenRA.Game/Sync.cs +++ b/OpenRA.Game/Sync.cs @@ -172,7 +172,7 @@ namespace OpenRA RunUnsynced(checkSyncHash, world, () => { fn(); return true; }); } - static bool inUnsyncedCode = false; + static int unsyncCount = 0; public static T RunUnsynced(World world, Func fn) { @@ -181,32 +181,30 @@ namespace OpenRA public static T RunUnsynced(bool checkSyncHash, World world, Func fn) { - // PERF: Detect sync changes in top level entry point only. Do not recalculate sync hash during reentry. - if (inUnsyncedCode || world == null) - return fn(); + unsyncCount++; - var sync = checkSyncHash ? world.SyncHash() : 0; - inUnsyncedCode = true; + // Detect sync changes in top level entry point only. Do not recalculate sync hash during reentry. + var sync = unsyncCount == 1 && checkSyncHash && world != null ? world.SyncHash() : 0; - // Running this inside a try with a finally statement means isUnsyncedCode is set to false again as soon as fn completes + // Running this inside a try with a finally statement means unsyncCount is decremented as soon as fn completes try { return fn(); } finally { - inUnsyncedCode = false; + unsyncCount--; // When the world is disposing all actors and effects have been removed // So do not check the hash for a disposing world since it definitively has changed - if (checkSyncHash && !world.Disposing && sync != world.SyncHash()) + if (unsyncCount == 0 && checkSyncHash && world != null && !world.Disposing && sync != world.SyncHash()) throw new InvalidOperationException("RunUnsynced: sync-changing code may not run here"); } } public static void AssertUnsynced(string message) { - if (!inUnsyncedCode) + if (unsyncCount == 0) throw new InvalidOperationException(message); } }