提交 d8ab4e45 编写于 作者: H Heejae Chang

turn on esent only on a large solution. otherwise, just hold everything in memory.

threshold to turn on esent is 50MB > solution source size. esent will be turned on dynamically, once turned on, it will turned off again when solution is closed.

this PR addresses #910
上级 30b9379d
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Composition;
using Microsoft.CodeAnalysis.Host;
......
......@@ -102,25 +102,41 @@ public bool AddOrReplace(WorkItem item)
public void RequestCancellationOnRunningTasks()
{
List<CancellationTokenSource> cancellations;
lock (_gate)
{
// request to cancel all running works
CancelAll_NoLock();
cancellations = CancelAll_NoLock();
}
RaiseCancellation_NoLock(cancellations);
}
public void Dispose()
{
List<CancellationTokenSource> cancellations;
lock (_gate)
{
// here we don't need to care about progress reporter since
// it will be only called when host is shutting down.
// we do the below since we want to kill any pending tasks
Dispose_NoLock();
CancelAll_NoLock();
cancellations = CancelAll_NoLock();
}
RaiseCancellation_NoLock(cancellations);
}
private static void RaiseCancellation_NoLock(List<CancellationTokenSource> cancellations)
{
if (cancellations == null)
{
return;
}
// cancel can cause outer code to be run inlined, run it outside of the lock.
cancellations.Do(s => s.Cancel());
}
private bool HasAnyWork_NoLock
......@@ -131,22 +147,21 @@ private bool HasAnyWork_NoLock
}
}
private void CancelAll_NoLock()
private List<CancellationTokenSource> CancelAll_NoLock()
{
// nothing to do
if (_cancellationMap.Count == 0)
{
return;
return null;
}
// make a copy
var cancellations = _cancellationMap.Values.ToList();
// it looks like Cancel can cause some code to run at the same thread, which can cause _cancellationMap to be changed.
// make a copy of the list and call cancellation
cancellations.Do(s => s.Cancel());
// clear cancellation map
_cancellationMap.Clear();
return cancellations;
}
protected void Cancel_NoLock(object key)
......
......@@ -57,13 +57,14 @@ protected override async Task ExecuteAsync()
try
{
// we wait for global operation, higher and normal priority processor to finish its working
await Task.WhenAll(this.Processor._highPriorityProcessor.Running,
await Task.WhenAll(
this.Processor._highPriorityProcessor.Running,
this.Processor._normalPriorityProcessor.Running,
GlobalOperationWaitAsync()).ConfigureAwait(false);
// process any available project work, preferring the active project.
CancellationTokenSource projectCancellation;
// process any available project work, preferring the active project.
WorkItem workItem;
CancellationTokenSource projectCancellation;
if (_workItemQueue.TryTakeAnyWork(this.Processor.GetActiveProject(), out workItem, out projectCancellation))
{
await ProcessProjectAsync(this.Analyzers, workItem, projectCancellation).ConfigureAwait(false);
......
......@@ -60,7 +60,8 @@ private partial class WorkCoordinator
var entireProjectWorkerBackOffTimeSpanInMS = _optionService.GetOption(SolutionCrawlerOptions.EntireProjectWorkerBackOffTimeSpanInMS);
_documentAndProjectWorkerProcessor = new IncrementalAnalyzerProcessor(
listener, analyzerProviders, _registration, activeFileBackOffTimeSpanInMS, allFilesWorkerBackOffTimeSpanInMS, entireProjectWorkerBackOffTimeSpanInMS, _shutdownToken);
listener, analyzerProviders, _registration,
activeFileBackOffTimeSpanInMS, allFilesWorkerBackOffTimeSpanInMS, entireProjectWorkerBackOffTimeSpanInMS, _shutdownToken);
var semanticBackOffTimeSpanInMS = _optionService.GetOption(SolutionCrawlerOptions.SemanticChangeBackOffTimeSpanInMS);
var projectBackOffTimeSpanInMS = _optionService.GetOption(SolutionCrawlerOptions.ProjectPropagationBackOffTimeSpanInMS);
......@@ -244,12 +245,16 @@ private void ProcessEvents(WorkspaceChangeEventArgs args, IAsyncToken asyncToken
private void OnDocumentOpened(object sender, DocumentEventArgs e)
{
EnqueueWorkItem(e.Document, InvocationReasons.DocumentOpened);
var asyncToken = _listener.BeginAsyncOperation("OnDocumentOpened");
_eventProcessingQueue.ScheduleTask(
() => EnqueueWorkItem(e.Document, InvocationReasons.DocumentOpened), _shutdownToken).CompletesAsyncOperation(asyncToken);
}
private void OnDocumentClosed(object sender, DocumentEventArgs e)
{
EnqueueWorkItem(e.Document, InvocationReasons.DocumentClosed);
var asyncToken = _listener.BeginAsyncOperation("OnDocumentClosed");
_eventProcessingQueue.ScheduleTask(
() => EnqueueWorkItem(e.Document, InvocationReasons.DocumentClosed), _shutdownToken).CompletesAsyncOperation(asyncToken);
}
private void ProcessDocumentEvent(WorkspaceChangeEventArgs e, IAsyncToken asyncToken)
......
......@@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.VisualStudio.LanguageServices.Implementation;
using Microsoft.VisualStudio.LanguageServices.Implementation.SolutionSize;
using Xunit;
namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices
......@@ -356,6 +357,7 @@ private Solution CreateOrOpenSolution()
private IPersistentStorage GetStorage(Solution solution)
{
var storage = new PersistentStorageService(_persistentEnabledOptionService, testing: true).GetStorage(solution);
Assert.NotEqual(PersistentStorageService.NoOpPersistentStorageInstance, storage);
return storage;
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Concurrent;
using System.Composition;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionSize
{
/// <summary>
/// Track approximate solution size.
/// </summary>
[Export]
[ExportIncrementalAnalyzerProvider(WorkspaceKind.Host), Shared]
internal class SolutionSizeTracker : IIncrementalAnalyzerProvider
{
private readonly IncrementalAnalyzer tracker = new IncrementalAnalyzer();
public long GetSolutionSize(Workspace workspace, SolutionId solutionId)
{
var vsWorkspace = workspace as VisualStudioWorkspaceImpl;
if (vsWorkspace == null)
{
return -1;
}
return tracker.GetSolutionSize(solutionId);
}
IIncrementalAnalyzer IIncrementalAnalyzerProvider.CreateIncrementalAnalyzer(Workspace workspace)
{
var vsWorkspace = workspace as VisualStudioWorkspaceImpl;
if (vsWorkspace == null)
{
return null;
}
return tracker;
}
private class IncrementalAnalyzer : IIncrementalAnalyzer
{
private readonly ConcurrentDictionary<ProjectId, long> _map = new ConcurrentDictionary<ProjectId, long>(concurrencyLevel: 2, capacity: 10);
private SolutionId _solutionId;
private long _size;
public long GetSolutionSize(SolutionId solutionId)
{
if (_solutionId != solutionId)
{
return -1;
}
return _size;
}
public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken)
{
if (_solutionId != solution.Id)
{
_map.Clear();
_solutionId = solution.Id;
}
return SpecializedTasks.EmptyTask;
}
public async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken)
{
var sum = await GetProjectSizeAsync(project, cancellationToken).ConfigureAwait(false);
_map.AddOrUpdate(project.Id, sum, (id, existing) => sum);
_size = _map.Values.Sum(v => v);
}
public void RemoveProject(ProjectId projectId)
{
long unused;
_map.TryRemove(projectId, out unused);
_size = _map.Values.Sum(v => v);
}
private static async Task<long> GetProjectSizeAsync(Project project, CancellationToken cancellationToken)
{
if (project == null)
{
return 0;
}
var sum = 0L;
foreach (var document in project.Documents)
{
sum += await GetDocumentSizeAsync(document, cancellationToken).ConfigureAwait(false);
}
return sum;
}
private static async Task<long> GetDocumentSizeAsync(Document document, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (document == null)
{
return 0;
}
var result = GetFileSize(document.FilePath);
if (result >= 0)
{
return result;
}
// not a physical file, in that case, use text as a fallback.
var text = await document.GetTextAsync(CancellationToken.None).ConfigureAwait(false);
return text.Length;
}
private static long GetFileSize(string filepath)
{
if (filepath == null)
{
return -1;
}
try
{
// just to reduce exception thrown
if (!File.Exists(filepath))
{
return -1;
}
var info = new FileInfo(filepath);
return info.Length;
}
catch
{
return -1;
}
}
#region Not Used
public Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken)
{
return SpecializedTasks.EmptyTask;
}
public Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken)
{
return SpecializedTasks.EmptyTask;
}
public Task DocumentOpenAsync(Document document, CancellationToken cancellationToken)
{
return SpecializedTasks.EmptyTask;
}
public Task DocumentResetAsync(Document document, CancellationToken cancellationToken)
{
return SpecializedTasks.EmptyTask;
}
public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs e)
{
return false;
}
public void RemoveDocument(DocumentId documentId)
{
}
#endregion
}
}
}
......@@ -29,7 +29,6 @@ internal partial class EsentPersistentStorage : AbstractPersistentStorage
base(optionService, workingFolderPath, solutionFilePath, disposer)
{
// solution must exist in disk. otherwise, we shouldn't be here at all.
Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(workingFolderPath));
Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(solutionFilePath));
var databaseFile = GetDatabaseFile(workingFolderPath);
......@@ -47,6 +46,13 @@ internal partial class EsentPersistentStorage : AbstractPersistentStorage
_esentStorage = new EsentStorage(databaseFile, enablePerformanceMonitor);
}
public static string GetDatabaseFile(string workingFolderPath)
{
Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(workingFolderPath));
return Path.Combine(workingFolderPath, StorageExtension, PersistentStorageFileName);
}
public void Initialize()
{
_esentStorage.Initialize();
......@@ -262,11 +268,6 @@ public override void Close()
_esentStorage.Close();
}
private string GetDatabaseFile(string workingFolderPath)
{
return Path.Combine(workingFolderPath, StorageExtension, PersistentStorageFileName);
}
private bool TryGetUniqueNameId(string name, out int id)
{
return TryGetUniqueId(name, false, out id);
......
......@@ -4,20 +4,29 @@
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.VisualStudio.LanguageServices.Implementation.SolutionSize;
namespace Microsoft.VisualStudio.LanguageServices.Implementation
{
[ExportWorkspaceServiceFactory(typeof(IPersistentStorageService), ServiceLayer.Host), Shared]
internal class PersistenceServiceFactory : IWorkspaceServiceFactory
{
private readonly SolutionSizeTracker _solutionSizeTracker;
private IPersistentStorageService _singleton;
[ImportingConstructor]
public PersistenceServiceFactory(SolutionSizeTracker solutionSizeTracker)
{
_solutionSizeTracker = solutionSizeTracker;
}
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
if (_singleton == null)
{
var optionService = workspaceServices.GetService<IOptionService>();
System.Threading.Interlocked.CompareExchange(ref _singleton, new PersistentStorageService(optionService), null);
System.Threading.Interlocked.CompareExchange(ref _singleton, new PersistentStorageService(optionService, _solutionSizeTracker), null);
}
return _singleton;
......
......@@ -7,9 +7,11 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.Isam.Esent.Interop;
using Microsoft.VisualStudio.LanguageServices.Implementation.Esent;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.Implementation.SolutionSize;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation
......@@ -20,9 +22,16 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation
/// </summary>
internal partial class PersistentStorageService : IPersistentStorageService
{
/// <summary>
/// threshold to start to use esent (50MB)
/// </summary>
private const int SolutionSizeThreshold = 50 * 1024 * 1024;
internal static readonly IPersistentStorage NoOpPersistentStorageInstance = new NoOpPersistentStorage();
private readonly IOptionService _optionService;
private readonly SolutionSizeTracker _solutionSizeTracker;
private readonly object _lookupAccessLock;
private readonly Dictionary<string, AbstractPersistentStorage> _lookup;
private readonly bool _testing;
......@@ -32,9 +41,12 @@ internal partial class PersistentStorageService : IPersistentStorageService
private SolutionId _primarySolutionId;
private AbstractPersistentStorage _primarySolutionStorage;
public PersistentStorageService(IOptionService optionService)
public PersistentStorageService(
IOptionService optionService,
SolutionSizeTracker solutoinSizeTracker)
{
_optionService = optionService;
_solutionSizeTracker = solutoinSizeTracker;
_lookupAccessLock = new object();
_lookup = new Dictionary<string, AbstractPersistentStorage>();
......@@ -50,9 +62,13 @@ public PersistentStorageService(IOptionService optionService, bool testing) : th
_testing = true;
}
public PersistentStorageService(IOptionService optionService) : this(optionService, null)
{
}
public IPersistentStorage GetStorage(Solution solution)
{
if (solution.FilePath == null)
if (!ShouldUseEsent(solution))
{
return NoOpPersistentStorageInstance;
}
......@@ -78,21 +94,25 @@ public IPersistentStorage GetStorage(Solution solution)
return NoOpPersistentStorageInstance;
}
return GetStorage(solution, workingFolderPath);
}
private IPersistentStorage GetStorage(Solution solution, string workingFolderPath)
{
lock (_lookupAccessLock)
{
// see whether we have something we can use
AbstractPersistentStorage storage;
if (_lookup.TryGetValue(solution.FilePath, out storage))
{
// previous attempt to create esent storage failed, don't try again within same VS session
// just don't use esent within this same vs session
if (storage == null)
// previous attempt to create esent storage failed.
if (storage == null && !SolutionSizeAboveThreshold(solution))
{
return NoOpPersistentStorageInstance;
}
// everything seems right, use what we have
if (storage.WorkingFolderPath == workingFolderPath)
if (storage?.WorkingFolderPath == workingFolderPath)
{
storage.AddRefUnsafe();
return storage;
......@@ -103,6 +123,13 @@ public IPersistentStorage GetStorage(Solution solution)
// remove existing one
_lookup.Remove(solution.FilePath);
var dbFile = EsentPersistentStorage.GetDatabaseFile(workingFolderPath);
if (!File.Exists(dbFile) && !SolutionSizeAboveThreshold(solution))
{
_lookup.Add(solution.FilePath, storage);
return NoOpPersistentStorageInstance;
}
// try create new one
storage = TryCreateEsentStorage(workingFolderPath, solution.FilePath);
_lookup.Add(solution.FilePath, storage);
......@@ -119,6 +146,38 @@ public IPersistentStorage GetStorage(Solution solution)
}
}
private bool ShouldUseEsent(Solution solution)
{
if (_testing)
{
return true;
}
// we only use esent for primary solution. (Ex, forked solution will not use esent)
if (solution.BranchId != solution.Workspace.PrimaryBranchId || solution.FilePath == null)
{
return false;
}
return true;
}
private bool SolutionSizeAboveThreshold(Solution solution)
{
if (_testing)
{
return true;
}
if (_solutionSizeTracker == null)
{
return false;
}
var size = _solutionSizeTracker.GetSolutionSize(solution.Workspace, solution.Id);
return size > SolutionSizeThreshold;
}
private void RegisterPrimarySolutionStorageIfNeeded(Solution solution, AbstractPersistentStorage storage)
{
if (_primarySolutionStorage != null || solution.Id != _primarySolutionId)
......@@ -141,7 +200,6 @@ private string GetWorkingFolderPath(Solution solution)
var vsWorkspace = solution.Workspace as VisualStudioWorkspaceImpl;
if (vsWorkspace == null)
{
// we can't get to working path.
return null;
}
......
......@@ -42,6 +42,7 @@
<Compile Include="Implementation\Options\ExportOptionSerializerAttribute.cs" />
<Compile Include="Implementation\ProjectSystem\Interop\IVsUndoState.cs" />
<Compile Include="Implementation\Serialization\AssemblySerializationInfoService.cs" />
<Compile Include="Implementation\SolutionSize\SolutionSizeTracker.cs" />
<Compile Include="Implementation\TableDataSource\MiscDiagnosticTableControlEventProcessorProvider.cs" />
<Compile Include="Implementation\TableDataSource\MiscellaneousDiagnosticListTable.cs" />
<Compile Include="Implementation\TableDataSource\MiscellaneousTodoListTable.cs" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册