Skip to content

Commit a327c20

Browse files
Use ConfigureAwait(true) when in a blocking JTF run call (#79456)
2 parents 8487180 + a08551e commit a327c20

File tree

2 files changed

+47
-21
lines changed

2 files changed

+47
-21
lines changed

src/VisualStudio/Core/Def/Extensions/VsTextSpanExtensions.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Threading;
66
using System.Threading.Tasks;
77
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
89
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
910
using Microsoft.VisualStudio.LanguageServices.Implementation.Venus;
1011
using VsTextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan;
@@ -24,6 +25,24 @@ internal static class VsTextSpanExtensions
2425
return null;
2526

2627
await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
28+
return MapSpanFromSecondaryBufferToPrimaryBuffer(
29+
spanInSecondaryBuffer, threadingContext, documentId, containedDocument);
30+
}
31+
32+
/// <summary>
33+
/// Only call this from the UI thread. See <see cref="MapSpanFromSecondaryBufferToPrimaryBufferAsync"/> for the async version.
34+
/// </summary>
35+
public static VsTextSpan? MapSpanFromSecondaryBufferToPrimaryBuffer(
36+
this VsTextSpan spanInSecondaryBuffer,
37+
IThreadingContext threadingContext,
38+
DocumentId documentId,
39+
ContainedDocument? containedDocument = null)
40+
{
41+
threadingContext.ThrowIfNotOnUIThread();
42+
43+
containedDocument ??= ContainedDocument.TryGetContainedDocument(documentId);
44+
if (containedDocument == null)
45+
return null;
2746
var bufferCoordinator = containedDocument.BufferCoordinator;
2847

2948
var primary = new VsTextSpan[1];

src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,13 @@ public int ResolveName(string? pszName, uint dwFlags, out IVsEnumDebugName? ppNa
177177
return VSConstants.S_FALSE;
178178
}
179179

180+
// NOTE(cyrusn): We have to wait here because the debuggers' ResolveName
181+
// call is synchronous. In the future it would be nice to make it async.
180182
ppNames = this.ThreadingContext.JoinableTaskFactory.Run(async () =>
181183
{
184+
// We're in a blocking JTF run. So ConfigureAwait(true) all calls to ensure we're coming back
185+
// and using the blocked thread whenever possible.
186+
182187
using (Logger.LogBlock(FunctionId.Debugging_VsLanguageDebugInfo_ResolveName, CancellationToken.None))
183188
{
184189
using var waitContext = _uiThreadOperationExecutor.BeginExecute(
@@ -192,14 +197,11 @@ public int ResolveName(string? pszName, uint dwFlags, out IVsEnumDebugName? ppNa
192197
{
193198
var solution = _languageService.Workspace.CurrentSolution;
194199

195-
// NOTE(cyrusn): We have to wait here because the debuggers' ResolveName
196-
// call is synchronous. In the future it would be nice to make it async.
197200
if (_breakpointService != null)
198201
{
199202
var breakpoints = await _breakpointService.ResolveBreakpointsAsync(
200-
solution, pszName, cancellationToken).ConfigureAwait(false);
201-
var debugNames = await breakpoints.SelectAsArrayAsync(
202-
bp => CreateDebugNameAsync(bp, cancellationToken)).ConfigureAwait(true);
203+
solution, pszName, cancellationToken).ConfigureAwait(true);
204+
var debugNames = breakpoints.SelectAsArray(bp => CreateDebugName(bp, cancellationToken));
203205

204206
return new VsEnumDebugName(debugNames);
205207
}
@@ -210,23 +212,28 @@ public int ResolveName(string? pszName, uint dwFlags, out IVsEnumDebugName? ppNa
210212
});
211213

212214
return ppNames != null ? VSConstants.S_OK : VSConstants.E_NOTIMPL;
213-
}
214215

215-
private async ValueTask<IVsDebugName> CreateDebugNameAsync(
216-
BreakpointResolutionResult breakpoint, CancellationToken cancellationToken)
217-
{
218-
var document = breakpoint.Document;
219-
var filePath = _languageService.Workspace.GetFilePath(document.Id);
220-
var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
221-
var span = text.GetVsTextSpanForSpan(breakpoint.TextSpan);
222-
// If we're inside an Venus code nugget, we need to map the span to the surface buffer.
223-
// Otherwise, we'll just use the original span.
224-
var mappedSpan = await span.MapSpanFromSecondaryBufferToPrimaryBufferAsync(
225-
this.ThreadingContext, document.Id, cancellationToken).ConfigureAwait(true);
226-
if (mappedSpan != null)
227-
span = mappedSpan.Value;
228-
229-
return new VsDebugName(breakpoint.LocationNameOpt, filePath!, span);
216+
IVsDebugName CreateDebugName(
217+
BreakpointResolutionResult breakpoint, CancellationToken cancellationToken)
218+
{
219+
// We're in a blocking jtf run. So CA(true) all calls to ensure we're coming bac
220+
// and using the blocked thread whenever possible.
221+
222+
var document = breakpoint.Document;
223+
var filePath = _languageService.Workspace.GetFilePath(document.Id);
224+
225+
// We're (unfortunately) blocking the UI thread here. So avoid async io as we actually
226+
// awant the IO to complete as quickly as possible, on this thread if necessary.
227+
var text = document.GetTextSynchronously(cancellationToken);
228+
var span = text.GetVsTextSpanForSpan(breakpoint.TextSpan);
229+
// If we're inside an Venus code nugget, we need to map the span to the surface buffer.
230+
// Otherwise, we'll just use the original span.
231+
var mappedSpan = span.MapSpanFromSecondaryBufferToPrimaryBuffer(this.ThreadingContext, document.Id);
232+
if (mappedSpan != null)
233+
span = mappedSpan.Value;
234+
235+
return new VsDebugName(breakpoint.LocationNameOpt, filePath!, span);
236+
}
230237
}
231238

232239
public int ValidateBreakpointLocation(IVsTextBuffer pBuffer, int iLine, int iCol, VsTextSpan[] pCodeSpan)

0 commit comments

Comments
 (0)