public SolutionCreator(HostServices hostServices, AssetProvider assetService, Solution baseSolution, CancellationToken cancellationToken) { _hostServices = hostServices; _assetProvider = assetService; _baseSolution = baseSolution; _cancellationToken = cancellationToken; }
private async Task <Dictionary <ProjectId, ProjectStateChecksums> > GetProjectMapAsync( AssetProvider assetProvider, HashSet <Checksum> projects ) { var map = new Dictionary <ProjectId, ProjectStateChecksums>(); var projectChecksums = await assetProvider .GetAssetsAsync <ProjectStateChecksums>(projects, _cancellationToken) .ConfigureAwait(false); var infos = await assetProvider .GetAssetsAsync <ProjectInfo.ProjectAttributes>( projectChecksums.Select(p => p.Item2.Info), _cancellationToken ) .ConfigureAwait(false); foreach (var kv in projectChecksums) { var info = await assetProvider .GetAssetAsync <ProjectInfo.ProjectAttributes>( kv.Item2.Info, _cancellationToken ) .ConfigureAwait(false); map.Add(info.Id, kv.Item2); } return(map); }
public SolutionCreator(AssetProvider assetService, Solution baseSolution, CancellationToken cancellationToken) { Contract.ThrowIfNull(baseSolution); _assetProvider = assetService; _baseSolution = baseSolution; _cancellationToken = cancellationToken; }
/// <summary> /// The workspace is designed to be stateless. If someone asks for a solution (through solution checksum), /// it will create one and return the solution. The engine takes care of syncing required data and creating a solution /// corresponding to the given checksum. /// /// but doing that from scratch all the time will be expansive in terms of syncing data, compilation being cached, file being parsed /// and etc. so even if the service itself is stateless, internally it has several caches to improve perf of various parts. /// /// first, it holds onto last solution got built. this will take care of common cases where multiple services running off same solution. /// second, it uses assets cache to hold onto data just synched (within 3 min) so that if it requires to build new solution, /// it can save some time to re-sync data which might just used by other solution. /// third, it holds onto solution from primary branch from Host. and it will try to see whether it can build new solution off the /// primary solution it is holding onto. this will make many solution level cache to be re-used. /// /// the primary solution can be updated in 2 ways. /// first, host will keep track of primary solution changes in host, and call OOP to synch to latest time to time. /// second, engine keeps track of whether a certain request is for primary solution or not, and if it is, /// it let that request to update primary solution cache to latest. /// /// these 2 are complimentary to each other. #1 makes OOP's primary solution to be ready for next call (push), #2 makes OOP's primary /// solution be not stale as much as possible. (pull) /// </summary> private async Task <Solution> CreateSolution_NoLockAsync( AssetProvider assetProvider, Checksum solutionChecksum, bool fromPrimaryBranch, int workspaceVersion, Solution baseSolution, CancellationToken cancellationToken) { try { var updater = new SolutionCreator(Services.HostServices, assetProvider, baseSolution, cancellationToken); // check whether solution is update to the given base solution if (await updater.IsIncrementalUpdateAsync(solutionChecksum).ConfigureAwait(false)) { // create updated solution off the baseSolution var solution = await updater.CreateSolutionAsync(solutionChecksum).ConfigureAwait(false); if (fromPrimaryBranch) { // if the solutionChecksum is for primary branch, update primary workspace cache with the solution return(UpdateSolutionIfPossible(solution, workspaceVersion)); } // otherwise, just return the solution return(solution); } // we need new solution. bulk sync all asset for the solution first. await assetProvider.SynchronizeSolutionAssetsAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); // get new solution info and options var(solutionInfo, options) = await assetProvider.CreateSolutionInfoAndOptionsAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); if (fromPrimaryBranch) { // if the solutionChecksum is for primary branch, update primary workspace cache with new solution if (TrySetCurrentSolution(solutionInfo, workspaceVersion, options, out var solution)) { return(solution); } } // otherwise, just return new solution var workspace = new TemporaryWorkspace(Services.HostServices, WorkspaceKind.RemoteTemporaryWorkspace, solutionInfo, options); return(workspace.CurrentSolution); } catch (Exception e) when(FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { throw ExceptionUtilities.Unreachable; } }
internal static async Task AssertChecksumsAsync( AssetProvider assetService, Checksum checksumFromRequest, Solution solutionFromScratch, Solution incrementalSolutionBuilt) { #if DEBUG var sb = new StringBuilder(); var allChecksumsFromRequest = await GetAllChildrenChecksumsAsync(checksumFromRequest).ConfigureAwait(false); var assetMapFromNewSolution = await solutionFromScratch.GetAssetMapAsync(CancellationToken.None).ConfigureAwait(false); var assetMapFromIncrementalSolution = await incrementalSolutionBuilt.GetAssetMapAsync(CancellationToken.None).ConfigureAwait(false); // check 4 things // 1. first see if we create new solution from scratch, it works as expected (indicating a bug in incremental update) var mismatch1 = assetMapFromNewSolution.Where(p => !allChecksumsFromRequest.Contains(p.Key)).ToList(); AppendMismatch(mismatch1, "assets only in new solutoin but not in the request", sb); // 2. second check what items is mismatching for incremental solution var mismatch2 = assetMapFromIncrementalSolution.Where(p => !allChecksumsFromRequest.Contains(p.Key)).ToList(); AppendMismatch(mismatch2, "assets only in the incremental solution but not in the request", sb); // 3. check whether solution created from scratch and incremental one have any mismatch var mismatch3 = assetMapFromNewSolution.Where(p => !assetMapFromIncrementalSolution.ContainsKey(p.Key)).ToList(); AppendMismatch(mismatch3, "assets only in new solution but not in incremental solution", sb); var mismatch4 = assetMapFromIncrementalSolution.Where(p => !assetMapFromNewSolution.ContainsKey(p.Key)).ToList(); AppendMismatch(mismatch4, "assets only in incremental solution but not in new solution", sb); // 4. see what item is missing from request var mismatch5 = await GetAssetFromAssetServiceAsync(allChecksumsFromRequest.Except(assetMapFromNewSolution.Keys)).ConfigureAwait(false); AppendMismatch(mismatch5, "assets only in the request but not in new solution", sb); var mismatch6 = await GetAssetFromAssetServiceAsync(allChecksumsFromRequest.Except(assetMapFromIncrementalSolution.Keys)).ConfigureAwait(false); AppendMismatch(mismatch6, "assets only in the request but not in incremental solution", sb); var result = sb.ToString(); if (result.Length > 0) { Logger.Log(FunctionId.SolutionCreator_AssetDifferences, result); Debug.Fail("Differences detected in solution checksum: " + result); }
public async Task UpdatePrimaryBranchSolutionAsync(AssetProvider assetProvider, Checksum solutionChecksum, int workspaceVersion, CancellationToken cancellationToken) { var currentSolution = CurrentSolution; var currentSolutionChecksum = await currentSolution.State.GetChecksumAsync(cancellationToken).ConfigureAwait(false); if (currentSolutionChecksum == solutionChecksum) { return; } using (await _availableSolutionsGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { var solution = await CreateSolution_NoLockAsync(assetProvider, solutionChecksum, fromPrimaryBranch : true, workspaceVersion, currentSolution, cancellationToken).ConfigureAwait(false); _primaryBranchSolutionWithChecksum = Tuple.Create(solutionChecksum, solution); } }
public async ValueTask <Solution> GetSolutionAsync( AssetProvider assetProvider, Checksum solutionChecksum, bool fromPrimaryBranch, int workspaceVersion, CancellationToken cancellationToken ) { var availableSolution = TryGetAvailableSolution(solutionChecksum); if (availableSolution != null) { return(availableSolution); } // make sure there is always only one that creates a new solution using ( await _availableSolutionsGate .DisposableWaitAsync(cancellationToken) .ConfigureAwait(false) ) { availableSolution = TryGetAvailableSolution(solutionChecksum); if (availableSolution != null) { return(availableSolution); } var solution = await CreateSolution_NoLockAsync( assetProvider, solutionChecksum, fromPrimaryBranch, workspaceVersion, CurrentSolution, cancellationToken ) .ConfigureAwait(false); _lastRequestedSolutionWithChecksum = Tuple.Create(solutionChecksum, solution); return(solution); } }
public ChecksumSynchronizer(AssetProvider assetProvider) { _assetProvider = assetProvider; }
public SolutionService(AssetProvider assetProvider) => AssetProvider = assetProvider;
private static async Task <Dictionary <DocumentId, DocumentStateChecksums> > GetDocumentMapAsync(AssetProvider assetProvider, HashSet <Checksum> documents, CancellationToken cancellationToken) { var map = new Dictionary <DocumentId, DocumentStateChecksums>(); var documentChecksums = await assetProvider.GetAssetsAsync <DocumentStateChecksums>(documents, cancellationToken).ConfigureAwait(false); var infos = await assetProvider.GetAssetsAsync <DocumentInfo.DocumentAttributes>(documentChecksums.Select(p => p.Item2.Info), cancellationToken).ConfigureAwait(false); foreach (var kv in documentChecksums) { Debug.Assert(assetProvider.EnsureCacheEntryIfExists(kv.Item2.Info), "Expected the prior call to GetAssetsAsync to obtain all items for this loop."); var info = await assetProvider.GetAssetAsync <DocumentInfo.DocumentAttributes>(kv.Item2.Info, cancellationToken).ConfigureAwait(false); map.Add(info.Id, kv.Item2); } return(map); }
public SolutionCreator(HostServices hostServices, AssetProvider assetService, Solution baseSolution) { _hostServices = hostServices; _assetProvider = assetService; _baseSolution = baseSolution; }
internal static async Task AssertChecksumsAsync( AssetProvider assetService, Checksum checksumFromRequest, Solution solutionFromScratch, Solution incrementalSolutionBuilt) { #if DEBUG var sb = new StringBuilder(); var allChecksumsFromRequest = await GetAllChildrenChecksumsAsync(checksumFromRequest).ConfigureAwait(false); var assetMapFromNewSolution = await solutionFromScratch.GetAssetMapAsync(includeProjectCones : true, CancellationToken.None).ConfigureAwait(false); var assetMapFromIncrementalSolution = await incrementalSolutionBuilt.GetAssetMapAsync(includeProjectCones : true, CancellationToken.None).ConfigureAwait(false); // check 4 things // 1. first see if we create new solution from scratch, it works as expected (indicating a bug in incremental update) var mismatch1 = assetMapFromNewSolution.Where(p => !allChecksumsFromRequest.Contains(p.Key)).ToList(); AppendMismatch(mismatch1, "assets only in new solutoin but not in the request", sb); // 2. second check what items is mismatching for incremental solution var mismatch2 = assetMapFromIncrementalSolution.Where(p => !allChecksumsFromRequest.Contains(p.Key)).ToList(); AppendMismatch(mismatch2, "assets only in the incremental solution but not in the request", sb); // 3. check whether solution created from scratch and incremental one have any mismatch var mismatch3 = assetMapFromNewSolution.Where(p => !assetMapFromIncrementalSolution.ContainsKey(p.Key)).ToList(); AppendMismatch(mismatch3, "assets only in new solution but not in incremental solution", sb); var mismatch4 = assetMapFromIncrementalSolution.Where(p => !assetMapFromNewSolution.ContainsKey(p.Key)).ToList(); AppendMismatch(mismatch4, "assets only in incremental solution but not in new solution", sb); // 4. see what item is missing from request var mismatch5 = await GetAssetFromAssetServiceAsync(allChecksumsFromRequest.Except(assetMapFromNewSolution.Keys)).ConfigureAwait(false); AppendMismatch(mismatch5, "assets only in the request but not in new solution", sb); var mismatch6 = await GetAssetFromAssetServiceAsync(allChecksumsFromRequest.Except(assetMapFromIncrementalSolution.Keys)).ConfigureAwait(false); AppendMismatch(mismatch6, "assets only in the request but not in incremental solution", sb); AppendOptionSets(); var result = sb.ToString(); if (result.Length > 0) { Logger.Log(FunctionId.SolutionCreator_AssetDifferences, result); Debug.Fail("Differences detected in solution checksum: " + result); } return; void AppendOptionSets() { var seenChecksums = new HashSet <Checksum>(); foreach (var list in new[] { mismatch1, mismatch2, mismatch3, mismatch4, mismatch5, mismatch6 }) { foreach (var(checksum, val) in list) { if (seenChecksums.Add(checksum) && val is SerializableOptionSet optionSet) { sb.AppendLine($"Checksum: {checksum}"); sb.AppendLine("Options:"); sb.AppendLine(optionSet.GetDebugString()); sb.AppendLine(); } } } }