Skip to content

Extract the RDT implementation for Misc files and VS open file tracker#36006

Merged
dibarbet merged 15 commits intodotnet:masterfrom
dibarbet:extract_open_file_tracker
Jun 13, 2019
Merged

Extract the RDT implementation for Misc files and VS open file tracker#36006
dibarbet merged 15 commits intodotnet:masterfrom
dibarbet:extract_open_file_tracker

Conversation

@dibarbet
Copy link
Copy Markdown
Member

@dibarbet dibarbet commented May 28, 2019

Liveshare implements their own roslyn remote workspace which tracks files using a different system. Now that we're moving it inside, we want to re-use the already created interactions with the RDT wherever possible.

This PR just extracts out the common pieces into an event system where subscribers can get notified of specific RDT events.

@jasonmalinowski @heejaechang does this look roughly like we discussed?

@dibarbet dibarbet added this to the 16.2.P3 milestone May 28, 2019
@dibarbet dibarbet requested review from a team, heejaechang and jasonmalinowski May 28, 2019 23:51
Copy link
Copy Markdown
Contributor

@heejaechang heejaechang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have test for these?

@heejaechang
Copy link
Copy Markdown
Contributor

let me tag @jasonmalinowski as well.

@dibarbet
Copy link
Copy Markdown
Member Author

do we have test for these?

Per Jason, this would be exercised in the integration tests. We have unit tests for these since it uses VS services.

Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the slice point needs to be somewhere else, since we don't want the RDT interactions being redone by other teams since it's really tricky.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the expectation that other derives outside of Roslyn will use this? My worry is whether we're best off making these private and expect the derived type to re-query if it needs them; otherwise we're exposing more here than we may want.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My expectation would have been that all of this still lives in the base. The only bit that would be in the derived methods would be the stuff that starts on if (w.CurrentSolution.ContainsDocument(…)) stuff, where the input to the function is a DocumentId and the SourceTextContainer.

This is of course tricky because of the locking semantics, so you might have to have two methods that are implemented by derivers

void ApplyWorkspaceChangeUnderLock(Action<Workspace> w);

which just forwards to the VS workspace one that acquires the lock, and then:

void ApplyDocumentOpen(DocumentId, SourceTextContainer)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(referring to the earlier comment) -- start what is the derived behavior here -- keep the rest still in the base.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public sealed class VisualStudioOpenFileTracker : AbstractOpenFileTracker<VisualStudioWorkspaceImpl>
public sealed class VisualStudioWorkspaceOpenFileTracker : AbstractOpenFileTracker<VisualStudioWorkspaceImpl>

The reason being that sometimes we use "VisualStudioBlah" to generally mean an implementation that's used for all workspace implementations running in VS; this isn't quite the same.

@dibarbet
Copy link
Copy Markdown
Member Author

Going to update this review, per Jason -
Since the liveshare workspace that we want to get document notifications for does not necessarily have the document as part of the workspace (it might need to add it), we shouldn't use the open file tracker, instead we should have a common RDT event handler that handles common logic (make sure the rdt document is initialized, extract out the text buffer, moniker, etc) and passes the event down to whoever wants to handle it (OpenFileTracker, our new liveshare impl.

@dibarbet dibarbet force-pushed the extract_open_file_tracker branch from f2e06bd to f8cf872 Compare June 3, 2019 21:09
@dibarbet dibarbet force-pushed the extract_open_file_tracker branch from f8cf872 to 3ab2ce9 Compare June 3, 2019 21:11
@dibarbet dibarbet changed the title Extract an abstract open file tracker so that we can re-use the open Extract the RDT implementation for Misc files and VS open file tracker Jun 3, 2019
Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is on the right track but I feel like the RDT abstraction is still leaking quite a bit of RDT specifics that are worth abstracting. The "abstracted" interface to me seems too like it should be a bit simpler and just have open/close/rename/context changed. You're having clients sometimes handle one kind of open but not another, and that looks suspicious to me. It might have been what the code was doing before, but that might have been a bug.

/// Defines the methods that get called by the <see cref="RunningDocumentTableEventTracker"/>
/// for getting notified about running document table events.
/// </summary>
internal interface IRunningDocumentTableEventListener
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why an interface over just events? It seems neither client needed to use all of the methods, and this pattern doesn't scale well if we ever need to add another one. That the RDT events themselves are done via interfaces is just because events hadn't been invented back then.*

(* Well, yes, COM can do events too, but let's not talk about such things in polite company.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now @tmat wanted it this way, and his argument is persuasive given what he commented on. I suspect part of the problem though was there were more events than necessary, and I guess I'm not a particularly strong believer in EventArgs being a useful pattern, so maybe that would have helped. :-)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My experience with C# eventing is a little limited - what is the alternative to event args?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can be lazy and just not do the pattern. Just have delegate types that have the parameters you want.

@@ -47,7 +49,6 @@ internal sealed partial class MiscellaneousFilesWorkspace : Workspace, IVsRunnin
private readonly Dictionary<uint, (ProjectId projectId, SourceTextContainer textContainer)> _docCookiesToProjectIdAndContainer = new Dictionary<uint, (ProjectId, SourceTextContainer)>();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must admit: I'm curious what this would look like if we replaced this dictionary operating on cookies with file names. Would this then let us toss all the cookie passing in common helper? That'd be great then when we replace this with an editor API that wouldn't be talking about cookies anyways.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And maybe this isn't a strict requirement now, but if you squint a bit you could imagine this isn't a RunningDocumentTableEventsTracker, but just an generic abstraction over "give me the open files and let me know when that changes" -- we might be able to get rid of cookies entirely. That might be handy if we want to use the misc files workspace somewhere else since once this isn't touching the RDT directly it's pretty light on other VS tie-ins.

@dibarbet
Copy link
Copy Markdown
Member Author

dibarbet commented Jun 5, 2019

This is on the right track but I feel like the RDT abstraction is still leaking quite a bit of RDT specifics that are worth abstracting. The "abstracted" interface to me seems too like it should be a bit simpler and just have open/close/rename/context changed. You're having clients sometimes handle one kind of open but not another, and that looks suspicious to me. It might have been what the code was doing before, but that might have been a bug.

Thanks for the review :)
I was attempting to err on the side of 'change as little of the actual behavior as possible' to avoid breaking things in this rather complicated area. What might work is this

  1. Unify events as you mentioned. Seems like the right thing to do and straightforward-ish
  2. I can attempt to abstract out more of the RDT interactions (e.g. methods for just GetOpenDocuments or removing cookies). However, I do need to eventually get to work on the liveshare piece of this so I'll timebox this so I don't end up going down a rabbit hole.

Does that sound OK @jasonmalinowski ?

@jasonmalinowski
Copy link
Copy Markdown
Member

Sounds good. I think the tricky bit in it's current state is the attempt to preserve behavior perhaps makes it look more confusing and more "wrong" than if we tried to clean things up a bit. I wonder if some of the differences you're trying to preserve are either bugs or oversights -- in either case looking at it in a fresh light makes it look even stranger.

@dibarbet
Copy link
Copy Markdown
Member Author

dibarbet commented Jun 6, 2019

Sounds good. I think the tricky bit in it's current state is the attempt to preserve behavior perhaps makes it look more confusing and more "wrong" than if we tried to clean things up a bit. I wonder if some of the differences you're trying to preserve are either bugs or oversights -- in either case looking at it in a fresh light makes it look even stranger.

@jasonmalinowski
I've tried to clean this up a bit, let me know if it's going the right direction.
What I've done is -

  1. Combine events, so there are now four - OnOpenDocument, OnCloseDocument, OnRefreshDocumentContext, and OnRenameDocument
  2. Get rid of some of the extraneous open RDT events and just using DocDataReloaded (based on discussion with David).
  3. Removed all references to cookies and RDT inside the OpenFileTracker and Misc workspace.

This should be a bit less leaky I hope.

Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice -- the Enumerate* methods still seem a bit odd to me in that they're not IEnumerable and also it feels like we can decompose them a bit better. But I wouldn't consider that blocking if you have other work piled up behind this if you want to get things going in parallel.

_textManager = (IVsTextManager)serviceProvider.GetService(typeof(SVsTextManager));

((IVsRunningDocumentTable)_runningDocumentTable).AdviseRunningDocTableEvents(this, out _runningDocumentTableEventsCookie);
var runningDocumentTable = (IVsRunningDocumentTable4)serviceProvider.GetService(typeof(SVsRunningDocumentTable));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels a bit strange we explicitly cast this to IVsRunningDocumentTable4 -- let the helper just take the base one and it can cast it to whatever versions it needs.

_foregroundAffinitization.AssertIsForeground();
if (_runningDocumentTable.IsDocumentInitialized(docCookie) && TryGetBuffer(docCookie, out var buffer))
{
_listener.OnRenameDocument(pszMkDocumentNew, pszMkDocumentOld, buffer);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: use named arguments for passing the pszMkDocumentOld/New, so there's no chance these are flipped.

return VSConstants.E_NOTIMPL;
}

public bool IsMonikerValid(string fileName) => _runningDocumentTable.IsMonikerValid(fileName);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the API perspective should this be called IsFileOpen?

textBuffer = null;

// The cast from dynamic to object doesn't change semantics, but avoids loading the dynamic binder
// which saves us JIT time in this method.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not just JIT time in this method -- avoids an entire assembly load.

/// Applies an action to all initialized files in the RDT.
/// </summary>
/// <param name="enumerateAction">the action to apply.</param>
public void EnumerateDocumentSet(Action<string, ITextBuffer, IVsHierarchy> enumerateAction)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any resaon this isn't return an IEnumerable? IEnumerable of tuple here seems most appropriate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(also means the enumeration is abortable...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or why can't a caller just enumerate just the file names, and they can call TryGetBufferFromMoniker themselves?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I did it this way was to avoid having to return all three pieces of data. But an IEnumerable of tuple seems like a reasonable way to do it. Will update.

/// </summary>
/// <param name="enumerateAction">the action to apply.</param>
/// <param name="fileSet">the file set to apply the action to.</param>
public void EnumerateSpecifiedDocumentSet(Action<string, ITextBuffer, IVsHierarchy> enumerateAction, IEnumerable<string> fileSet)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't a caller just do this via the APIs you already have?


private void ConnectToRunningDocumentTable()
/// <summary>
/// The VS open file tracker handles renames through <see cref="QueueCheckForFilesBeingOpen"/>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure comment makes sense here: might be better to say "when a file is renamed, the old document is removed and new document added by the workspace". QueueCheckForFilesBeingOpen isn't really "handing" the rename per se, it's just the helper.

@dibarbet
Copy link
Copy Markdown
Member Author

@jasonmalinowski thanks for the review! I've addressed the comments since the CI was stuck not reporting the status anyway. Once the tests pass I'll go ahead and merge unless you have any other concerns.

@dibarbet dibarbet merged commit 64ed5eb into dotnet:master Jun 13, 2019
@dibarbet dibarbet deleted the extract_open_file_tracker branch June 13, 2019 21:53
@dibarbet dibarbet restored the extract_open_file_tracker branch June 17, 2019 19:03
dibarbet added a commit to dibarbet/roslyn that referenced this pull request Jun 17, 2019
Extract the RDT implementation for Misc files and VS open file tracker
dibarbet added a commit to dibarbet/roslyn that referenced this pull request Jun 18, 2019
Extract the RDT implementation for Misc files and VS open file tracker
dibarbet added a commit to dibarbet/roslyn that referenced this pull request Jun 19, 2019
Extract the RDT implementation for Misc files and VS open file tracker
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants