From 1d3808d53575a29dbeb3cb92120aa24e81939bf0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 11 Jun 2024 10:55:09 -0700 Subject: [PATCH 1/3] Change AsyncLazy from NonReentrantLock to SemaphoreSlim --- .../Compiler/Core/Utilities/AsyncLazy`1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy`1.cs index 43feb207f76a..1f190a7adfce 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy`1.cs @@ -66,7 +66,7 @@ private sealed class AsyncLazyImpl : AsyncLazy /// by using a single lock for all AsyncLazy instances. Only trivial and non-reentrant work /// should be done while holding the lock. /// - private static readonly NonReentrantLock s_gate = new(useThisInstanceForSynchronization: true); + private static readonly SemaphoreSlim s_gate = new(initialCount: 1); /// /// The hash set of all currently outstanding asynchronous requests. Null if there are no requests, From 4e72da1ecb6701b1f9de1e7cc58a1902962ccd46 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 11 Jun 2024 12:58:33 -0700 Subject: [PATCH 2/3] Switch to objecT --- .../Compiler/Core/Utilities/AsyncLazy`1.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy`1.cs index 1f190a7adfce..166bf7ca400a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy`1.cs @@ -61,12 +61,12 @@ private sealed class AsyncLazyImpl : AsyncLazy private Task? _cachedResult; /// - /// Mutex used to protect reading and writing to all mutable objects and fields. Traces - /// indicate that there's negligible contention on this lock, hence we can save some memory - /// by using a single lock for all AsyncLazy instances. Only trivial and non-reentrant work - /// should be done while holding the lock. + /// Mutex used to protect reading and writing to all mutable objects and fields. Traces indicate that there's + /// negligible contention on this lock (and on any particular async-lazy in general), hence we can save some + /// memory by using ourselves as the lock, even though this may inhibit cancellation. Work done while holding + /// the lock should be kept to a minimum. /// - private static readonly SemaphoreSlim s_gate = new(initialCount: 1); + private object SyncObject => this; /// /// The hash set of all currently outstanding asynchronous requests. Null if there are no requests, @@ -136,7 +136,10 @@ public static AsyncLazy CreateImpl( /// private WaitThatValidatesInvariants TakeLock(CancellationToken cancellationToken) { - s_gate.Wait(cancellationToken); + Contract.ThrowIfTrue(Monitor.IsEntered(SyncObject)); + + cancellationToken.ThrowIfCancellationRequested(); + Monitor.Enter(SyncObject); AssertInvariants_NoLock(); return new WaitThatValidatesInvariants(this); } @@ -146,7 +149,8 @@ private readonly struct WaitThatValidatesInvariants(AsyncLazyImpl asyncLa public void Dispose() { asyncLazy.AssertInvariants_NoLock(); - s_gate.Release(); + Contract.ThrowIfFalse(Monitor.IsEntered(asyncLazy.SyncObject)); + Monitor.Exit(asyncLazy.SyncObject); } } From f86796c6ce37d10ad49aa3b3c42e6b5ca7343ab8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 11 Jun 2024 12:59:33 -0700 Subject: [PATCH 3/3] Docs --- .../Compiler/Core/Utilities/AsyncLazy`1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy`1.cs index 166bf7ca400a..d1443088d902 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy`1.cs @@ -136,7 +136,7 @@ public static AsyncLazy CreateImpl( /// private WaitThatValidatesInvariants TakeLock(CancellationToken cancellationToken) { - Contract.ThrowIfTrue(Monitor.IsEntered(SyncObject)); + Contract.ThrowIfTrue(Monitor.IsEntered(SyncObject), "Attempt to take the lock while already holding it!"); cancellationToken.ThrowIfCancellationRequested(); Monitor.Enter(SyncObject);