From 46b079d8456bf219929c43fb6c4f9bad8c1a6297 Mon Sep 17 00:00:00 2001 From: Lifeng Lu Date: Thu, 6 Mar 2025 10:00:58 -0800 Subject: [PATCH] Fix a race condition which leads product to hang the order to read this.joinableTask is not ensured in the current lock-free part of the logic. Under a race condition, this leads AsyncLazy to wait the inner task instead of the joinable task when two threads hitting this code about the same time. --- src/Microsoft.VisualStudio.Threading/AsyncLazy`1.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Microsoft.VisualStudio.Threading/AsyncLazy`1.cs b/src/Microsoft.VisualStudio.Threading/AsyncLazy`1.cs index 013f96fa2..1f9c3ae6f 100644 --- a/src/Microsoft.VisualStudio.Threading/AsyncLazy`1.cs +++ b/src/Microsoft.VisualStudio.Threading/AsyncLazy`1.cs @@ -224,6 +224,10 @@ public Task GetValueAsync(CancellationToken cancellationToken) // to synchronously block the Main thread waiting for the result // without leading to deadlocks. this.joinableTask = this.jobFactory.RunAsync(valueFactory); + + // this ensures that this.joinableTask must be committed before this.value + Thread.MemoryBarrier(); + this.value = this.joinableTask.Task; } else @@ -245,6 +249,9 @@ public Task GetValueAsync(CancellationToken cancellationToken) resumableAwaiter?.Resume(); } + // this ensures that this.joinableTask cannot be retrieved before the conditional check using this.value + Thread.MemoryBarrier(); + return this.joinableTask?.JoinAsync(continueOnCapturedContext: false, cancellationToken) ?? this.value.WithCancellation(cancellationToken); }