File: ProjectSystem\RuleSets\VisualStudioRuleSetManager.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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.
 
#nullable disable
 
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ProjectSystem;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Workspaces.ProjectSystem;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
 
internal sealed partial class VisualStudioRuleSetManager : IRuleSetManager
{
    private readonly IThreadingContext _threadingContext;
    private readonly IFileChangeWatcher _fileChangeWatcher;
    private readonly IAsynchronousOperationListener _listener;
 
    private readonly ReferenceCountedDisposableCache<string, RuleSetFile> _ruleSetFileMap = new();
 
    public VisualStudioRuleSetManager(
        IThreadingContext threadingContext,
        IFileChangeWatcher fileChangeWatcher,
        IAsynchronousOperationListener listener)
    {
        _threadingContext = threadingContext;
        _fileChangeWatcher = fileChangeWatcher;
        _listener = listener;
    }
 
    public IReferenceCountedDisposable<ICacheEntry<string, IRuleSetFile>> GetOrCreateRuleSet(string ruleSetFileFullPath)
    {
        var cacheEntry = _ruleSetFileMap.GetOrCreate(ruleSetFileFullPath, static (ruleSetFileFullPath, self) => new RuleSetFile(ruleSetFileFullPath, self), this);
 
        // Call InitializeFileTracking outside the lock inside ReferenceCountedDisposableCache, so we don't have requests
        // for other files blocking behind the initialization of this one. RuleSetFile itself will ensure InitializeFileTracking is locked as appropriate.
        cacheEntry.Target.Value.InitializeFileTracking(_fileChangeWatcher);
 
        return cacheEntry;
    }
 
    private void StopTrackingRuleSetFile(RuleSetFile ruleSetFile)
    {
        // We can arrive here in one of two situations:
        // 
        // 1. The underlying RuleSetFile was disposed by all consumers, and we can try cleaning up our weak reference. This is purely an optimization
        //    to avoid the key/value pair being unnecessarily held.
        // 2. The RuleSetFile was modified, and we want to get rid of our cache now. Anybody still holding onto the values will dispose at their leaisure,
        //    but it won't really matter anyways since the Dispose() that cleans up file trackers is already done.
        //
        // In either case, we can just be lazy and remove the key/value pair. It's possible in the mean time that the rule set had already been removed
        // (perhaps by a file change), and we're removing a live instance. This is fine, as this doesn't affect correctness: we consider this to be a cache
        // and if two callers got different copies that's fine. We *could* fetch the item out of the dictionary, check if the item is strong and then compare,
        // but that seems overkill.
        _ruleSetFileMap.Evict(ruleSetFile.FilePath);
    }
}