File: Host\RemoteWorkspace_SolutionCaching.cs
Web Access
Project: ..\..\..\src\Workspaces\Remote\ServiceHub\Microsoft.CodeAnalysis.Remote.ServiceHub.csproj (Microsoft.CodeAnalysis.Remote.ServiceHub)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Remote
{
    internal sealed partial class RemoteWorkspace
    {
        /// <summary>
        /// The last solution for the primary branch fetched from the client.  Cached as it's very common to have a
        /// flurry of requests for the same checksum that don't run concurrently.  Only read/write while holding <see
        /// cref="_gate"/>.
        /// </summary>
        private (Checksum checksum, Solution solution) _lastRequestedPrimaryBranchSolution;
 
        /// <summary>
        /// The last solution requested by a service.  Cached as it's very common to have a flurry of requests for the
        /// same checksum that don't run concurrently.  Only read/write while holding <see cref="_gate"/>.
        /// </summary>
        private (Checksum checksum, Solution solution) _lastRequestedAnyBranchSolution;
 
        /// <summary>
        /// Mapping from solution-checksum to the solution computed for it.  This is used so that we can hold a solution
        /// around as long as the checksum for it is being used in service of some feature operation (e.g.
        /// classification).  As long as we're holding onto it, concurrent feature requests for the same solution
        /// checksum can share the computation of that particular solution and avoid duplicated concurrent work.  Only
        /// read/write while holding <see cref="_gate"/>.
        /// </summary>
        private readonly Dictionary<Checksum, InFlightSolution> _solutionChecksumToSolution = new();
 
        /// <summary>
        /// Deliberately not cancellable.  This code must always run fully to completion.
        /// </summary>
        private InFlightSolution GetOrCreateSolutionAndAddInFlightCount_NoLock(
            AssetProvider assetProvider,
            Checksum solutionChecksum,
            int workspaceVersion,
            bool updatePrimaryBranch)
        {
            Contract.ThrowIfFalse(_gate.CurrentCount == 0);
 
            CheckCacheInvariants_NoLock();
 
            var solution = GetOrCreateSolutionAndAddInFlightCount_NoLock();
 
            // The solution must now have a valid in-flight-count.
            Contract.ThrowIfTrue(solution.InFlightCount < 1);
 
            // We may be getting back a solution that only was computing a non-primary branch.  If we were asked
            // to compute the primary branch as well, let it know so it can start that now.
            if (updatePrimaryBranch)
            {
                solution.TryKickOffPrimaryBranchWork_NoLock((disconnectedSolution, cancellationToken) =>
                    this.TryUpdateWorkspaceCurrentSolutionAsync(workspaceVersion, disconnectedSolution, cancellationToken));
            }
 
            CheckCacheInvariants_NoLock();
 
            return solution;
 
            InFlightSolution GetOrCreateSolutionAndAddInFlightCount_NoLock()
            {
                Contract.ThrowIfFalse(_gate.CurrentCount == 0);
 
                if (_solutionChecksumToSolution.TryGetValue(solutionChecksum, out var solution))
                {
                    // The cached solution must have a valid in-flight-count
                    Contract.ThrowIfTrue(solution.InFlightCount < 1);
 
                    // Increase the count as our caller now is keeping this solution in-flight
                    solution.IncrementInFlightCount_NoLock();
                    Contract.ThrowIfTrue(solution.InFlightCount < 2);
 
                    return solution;
                }
 
                // See if we're being asked for a checksum we already have cached a solution for.  Safe to read directly
                // as we're holding _gate.
                var cachedSolution =
                    _lastRequestedPrimaryBranchSolution.checksum == solutionChecksum ? _lastRequestedPrimaryBranchSolution.solution :
                    _lastRequestedAnyBranchSolution.checksum == solutionChecksum ? _lastRequestedAnyBranchSolution.solution : null;
 
                // We're the first call that is asking about this checksum.  Kick off async computation to compute it
                // (or use an existing cached value we already have).  Start with an in-flight-count of 1 to represent
                // our caller. 
                solution = new InFlightSolution(
                    this, solutionChecksum,
                    async cancellationToken => cachedSolution ?? await ComputeDisconnectedSolutionAsync(assetProvider, solutionChecksum, cancellationToken).ConfigureAwait(false));
                Contract.ThrowIfFalse(solution.InFlightCount == 1);
 
                _solutionChecksumToSolution.Add(solutionChecksum, solution);
 
                return solution;
            }
        }
 
        private void CheckCacheInvariants_NoLock()
        {
            Contract.ThrowIfFalse(_gate.CurrentCount == 0);
 
            foreach (var (solutionChecksum, solution) in _solutionChecksumToSolution)
            {
                // Anything in this dictionary is currently in flight with an existing request.  So it must have an
                // in-flight-count of at least 1.  Note: this in-flight-request may be an actual request that has come
                // in from the client.  Or it can be a virtual one we've created through _lastAnyBranchSolution or
                // _lastPrimaryBranchSolution
                Contract.ThrowIfTrue(solution.InFlightCount < 1);
                Contract.ThrowIfTrue(solutionChecksum != solution.SolutionChecksum);
            }
        }
    }
}