Skip to content

Run HasRefactoringsAsync out-of-process#40672

Open
sharwell wants to merge 5 commits intodotnet:mainfrom
sharwell:oop-refactoring
Open

Run HasRefactoringsAsync out-of-process#40672
sharwell wants to merge 5 commits intodotnet:mainfrom
sharwell:oop-refactoring

Conversation

@sharwell
Copy link
Copy Markdown
Contributor

@sharwell sharwell commented Dec 31, 2019

Marked as a draft until we verify that it works for both built-in refactorings and refactorings provided by a VSIX package.

This change means refactorings no longer have access to Visual Studio services, but our options are somewhat limited. The infrastructure to execute HasRefactoringsAsync cannot be executed in the limited process space of devenv.exe without risking OOM crashes in large sources. Moving out-of-process means the 64-bit address space can be leveraged when necessary.

We could provide a hook for individual refactorings to run in process, but it will have a tremendous negative performance impact on those refactorings (they will no longer share a semantic model with other refactorings running on the same compilation, so moving a refactoring in-process means the entire burden of parsing+binding+analysis will fall on the refactoring that chose to run in process).

Refactorings that require Visual Studio services always have the option of interacting directly with the Light Bulb APIs instead of operating as a CodeRefactoringProvider.

@sharwell
Copy link
Copy Markdown
Contributor Author

@tmat @CyrusNajmabadi This is the change I was mentioning in our meeting last week

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

Overall 1000% for this. But i think we can simplify in a very easy way.

@sharwell sharwell force-pushed the oop-refactoring branch from a7a21d0 to b3fc97c Compare May 6, 2020 18:30
Comment on lines +982 to +985
var pastedTextSpanOpt =
provider._pasteTrackingService.TryGetPastedTextSpan(sourceText.Container, out var pastedTextSpan)
? (TextSpan?)pastedTextSpan
: null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
var pastedTextSpanOpt =
provider._pasteTrackingService.TryGetPastedTextSpan(sourceText.Container, out var pastedTextSpan)
? (TextSpan?)pastedTextSpan
: null;
var pastedTextSpanOpt = provider._pasteTrackingService.TryGetPastedTextSpan(sourceText.Container, out var pastedTextSpan)
? (TextSpan?)pastedTextSpan
: null;

The problem with this style is how difficult it is to distinguish the parent/child relationship. For exaple, this would be even more difficult to understand:

Suggested change
var pastedTextSpanOpt =
provider._pasteTrackingService.TryGetPastedTextSpan(sourceText.Container, out var pastedTextSpan)
? (TextSpan?)pastedTextSpan
: null;
var pastedTextSpanOpt = provider._pasteTrackingService.TryGetPastedTextSpan(sourceText.Container, out var pastedTextSpan)
? another.complex.expr()
? and.yet.another()
? baz
: quuz
: bar
: null;

As opposed to:

Suggested change
var pastedTextSpanOpt =
provider._pasteTrackingService.TryGetPastedTextSpan(sourceText.Container, out var pastedTextSpan)
? (TextSpan?)pastedTextSpan
: null;
var pastedTextSpanOpt = provider._pasteTrackingService.TryGetPastedTextSpan(sourceText.Container, out var pastedTextSpan)
? another.complex.expr()
? and.yet.another()
? baz
: quuz
: bar
: null;

Now i can very easily see what children are actually siblings, and i can easily tell the parent/child relationship.

i get that you continually push this strict adherence to indentation. But it's not going to fly with me. Custom indentation serves a valuable purpose and we do judiciously use it across the IDE codebase where appropriate because it really helps with clarity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is all covered by #38255 (comment). The form I used is correct (covered by 1(ii)), as are the first and third examples here (covered by 1(iii)).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i don't actually see the value of 1ii. in particular it seems to entirely not what i'd want for indenting whenever the expression itself starts on a new line. i.e. on a new line you'll always get:

....
    startOfExpr
    ? whenTrue
    : whenFalse

Which never seems desirable. This also doesn't really match teh examples shown in that linked post, none of which show the whenTrue/whenFalse parts aligning with the condition.

Comment on lines +58 to +59
// Cannot track two different pasted spans for the same container
throw ExceptionUtilities.UnexpectedValue(pastedTextSpan);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@CyrusNajmabadi The other option here is to assign null (no pasted text span) if multiple callers cannot agree on the same non-null span. As it stands, code that attempts to use a different span will fail if prior work has not yet completed.

@sharwell sharwell marked this pull request as ready for review May 7, 2020 00:09
@sharwell sharwell requested a review from a team as a code owner May 7, 2020 00:09
@sharwell sharwell requested a review from a team May 7, 2020 00:09
return textSpan;

return null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is a bit concerning that we're exposing a synchronous method over to the OOP side. in general we don't do that. can this be made async?

var pasteTrackingService = (RemotePasteTrackingService?)mefHostExportProvider.GetExports<IPasteTrackingService>().SingleOrDefault()?.Value;
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var container = sourceText.Container;
using var _ = pasteTrackingService?.SetPastedTextSpanForRemoteCall(container, pastedTextSpan);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is the ?. needed? can it actually fail to get the IPasteTrackingService?

textSpan,
pastedTextSpan,
},
callbackTarget: new CodeRefactoringServiceCallback(this),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

tbh.... i don't get what this callback is for. i don't eally see anyone consuming it on the remote side. could you clarify?

lock (_trackedSpans)
{
var result = _trackedSpans.TryGetValue(sourceTextContainer, out var spanAndCount);
if (!result || spanAndCount.referenceCount == 0 || spanAndCount.span is null)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the .referenceCount == 0 case seems impossible. wlud prefer we throw in that case vs bailing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will add an assertion

throw ExceptionUtilities.UnexpectedValue(container);

Debug.Assert(spanAndCount.referenceCount > 0);
if (spanAndCount.referenceCount <= 1)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the < case seems imposible. would prefer we throw in that case.

Copy link
Copy Markdown
Contributor

@CyrusNajmabadi CyrusNajmabadi left a comment

Choose a reason for hiding this comment

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

Overall looking good. One thing i didn't understand, and a few places i think coudl clean up.

Base automatically changed from master to main March 3, 2021 23:52
{
if (await state.Target.Owner._codeRefactoringService.HasRefactoringsAsync(
document, selection.Value, cancellationToken).ConfigureAwait(false))
document, selection.Value, pastedTextSpanOpt, cancellationToken).ConfigureAwait(false))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so, generally, we've been trying to move to the model more where we have 'args' that can get passed along with the op, to avoid the 'just jam a ton of individual parameters along' . Just food for thought.

if (result.HasValue)
{
return result.Value;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i believe we're no longer following this pattern. if the operation fails, we do not try to fallback to local processing. instead, we jsut bail out (As we will also have had a yellowbar here). note we may not be 100% consistent here, but i believe that Tomas' strong position on what this ll needs to be.

// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

remove.

return new Releaser(this, container);
}

internal readonly struct Releaser : IDisposable
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: my preference is public on all nested symbols that are made 'internal' by virtue of their container.

}, cancellationToken);
}

public ValueTask<bool> HasRefactoringsAsync(PinnedSolutionInfo solutionInfo, DocumentId documentId, TextSpan textSpan, TextSpan? pastedTextSpan, CancellationToken cancellationToken)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

there's a potential future optimizatino we can do/track if we're willing to make the statement that a refactoring should be computable without knowing about projects that depend on. Specifically, OOP supports the idea of quickly syncing only a project (and the projects it depends on). we could use that to more quickly sync and find out if there are refactorings. i believe all our refactorings would satisfy this. but some third party ones might not.

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

i have a general design concern here given that we run HasRefactorings OOP, but GetRefactorign in prc. It means that we cannot leverage all the work that HasRefactorings deos (in terms of warming up compilations, etc.) that happen externally. THis risks paying the same price again when we now actually do the work in proc to get the refactorings.

That said, maybe it's ok. an A/B test with code markers would help here.

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.

3 participants