diff --git a/src/EditorFeatures/Core/EditorFeatures.csproj b/src/EditorFeatures/Core/EditorFeatures.csproj index a76cdd24561225a3825b500602058fbc1f0224c9..1d281adf49a56afec46a31c3180841b9a6790711 100644 --- a/src/EditorFeatures/Core/EditorFeatures.csproj +++ b/src/EditorFeatures/Core/EditorFeatures.csproj @@ -53,22 +53,36 @@ true - false + + false + true - false + + false + - false + + false + - false + + false + - false - false - false + + false + + + false + + + false + @@ -651,8 +665,8 @@ - - + + diff --git a/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheService.SimpleMRUCache.cs b/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheService.SimpleMRUCache.cs new file mode 100644 index 0000000000000000000000000000000000000000..680f736105eaa72911c34ef0ad24fcafa0ed2fde --- /dev/null +++ b/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheService.SimpleMRUCache.cs @@ -0,0 +1,134 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces +{ + internal partial class ProjectCacheService : IProjectCacheHostService + { + private class SimpleMRUCache + { + private const int CacheSize = 3; + + private readonly Node[] nodes = new Node[CacheSize]; + + public bool Empty + { + get + { + for (var i = 0; i < nodes.Length; i++) + { + if (nodes[i].Data != null) + { + return false; + } + } + + return true; + } + } + + public void Touch(object instance) + { + var oldIndex = -1; + var oldTime = DateTime.UtcNow; + + for (var i = 0; i < nodes.Length; i++) + { + if (instance == nodes[i].Data) + { + nodes[i].LastTouched = DateTime.UtcNow; + return; + } + + if (oldTime >= nodes[i].LastTouched) + { + oldTime = nodes[i].LastTouched; + oldIndex = i; + } + } + + Contract.Requires(oldIndex >= 0); + nodes[oldIndex] = new Node(instance, DateTime.UtcNow); + } + + public void ClearExpiredItems(DateTime expirationTime) + { + for (var i = 0; i < nodes.Length; i++) + { + if (nodes[i].Data != null && nodes[i].LastTouched < expirationTime) + { + nodes[i] = default(Node); + } + } + } + + public void Clear() + { + Array.Clear(nodes, 0, nodes.Length); + } + + private struct Node + { + public readonly object Data; + public DateTime LastTouched; + + public Node(object data, DateTime lastTouched) + { + Data = data; + LastTouched = lastTouched; + } + } + } + + private class ImplicitCacheMonitor : IdleProcessor + { + private readonly ProjectCacheService _owner; + private readonly SemaphoreSlim _gate; + + public ImplicitCacheMonitor(ProjectCacheService owner, int backOffTimeSpanInMS) : + base(AggregateAsynchronousOperationListener.CreateEmptyListener(), + backOffTimeSpanInMS, + CancellationToken.None) + { + _owner = owner; + _gate = new SemaphoreSlim(0); + + Start(); + } + + protected override Task ExecuteAsync() + { + _owner.ClearExpiredImplicitCache(DateTime.UtcNow - TimeSpan.FromMilliseconds(BackOffTimeSpanInMS)); + + return SpecializedTasks.EmptyTask; + } + + public void Touch() + { + UpdateLastAccessTime(); + + if (_gate.CurrentCount == 0) + { + _gate.Release(); + } + } + + protected override Task WaitAsync(CancellationToken cancellationToken) + { + if (_owner.IsImplicitCacheEmpty) + { + return _gate.WaitAsync(cancellationToken); + } + + return SpecializedTasks.EmptyTask; + } + } + } +} diff --git a/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheService.cs b/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheService.cs new file mode 100644 index 0000000000000000000000000000000000000000..30b10df08f3085fa301b54ebd6f8978800be4db8 --- /dev/null +++ b/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheService.cs @@ -0,0 +1,195 @@ +// 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.Collections.Immutable; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces +{ + internal partial class ProjectCacheService : IProjectCacheHostService + { + internal const int ImplicitCacheSize = 3; + + private readonly object _gate = new object(); + + private readonly Workspace _workspace; + private readonly Dictionary _activeCaches = new Dictionary(); + + private readonly SimpleMRUCache _implicitCache = new SimpleMRUCache(); + private readonly ImplicitCacheMonitor _implicitCacheMonitor; + + public ProjectCacheService(Workspace workspace, int implicitCacheTimeout, bool forceCleanup = false) + { + _workspace = workspace; + + // forceCleanup is for testing + if (workspace?.Kind == WorkspaceKind.Host || forceCleanup) + { + // monitor implicit cache for host + _implicitCacheMonitor = new ImplicitCacheMonitor(this, implicitCacheTimeout); + } + } + + public bool IsImplicitCacheEmpty + { + get + { + lock (_gate) + { + return _implicitCache.Empty; + } + } + } + + public void ClearImplicitCache() + { + lock (_gate) + { + _implicitCache.Clear(); + } + } + + public void ClearExpiredImplicitCache(DateTime expirationTime) + { + lock (_gate) + { + _implicitCache.ClearExpiredItems(expirationTime); + } + } + + public IDisposable EnableCaching(ProjectId key) + { + lock (_gate) + { + Cache cache; + if (!_activeCaches.TryGetValue(key, out cache)) + { + cache = new Cache(this, key); + _activeCaches.Add(key, cache); + } + + cache.Count++; + return cache; + } + } + + public T CacheObjectIfCachingEnabledForKey(ProjectId key, object owner, T instance) where T : class + { + lock (_gate) + { + Cache cache; + if (_activeCaches.TryGetValue(key, out cache)) + { + cache.CreateStrongReference(owner, instance); + } + else if (!PartOfP2PReferences(key)) + { + _implicitCache.Touch(instance); + _implicitCacheMonitor?.Touch(); + } + + return instance; + } + } + + private bool PartOfP2PReferences(ProjectId key) + { + if (_activeCaches.Count == 0 || _workspace == null) + { + return false; + } + + var solution = _workspace.CurrentSolution; + var graph = solution.GetProjectDependencyGraph(); + + foreach (var projectId in _activeCaches.Keys) + { + // this should be cheap. graph is cached everytime project reference is updated. + var p2pReferences = (ImmutableHashSet)graph.GetProjectsThatThisProjectTransitivelyDependsOn(projectId); + if (p2pReferences.Contains(key)) + { + return true; + } + } + + return false; + } + + public T CacheObjectIfCachingEnabledForKey(ProjectId key, ICachedObjectOwner owner, T instance) where T : class + { + lock (_gate) + { + Cache cache; + if (owner.CachedObject == null && _activeCaches.TryGetValue(key, out cache)) + { + owner.CachedObject = instance; + cache.CreateOwnerEntry(owner); + } + + return instance; + } + } + + private void DisableCaching(ProjectId key, Cache cache) + { + lock (_gate) + { + cache.Count--; + if (cache.Count == 0) + { + _activeCaches.Remove(key); + cache.FreeOwnerEntries(); + } + } + } + + private class Cache : IDisposable + { + internal int Count; + private readonly ProjectCacheService _cacheService; + private readonly ProjectId _key; + private readonly ConditionalWeakTable _cache = new ConditionalWeakTable(); + private readonly List> _ownerObjects = new List>(); + + public Cache(ProjectCacheService cacheService, ProjectId key) + { + _cacheService = cacheService; + _key = key; + } + + public void Dispose() + { + _cacheService.DisableCaching(_key, this); + } + + internal void CreateStrongReference(object key, object instance) + { + object o; + if (!_cache.TryGetValue(key, out o)) + { + _cache.Add(key, instance); + } + } + + internal void CreateOwnerEntry(ICachedObjectOwner owner) + { + _ownerObjects.Add(new WeakReference(owner)); + } + + internal void FreeOwnerEntries() + { + foreach (var entry in _ownerObjects) + { + ICachedObjectOwner owner; + if (entry.TryGetTarget(out owner)) + { + owner.CachedObject = null; + } + } + } + } + } +} diff --git a/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.ProjectCacheService.cs b/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.ProjectCacheService.cs deleted file mode 100644 index b82b77bb8eaff0dab18aabdd4925b8eb1d77dcf5..0000000000000000000000000000000000000000 --- a/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.ProjectCacheService.cs +++ /dev/null @@ -1,198 +0,0 @@ -// 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.Collections.Immutable; -using System.Runtime.CompilerServices; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces -{ - internal partial class ProjectCacheHostServiceFactory : IWorkspaceServiceFactory - { - internal partial class ProjectCacheService : IProjectCacheHostService - { - internal const int ImplicitCacheSize = 3; - - private readonly object _gate = new object(); - - private readonly Workspace _workspace; - private readonly Dictionary _activeCaches = new Dictionary(); - - private readonly SimpleMRUCache _implicitCache = new SimpleMRUCache(); - private readonly ImplicitCacheMonitor _implicitCacheMonitor; - - public ProjectCacheService(Workspace workspace, int implicitCacheTimeout, bool forceCleanup = false) - { - _workspace = workspace; - - // forceCleanup is for testing - if (workspace?.Kind == WorkspaceKind.Host || forceCleanup) - { - // monitor implicit cache for host - _implicitCacheMonitor = new ImplicitCacheMonitor(this, implicitCacheTimeout); - } - } - - public bool IsImplicitCacheEmpty - { - get - { - lock (_gate) - { - return _implicitCache.Empty; - } - } - } - - public void ClearImplicitCache() - { - lock (_gate) - { - _implicitCache.Clear(); - } - } - - public void ClearExpiredImplicitCache(DateTime expirationTime) - { - lock (_gate) - { - _implicitCache.ClearExpiredItems(expirationTime); - } - } - - public IDisposable EnableCaching(ProjectId key) - { - lock (_gate) - { - Cache cache; - if (!_activeCaches.TryGetValue(key, out cache)) - { - cache = new Cache(this, key); - _activeCaches.Add(key, cache); - } - - cache.Count++; - return cache; - } - } - - public T CacheObjectIfCachingEnabledForKey(ProjectId key, object owner, T instance) where T : class - { - lock (_gate) - { - Cache cache; - if (_activeCaches.TryGetValue(key, out cache)) - { - cache.CreateStrongReference(owner, instance); - } - else if (!PartOfP2PReferences(key)) - { - _implicitCache.Touch(instance); - _implicitCacheMonitor?.Touch(); - } - - return instance; - } - } - - private bool PartOfP2PReferences(ProjectId key) - { - if (_activeCaches.Count == 0 || _workspace == null) - { - return false; - } - - var solution = _workspace.CurrentSolution; - var graph = solution.GetProjectDependencyGraph(); - - foreach (var projectId in _activeCaches.Keys) - { - // this should be cheap. graph is cached everytime project reference is updated. - var p2pReferences = (ImmutableHashSet)graph.GetProjectsThatThisProjectTransitivelyDependsOn(projectId); - if (p2pReferences.Contains(key)) - { - return true; - } - } - - return false; - } - - public T CacheObjectIfCachingEnabledForKey(ProjectId key, ICachedObjectOwner owner, T instance) where T : class - { - lock (_gate) - { - Cache cache; - if (owner.CachedObject == null && _activeCaches.TryGetValue(key, out cache)) - { - owner.CachedObject = instance; - cache.CreateOwnerEntry(owner); - } - - return instance; - } - } - - private void DisableCaching(ProjectId key, Cache cache) - { - lock (_gate) - { - cache.Count--; - if (cache.Count == 0) - { - _activeCaches.Remove(key); - cache.FreeOwnerEntries(); - } - } - } - - private class Cache : IDisposable - { - internal int Count; - private readonly ProjectCacheService _cacheService; - private readonly ProjectId _key; - private readonly ConditionalWeakTable _cache = new ConditionalWeakTable(); - private readonly List> _ownerObjects = new List>(); - - public Cache(ProjectCacheService cacheService, ProjectId key) - { - _cacheService = cacheService; - _key = key; - } - - public void Dispose() - { - _cacheService.DisableCaching(_key, this); - } - - internal void CreateStrongReference(object key, object instance) - { - object o; - if (!_cache.TryGetValue(key, out o)) - { - _cache.Add(key, instance); - } - } - - internal void CreateOwnerEntry(ICachedObjectOwner owner) - { - _ownerObjects.Add(new WeakReference(owner)); - } - - internal void FreeOwnerEntries() - { - foreach (var entry in _ownerObjects) - { - ICachedObjectOwner owner; - if (entry.TryGetTarget(out owner)) - { - owner.CachedObject = null; - } - } - } - } - } - } -} diff --git a/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.SimpleMRUCache.cs b/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.SimpleMRUCache.cs deleted file mode 100644 index 67c363ee33b8c758ffb04fe5a1cd9bfb15d0b38b..0000000000000000000000000000000000000000 --- a/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.SimpleMRUCache.cs +++ /dev/null @@ -1,138 +0,0 @@ -// 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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.SolutionCrawler; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces -{ - internal partial class ProjectCacheHostServiceFactory : IWorkspaceServiceFactory - { - internal partial class ProjectCacheService : IProjectCacheHostService - { - private class SimpleMRUCache - { - private const int CacheSize = 3; - - private readonly Node[] nodes = new Node[CacheSize]; - - public bool Empty - { - get - { - for (var i = 0; i < nodes.Length; i++) - { - if (nodes[i].Data != null) - { - return false; - } - } - - return true; - } - } - - public void Touch(object instance) - { - var oldIndex = -1; - var oldTime = DateTime.UtcNow; - - for (var i = 0; i < nodes.Length; i++) - { - if (instance == nodes[i].Data) - { - nodes[i].LastTouched = DateTime.UtcNow; - return; - } - - if (oldTime >= nodes[i].LastTouched) - { - oldTime = nodes[i].LastTouched; - oldIndex = i; - } - } - - Contract.Requires(oldIndex >= 0); - nodes[oldIndex] = new Node(instance, DateTime.UtcNow); - } - - public void ClearExpiredItems(DateTime expirationTime) - { - for (var i = 0; i < nodes.Length; i++) - { - if (nodes[i].Data != null && nodes[i].LastTouched < expirationTime) - { - nodes[i] = default(Node); - } - } - } - - public void Clear() - { - Array.Clear(nodes, 0, nodes.Length); - } - - private struct Node - { - public readonly object Data; - public DateTime LastTouched; - - public Node(object data, DateTime lastTouched) - { - Data = data; - LastTouched = lastTouched; - } - } - } - - private class ImplicitCacheMonitor : IdleProcessor - { - private readonly ProjectCacheService _owner; - private readonly SemaphoreSlim _gate; - - public ImplicitCacheMonitor(ProjectCacheService owner, int backOffTimeSpanInMS) : - base(AggregateAsynchronousOperationListener.CreateEmptyListener(), - backOffTimeSpanInMS, - CancellationToken.None) - { - _owner = owner; - _gate = new SemaphoreSlim(0); - - Start(); - } - - protected override Task ExecuteAsync() - { - _owner.ClearExpiredImplicitCache(DateTime.UtcNow - TimeSpan.FromMilliseconds(BackOffTimeSpanInMS)); - - return SpecializedTasks.EmptyTask; - } - - public void Touch() - { - UpdateLastAccessTime(); - - if (_gate.CurrentCount == 0) - { - _gate.Release(); - } - } - - protected override Task WaitAsync(CancellationToken cancellationToken) - { - if (_owner.IsImplicitCacheEmpty) - { - return _gate.WaitAsync(cancellationToken); - } - - return SpecializedTasks.EmptyTask; - } - } - } - } -} diff --git a/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.cs b/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.cs index e75580145c43a20968ee04125b738444061b9dd2..54f72f22b5a93cc9181a9ddbe3d1a3688aa6c270 100644 --- a/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.cs +++ b/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.cs @@ -15,88 +15,18 @@ internal partial class ProjectCacheHostServiceFactory : IWorkspaceServiceFactory public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { - var projectCacheService = new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS); - - var documentTrackingService = workspaceServices.GetService(); - - // Subscribe to events so that we can cache items from the active document's project - var manager = new ActiveProjectCacheManager(documentTrackingService, projectCacheService); - - // Subscribe to requests to clear the cache - var workspaceCacheService = workspaceServices.GetService(); - if (workspaceCacheService != null) - { - workspaceCacheService.CacheFlushRequested += (s, e) => manager.Clear(); - } + var service = new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS); // Also clear the cache when the solution is cleared or removed. workspaceServices.Workspace.WorkspaceChanged += (s, e) => { if (e.Kind == WorkspaceChangeKind.SolutionCleared || e.Kind == WorkspaceChangeKind.SolutionRemoved) { - manager.Clear(); + service.ClearImplicitCache(); } }; - return projectCacheService; - } - - private class ActiveProjectCacheManager - { - private readonly IDocumentTrackingService _documentTrackingService; - private readonly ProjectCacheService _projectCacheService; - private readonly object _guard = new object(); - - private ProjectId _mostRecentActiveProjectId; - private IDisposable _mostRecentCache; - - public ActiveProjectCacheManager(IDocumentTrackingService documentTrackingService, ProjectCacheService projectCacheService) - { - _documentTrackingService = documentTrackingService; - _projectCacheService = projectCacheService; - - if (documentTrackingService != null) - { - documentTrackingService.ActiveDocumentChanged += UpdateCache; - UpdateCache(null, documentTrackingService.GetActiveDocument()); - } - } - - private void UpdateCache(object sender, DocumentId activeDocument) - { - lock (_guard) - { - if (activeDocument != null && activeDocument.ProjectId != _mostRecentActiveProjectId) - { - ClearMostRecentCache_NoLock(); - _mostRecentCache = _projectCacheService.EnableCaching(activeDocument.ProjectId); - _mostRecentActiveProjectId = activeDocument.ProjectId; - } - } - } - - public void Clear() - { - lock (_guard) - { - // clear most recent cache - ClearMostRecentCache_NoLock(); - - // clear implicit cache - _projectCacheService.ClearImplicitCache(); - } - } - - private void ClearMostRecentCache_NoLock() - { - if (_mostRecentCache != null) - { - _mostRecentCache.Dispose(); - _mostRecentCache = null; - } - - _mostRecentActiveProjectId = null; - } + return service; } } } diff --git a/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs index 236ff0a32e6886f9693a08dad7673e8409faface..32df6bc5a6a16f152290be41f253e06e3b8c8b03 100644 --- a/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs @@ -19,7 +19,7 @@ private void Test(Action compilations = new List(); - for (int i = 0; i < ProjectCacheHostServiceFactory.ProjectCacheService.ImplicitCacheSize + 1; i++) + for (int i = 0; i < ProjectCacheService.ImplicitCacheSize + 1; i++) { compilations.Add(CSharpCompilation.Create(i.ToString())); } @@ -194,8 +194,8 @@ public void TestEjectFromImplicitCache() var weakFirst = new WeakReference(compilations[0]); var weakLast = new WeakReference(compilations[compilations.Count - 1]); - var cache = new ProjectCacheHostServiceFactory.ProjectCacheService(null, int.MaxValue); - for (int i = 0; i < ProjectCacheHostServiceFactory.ProjectCacheService.ImplicitCacheSize + 1; i++) + var cache = new ProjectCacheService(null, int.MaxValue); + for (int i = 0; i < ProjectCacheService.ImplicitCacheSize + 1; i++) { cache.CacheObjectIfCachingEnabledForKey(ProjectId.CreateNewId(), (object)null, compilations[i]); } @@ -218,7 +218,7 @@ public void TestCacheCompilationTwice() var weak3 = new WeakReference(comp3); var weak1 = new WeakReference(comp1); - var cache = new ProjectCacheHostServiceFactory.ProjectCacheService(null, int.MaxValue); + var cache = new ProjectCacheService(null, int.MaxValue); var key = ProjectId.CreateNewId(); var owner = new object(); cache.CacheObjectIfCachingEnabledForKey(key, owner, comp1); diff --git a/src/VisualStudio/Core/Def/Implementation/DebuggerIntelliSense/DebuggerIntellisenseWorkspace.cs b/src/VisualStudio/Core/Def/Implementation/DebuggerIntelliSense/DebuggerIntellisenseWorkspace.cs index 782e3d3d57305c3d2dcf3b550a50341db1547392..3ead91791e2bcb8af63522d01c52a54f4b1b48fa 100644 --- a/src/VisualStudio/Core/Def/Implementation/DebuggerIntelliSense/DebuggerIntellisenseWorkspace.cs +++ b/src/VisualStudio/Core/Def/Implementation/DebuggerIntelliSense/DebuggerIntellisenseWorkspace.cs @@ -1,9 +1,7 @@ // 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 Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.ComponentModelHost; namespace Microsoft.VisualStudio.LanguageServices.Implementation.DebuggerIntelliSense { diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..039bfc18bdb19f734d151318f346fb9c2829ff70 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs @@ -0,0 +1,136 @@ +// 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.Composition; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.LanguageServices; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces +{ + [ExportWorkspaceServiceFactory(typeof(IProjectCacheHostService), ServiceLayer.Host)] + [Shared] + internal partial class VisualStudioProjectCacheHostServiceFactory : IWorkspaceServiceFactory + { + private const int ImplicitCacheTimeoutInMS = 10000; + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + { + // we support active document tracking only for visual studio workspace host. + if (workspaceServices.Workspace is VisualStudioWorkspace) + { + return GetVisualStudioProjectCache(workspaceServices); + } + + return GetMiscProjectCache(workspaceServices); + } + + private static IWorkspaceService GetMiscProjectCache(HostWorkspaceServices workspaceServices) + { + var projectCacheService = new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS); + + var workspaceCacheService = workspaceServices.GetService(); + if (workspaceCacheService != null) + { + workspaceCacheService.CacheFlushRequested += (s, e) => projectCacheService.ClearImplicitCache(); + } + + // Also clear the cache when the solution is cleared or removed. + workspaceServices.Workspace.WorkspaceChanged += (s, e) => + { + if (e.Kind == WorkspaceChangeKind.SolutionCleared || e.Kind == WorkspaceChangeKind.SolutionRemoved) + { + projectCacheService.ClearImplicitCache(); + } + }; + + return projectCacheService; + } + + private static IWorkspaceService GetVisualStudioProjectCache(HostWorkspaceServices workspaceServices) + { + var projectCacheService = new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS); + + var documentTrackingService = workspaceServices.GetService(); + + // Subscribe to events so that we can cache items from the active document's project + var manager = new ActiveProjectCacheManager(documentTrackingService, projectCacheService); + + // Subscribe to requests to clear the cache + var workspaceCacheService = workspaceServices.GetService(); + if (workspaceCacheService != null) + { + workspaceCacheService.CacheFlushRequested += (s, e) => manager.Clear(); + } + + // Also clear the cache when the solution is cleared or removed. + workspaceServices.Workspace.WorkspaceChanged += (s, e) => + { + if (e.Kind == WorkspaceChangeKind.SolutionCleared || e.Kind == WorkspaceChangeKind.SolutionRemoved) + { + manager.Clear(); + } + }; + + return projectCacheService; + } + + private class ActiveProjectCacheManager + { + private readonly IDocumentTrackingService _documentTrackingService; + private readonly ProjectCacheService _projectCacheService; + private readonly object _guard = new object(); + + private ProjectId _mostRecentActiveProjectId; + private IDisposable _mostRecentCache; + + public ActiveProjectCacheManager(IDocumentTrackingService documentTrackingService, ProjectCacheService projectCacheService) + { + _documentTrackingService = documentTrackingService; + _projectCacheService = projectCacheService; + + if (documentTrackingService != null) + { + documentTrackingService.ActiveDocumentChanged += UpdateCache; + UpdateCache(null, documentTrackingService.GetActiveDocument()); + } + } + + private void UpdateCache(object sender, DocumentId activeDocument) + { + lock (_guard) + { + if (activeDocument != null && activeDocument.ProjectId != _mostRecentActiveProjectId) + { + ClearMostRecentCache_NoLock(); + _mostRecentCache = _projectCacheService.EnableCaching(activeDocument.ProjectId); + _mostRecentActiveProjectId = activeDocument.ProjectId; + } + } + } + + public void Clear() + { + lock (_guard) + { + // clear most recent cache + ClearMostRecentCache_NoLock(); + + // clear implicit cache + _projectCacheService.ClearImplicitCache(); + } + } + + private void ClearMostRecentCache_NoLock() + { + if (_mostRecentCache != null) + { + _mostRecentCache.Dispose(); + _mostRecentCache = null; + } + + _mostRecentActiveProjectId = null; + } + } + } +} diff --git a/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj b/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj index 00240ab73678736e5f0b6bc6241f33a038717393..63869bb3b624897ed1020832feb721ca54826d20 100644 --- a/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj +++ b/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj @@ -90,6 +90,7 @@ + @@ -225,31 +226,65 @@ - false - false - false + + false + + + false + + + false + - false - false - false - false - false + + false + + + false + + + false + + + false + + + false + - false + + false + - false - false + + false + + + false + - false - false - false - false - false - false + + false + + + false + + + false + + + false + + + false + + + false + True @@ -259,14 +294,30 @@ True - false - false - false - false - false - false - false - false + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + @@ -281,9 +332,15 @@ - false - false - false + + false + + + false + + + false + False