Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure current OOP calls for the same solution-checksum can share the same OOP solution computation #60575

Merged
merged 30 commits into from
Apr 6, 2022

Conversation

CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented Apr 5, 2022

Sending out review for initial thoughts/feedback.

The general idea here is to setup our host/OOP communication to avoid teh following problem.

  1. user makes edit on host, moving solution from Vn to Vn+1.
  2. immediately K features light up and call over to OOP to do work.
  3. all features see that OOP has not sync'ed solution Vn+1 over and do the work to do just that, producing solution snapshots that may not share some state (for example the compilation-trackers for the last edited project.

To avoid this, this PR introduces a slightly different model for host-OOP calls. Now, when the host calls into OOP for a paticular feature on a particular snapshot checksum, then OOP side creates a mapping from that checksum to the async computation for taht solution (an AsyncLazy), and it keeps it alive as long as that call is in flight. If any other calls come in during that time and need that same solution, they will be given the same solution computation object back so that everything is shared across those calls. This is done in a ref-counting fashion to ensure that as long as something is calling into oop that is working on that snapshot, the lazy for it will be kept alive and any concurrent calls will just get that and do no more extra work.

We still also keep track of hte last primary-workspace-solution, and the last-solution queried for, so that if a call gets either, and then returns, thne the next call can see those and potentially benefit from it.

/// classification). As long as we're holding onto it, concurrent feature requests for the same checksum can
/// share the computation of that particular solution and avoid duplicated concurrent work.
/// </summary>
private readonly Dictionary<Checksum, (int refCount, AsyncLazy<Solution> lazySolution)> _checksumToRefCountAndLazySolution = new();
Copy link
Member Author

Choose a reason for hiding this comment

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

this is the meat of the PR. as long as an operation is in flight that is using a particular PinnedSolutionInfo checksum, we will just hand anyone that wants the solution for it the corresponding AsyncLazy in this dictionary. The refcount is used to keep track of concurrent callers and to release the solution from this map once nothing is asking about it.

{
var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false);
var document = solution.GetDocument(documentId);
Copy link
Member Author

Choose a reason for hiding this comment

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

the new pattern for OOP features. Features call RunServiceWithSolutionAsync passing in their PinnedSolutionInfo they are associated with. They also pass in a callback which is given the solution that is computed once done. This allows that computation to be shared among multiple concurrent callers into oop with teh same solution checksum.

// solution instance).
return this.CurrentSolution;
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

these diffs are terrible. I'm not srue why it threw it off so much to indent. working on this.

@sharwell
Copy link
Member

sharwell commented Apr 5, 2022

Test failure showing gold bars:

Feature 'Document highlights' is currently unavailable due to an internal error.
StreamJsonRpc.RemoteInvocationException: The given key was not present in the dictionary.
   at StreamJsonRpc.JsonRpc.<InvokeCoreAsync>d__143`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable`1.ConfiguredValueTaskAwaiter.GetResult()
   at Microsoft.CodeAnalysis.Remote.BrokeredServiceConnection`1.<TryInvokeAsync>d__18`1.MoveNext() in /_/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs:line 217
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.VisualStudio.Telemetry.WindowsErrorReporting.WatsonReport.GetClrWatsonExceptionInfo(Exception exceptionObject)
RPC server exception:
StreamJsonRpc.RemoteInvocationException: The given key was not present in the dictionary.
      at StreamJsonRpc.JsonRpc.InvokeCoreAsync[TResult](RequestId id, String targetName, IReadOnlyList`1 arguments, IReadOnlyList`1 positionalArgumentDeclaredTypes, IReadOnlyDictionary`2 namedArgumentDeclaredTypes, CancellationToken cancellationToken, Boolean isParameterObject)
      at Microsoft.CodeAnalysis.Remote.BrokeredServiceConnection`1.<>c__DisplayClass26_0`1.<<InvokeStreamingServiceAsync>b__0>d.MoveNext() in /_/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs:line 402
   --- End of stack trace from previous location ---
      at Microsoft.CodeAnalysis.Remote.BrokeredServiceConnection`1.<>c__DisplayClass26_0`1.<<InvokeStreamingServiceAsync>b__0>d.MoveNext() in /_/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs:line 411
   --- End of stack trace from previous location ---
      at Microsoft.CodeAnalysis.Remote.BrokeredServiceConnection`1.InvokeStreamingServiceAsync[TResult](TService service, Func`4 invocation, Func`3 reader, CancellationToken cancellationToken) in /_/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs:line 434
      at Microsoft.CodeAnalysis.Remote.RemoteCallback`1.InvokeAsync[TResult](Func`4 invocation, Func`3 reader, CancellationToken cancellationToken) in /_/src/Workspaces/Remote/Core/RemoteCallback.cs:line 68
      at Microsoft.VisualStudio.Telemetry.WindowsErrorReporting.WatsonReport.GetClrWatsonExceptionInfo(Exception exceptionObject)
   --- End of stack trace from previous location ---
      at Microsoft.CodeAnalysis.Remote.SolutionAssetSource.GetAssetsAsync(Int32 scopeId, ISet`1 checksums, ISerializerService serializerService, CancellationToken cancellationToken) in /_/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs:line 34
      at Microsoft.CodeAnalysis.Remote.AssetProvider.RequestAssetsAsync(ISet`1 checksums, CancellationToken cancellationToken) in /_/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs:line 162
      at Microsoft.CodeAnalysis.Remote.AssetProvider.RequestAssetAsync(Checksum checksum, CancellationToken cancellationToken) in /_/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs:line 149
      at Microsoft.CodeAnalysis.Remote.AssetProvider.GetAssetAsync[T](Checksum checksum, CancellationToken cancellationToken) in /_/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs:line 48
      at Microsoft.CodeAnalysis.Remote.RemoteWorkspace.SolutionCreator.IsIncrementalUpdateAsync(Checksum newSolutionChecksum) in /_/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs:line 47
      at Microsoft.CodeAnalysis.Remote.RemoteWorkspace.ComputeSolutionAsync(AssetProvider assetProvider, Checksum solutionChecksum, Int32 workspaceVersion, Boolean fromPrimaryBranch, Solution baseSolution, CancellationToken cancellationToken) in /_/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs:line 286
      at Microsoft.CodeAnalysis.Remote.RemoteWorkspace.TrySlowGetSolutionAndRunAsync[T](AssetProvider assetProvider, Checksum solutionChecksum, Int32 workspaceVersion, Boolean fromPrimaryBranch, Solution baseSolution, Func`2 doWorkAsync, CancellationToken cancellationToken) in /_/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs:line 178
      at Microsoft.CodeAnalysis.Remote.RemoteWorkspace.RunWithSolutionAsync[T](AssetProvider assetProvider, Checksum solutionChecksum, Int32 workspaceVersion, Boolean fromPrimaryBranch, Func`2 doWorkAsync, CancellationToken cancellationToken) in /_/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs:line 137
      at Microsoft.CodeAnalysis.Remote.BrokeredServiceBase.RunWithSolutionAsync[T](PinnedSolutionInfo solutionInfo, Func`2 doWorkAsync, CancellationToken cancellationToken) in /_/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs:line 91
      at Microsoft.CodeAnalysis.Remote.BrokeredServiceBase.RunServiceImplAsync[T](Func`2 implementation, CancellationToken cancellationToken) in /_/src/Workspaces/Remote/ServiceHub/Services/BrokeredServiceBase.cs:line 119

// We're not doing an update, we're moving to a new solution entirely. Clear out the old one. This
// is necessary so that we clear out any open document information this workspace is tracking. Note:
// this seems suspect as the remote workspace should not be tracking any open document state.
this.ClearSolutionData();
Copy link
Member Author

Choose a reason for hiding this comment

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

@tmat for thoughts. t his preserves what we had before... but hwat we had before seems very fishy/suspect.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, let's keep it for now and just remove the workspace updating completely once it's possible.

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

@CyrusNajmabadi CyrusNajmabadi changed the title WIP: Ensure current OOP calls for the same solution-checksum can share the same OOP solution computation Ensure current OOP calls for the same solution-checksum can share the same OOP solution computation Apr 5, 2022
@CyrusNajmabadi CyrusNajmabadi marked this pull request as ready for review April 5, 2022 20:23
@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner April 5, 2022 20:23
/// classification). As long as we're holding onto it, concurrent feature requests for the same checksum can
/// share the computation of that particular solution and avoid duplicated concurrent work.
/// </summary>
private readonly Dictionary<Checksum, (int refCount, AsyncLazy<Solution> lazySolution)> _checksumToRefCountAndLazySolution = new();
Copy link
Member

Choose a reason for hiding this comment

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

_checksumToRefCountAndLazySolution

perhaps better name: _availableSolutions

Copy link
Member Author

Choose a reason for hiding this comment

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

Hrmm... i'm ok with the name as is. _availableSolutions is a bit too vague for me.

{
return null;
}
return await RunWithSolutionAsync(solutionInfo, async solution =>
Copy link
Member

Choose a reason for hiding this comment

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

Can't we use RunServiceWithSolutionAsync?

Copy link
Member Author

Choose a reason for hiding this comment

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

the issue here is preserving the meaning of the using-block for telemetry.

Copy link
Member

Choose a reason for hiding this comment

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

could LogBlock be around RunServiceAsync?

Copy link
Member Author

Choose a reason for hiding this comment

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

let me do that in a followup.

{
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
if (_checksumToRefCountAndLazySolution.TryGetValue(solutionChecksum, out var tuple))
Copy link
Member

Choose a reason for hiding this comment

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

tuple

out var (refCount, lazySolution)

Copy link
Member Author

Choose a reason for hiding this comment

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

not a thing the language supports (yet).

Copy link
Member

@tmat tmat left a comment

Choose a reason for hiding this comment

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

Great idea. Just a few suggestions to improve readability.

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.

4 participants