Skip to content

Commit 5b47c7f

Browse files
authored
Merge pull request #67978 from sharwell/async-open
Avoid accessing DocData prior to document initialization
2 parents 1ec6e88 + 5a00b22 commit 5b47c7f

File tree

1 file changed

+75
-20
lines changed

1 file changed

+75
-20
lines changed

src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using System.ComponentModel.Composition;
88
using System.Linq;
99
using System.Runtime.InteropServices;
10+
using System.Threading;
11+
using System.Threading.Tasks;
1012
using Microsoft.CodeAnalysis;
1113
using Microsoft.CodeAnalysis.Editor;
1214
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
@@ -31,7 +33,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation
3133
[Export]
3234
internal class VisualStudioActiveDocumentTracker : ForegroundThreadAffinitizedObject, IVsSelectionEvents
3335
{
36+
private readonly IAsyncServiceProvider _asyncServiceProvider;
3437
private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService;
38+
private IVsRunningDocumentTable4? _runningDocumentTable;
3539

3640
/// <summary>
3741
/// The list of tracked frames. This can only be written by the UI thread, although can be read (with care) from any thread.
@@ -51,6 +55,7 @@ public VisualStudioActiveDocumentTracker(
5155
IVsEditorAdaptersFactoryService editorAdaptersFactoryService)
5256
: base(threadingContext, assertIsForeground: false)
5357
{
58+
_asyncServiceProvider = asyncServiceProvider;
5459
_editorAdaptersFactoryService = editorAdaptersFactoryService;
5560
ThreadingContext.RunWithShutdownBlockAsync(async cancellationToken =>
5661
{
@@ -59,14 +64,16 @@ public VisualStudioActiveDocumentTracker(
5964
var monitorSelectionService = (IVsMonitorSelection?)await asyncServiceProvider.GetServiceAsync(typeof(SVsShellMonitorSelection)).ConfigureAwait(true);
6065
Assumes.Present(monitorSelectionService);
6166

67+
var runningDocumentTable = await GetRunningDocumentTableAsync(cancellationToken).ConfigureAwait(true);
68+
6269
// No need to track windows if we are shutting down
6370
cancellationToken.ThrowIfCancellationRequested();
6471

6572
if (ErrorHandler.Succeeded(monitorSelectionService.GetCurrentElementValue((uint)VSConstants.VSSELELEMID.SEID_DocumentFrame, out var value)))
6673
{
6774
if (value is IVsWindowFrame windowFrame)
6875
{
69-
TrackNewActiveWindowFrame(windowFrame);
76+
TrackNewActiveWindowFrame(windowFrame, runningDocumentTable);
7077
}
7178
}
7279

@@ -139,7 +146,7 @@ public ImmutableArray<DocumentId> GetVisibleDocuments(Workspace workspace)
139146
return ids.ToImmutableAndFree();
140147
}
141148

142-
public void TrackNewActiveWindowFrame(IVsWindowFrame frame)
149+
public void TrackNewActiveWindowFrame(IVsWindowFrame frame, IVsRunningDocumentTable4 runningDocumentTable)
143150
{
144151
AssertIsForeground();
145152

@@ -150,20 +157,33 @@ public void TrackNewActiveWindowFrame(IVsWindowFrame frame)
150157
var existingFrame = _visibleFrames.FirstOrDefault(f => f.Frame == frame);
151158
if (existingFrame == null)
152159
{
153-
_visibleFrames = _visibleFrames.Add(new FrameListener(this, frame));
160+
_visibleFrames = _visibleFrames.Add(new FrameListener(this, frame, runningDocumentTable));
154161
}
155162
else if (existingFrame.TextBuffer == null)
156163
{
157164
// If no text buffer is associated with existing frame, remove the existing frame and add the new one.
158165
// Note that we do not need to disconnect the existing frame here. It will get disconnected along with
159166
// the new frame whenever the document is closed or de-activated.
160167
_visibleFrames = _visibleFrames.Remove(existingFrame);
161-
_visibleFrames = _visibleFrames.Add(new FrameListener(this, frame));
168+
_visibleFrames = _visibleFrames.Add(new FrameListener(this, frame, runningDocumentTable));
162169
}
163170

164171
this.DocumentsChanged?.Invoke(this, EventArgs.Empty);
165172
}
166173

174+
private async ValueTask<IVsRunningDocumentTable4> GetRunningDocumentTableAsync(CancellationToken cancellationToken)
175+
{
176+
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
177+
178+
if (_runningDocumentTable is null)
179+
{
180+
_runningDocumentTable = (IVsRunningDocumentTable4?)await _asyncServiceProvider.GetServiceAsync(typeof(SVsRunningDocumentTable)).ConfigureAwait(true);
181+
Assumes.Present(_runningDocumentTable);
182+
}
183+
184+
return _runningDocumentTable;
185+
}
186+
167187
private void RemoveFrame(FrameListener frame)
168188
{
169189
AssertIsForeground();
@@ -198,7 +218,8 @@ int IVsSelectionEvents.OnElementValueChanged([ComAliasName("Microsoft.VisualStud
198218
ErrorHandler.Succeeded(frame.GetProperty((int)__VSFPROPID.VSFPROPID_Type, out var frameType)) &&
199219
(int)frameType == (int)__WindowFrameTypeFlags.WINDOWFRAMETYPE_Document)
200220
{
201-
TrackNewActiveWindowFrame(frame);
221+
var runningDocumentTable = ThreadingContext.JoinableTaskFactory.Run(() => GetRunningDocumentTableAsync(ThreadingContext.DisposalToken).AsTask());
222+
TrackNewActiveWindowFrame(frame, runningDocumentTable);
202223
}
203224
}
204225

@@ -217,31 +238,22 @@ private class FrameListener : IVsWindowFrameNotify, IVsWindowFrameNotify2
217238
public readonly IVsWindowFrame Frame;
218239

219240
private readonly VisualStudioActiveDocumentTracker _documentTracker;
241+
private readonly IVsRunningDocumentTable4 _runningDocumentTable;
220242
private readonly uint _frameEventsCookie;
221243

222-
internal ITextBuffer? TextBuffer { get; }
244+
internal ITextBuffer? TextBuffer { get; private set; }
223245

224-
public FrameListener(VisualStudioActiveDocumentTracker service, IVsWindowFrame frame)
246+
public FrameListener(VisualStudioActiveDocumentTracker service, IVsWindowFrame frame, IVsRunningDocumentTable4 runningDocumentTable)
225247
{
226248
_documentTracker = service;
249+
_runningDocumentTable = runningDocumentTable;
250+
227251
_documentTracker.AssertIsForeground();
228252

229253
this.Frame = frame;
230-
231254
((IVsWindowFrame2)frame).Advise(this, out _frameEventsCookie);
232255

233-
if (ErrorHandler.Succeeded(frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocData, out var docData)))
234-
{
235-
if (docData is IVsTextBuffer bufferAdapter)
236-
{
237-
TextBuffer = _documentTracker._editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter);
238-
239-
if (TextBuffer != null && !TextBuffer.ContentType.IsOfType(ContentTypeNames.RoslynContentType))
240-
{
241-
TextBuffer.Changed += NonRoslynTextBuffer_Changed;
242-
}
243-
}
244-
}
256+
TryInitializeTextBuffer();
245257
}
246258

247259
private void NonRoslynTextBuffer_Changed(object sender, TextContentChangedEventArgs e)
@@ -272,6 +284,17 @@ int IVsWindowFrameNotify.OnShow(int fShow)
272284
{
273285
switch ((__FRAMESHOW)fShow)
274286
{
287+
case __FRAMESHOW.FRAMESHOW_WinShown when TextBuffer is null:
288+
TryInitializeTextBuffer();
289+
if (TextBuffer is not null)
290+
{
291+
// The current TextBuffer was initialized in the OnShow instead of being initialized in the
292+
// constructor. For consumers, treat this the same way as when the active document changes.
293+
_documentTracker.DocumentsChanged?.Invoke(_documentTracker, EventArgs.Empty);
294+
}
295+
296+
return VSConstants.S_OK;
297+
275298
case __FRAMESHOW.FRAMESHOW_WinClosed:
276299
case __FRAMESHOW.FRAMESHOW_WinHidden:
277300
case __FRAMESHOW.FRAMESHOW_TabDeactivated:
@@ -287,6 +310,38 @@ int IVsWindowFrameNotify.OnSize()
287310
int IVsWindowFrameNotify2.OnClose(ref uint pgrfSaveOptions)
288311
=> Disconnect();
289312

313+
private void TryInitializeTextBuffer()
314+
{
315+
RoslynDebug.Assert(TextBuffer is null);
316+
317+
_documentTracker.AssertIsForeground();
318+
319+
if (ErrorHandler.Succeeded(Frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocCookie, out var boxedDocCookie)) && boxedDocCookie is uint docCookie)
320+
{
321+
var flags = (_VSRDTFLAGS)_runningDocumentTable.GetDocumentFlags(docCookie);
322+
if ((flags & (_VSRDTFLAGS)_VSRDTFLAGS4.RDT_PendingInitialization) != 0)
323+
{
324+
// This document is not yet initialized. Defer initialization to the next OnShow event.
325+
return;
326+
}
327+
}
328+
329+
if (ErrorHandler.Succeeded(Frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocData, out var docData)))
330+
{
331+
if (docData is IVsTextBuffer bufferAdapter)
332+
{
333+
TextBuffer = _documentTracker._editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter);
334+
335+
if (TextBuffer != null && !TextBuffer.ContentType.IsOfType(ContentTypeNames.RoslynContentType))
336+
{
337+
TextBuffer.Changed += NonRoslynTextBuffer_Changed;
338+
}
339+
}
340+
}
341+
342+
return;
343+
}
344+
290345
private int Disconnect()
291346
{
292347
_documentTracker.AssertIsForeground();

0 commit comments

Comments
 (0)