File: BackEnd\Components\SdkResolution\CachingSdkResolverLoader.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Concurrent;
using System.Collections.Generic;
using Microsoft.Build.Construction;
using Microsoft.Build.Framework;
 
namespace Microsoft.Build.BackEnd.SdkResolution
{
    /// <summary>
    /// A subclass of <see cref="SdkResolverLoader"/> which creates resolver manifests and SDK resolvers only once and
    /// then returns cached results.
    /// </summary>
    internal sealed class CachingSdkResolverLoader : SdkResolverLoader
    {
        /// <summary>
        /// Cached list of default resolvers. Set eagerly.
        /// </summary>
        private readonly IReadOnlyList<SdkResolver> _defaultResolvers;
 
        /// <summary>
        /// Cached manifest -> resolver dictionary. Populated lazily.
        /// </summary>
        private readonly ConcurrentDictionary<SdkResolverManifest, IReadOnlyList<SdkResolver>> _resolversByManifest = new();
 
        /// <summary>
        /// Cached list of all resolvers. Set lazily.
        /// </summary>
        private IReadOnlyList<SdkResolver>? _allResolvers;
 
        /// <summary>
        /// Cached list of all resolver manifests. Set lazily.
        /// </summary>
        private IReadOnlyList<SdkResolverManifest>? _resolversManifests;
 
        /// <summary>
        /// A lock object protecting <see cref="_allResolvers"/> and <see cref="_resolversManifests"/>.
        /// </summary>
        private readonly object _lock = new();
 
        /// <summary>
        /// A static instance of <see cref="CachingSdkResolverLoader"/>.
        /// </summary>
        /// <remarks>
        /// The set of available SDK resolvers is expected to be fixed for the given MSBuild installation so it should be safe to use
        /// a static instance as opposed to creating <see cref="CachingSdkResolverLoader"/> or <see cref="SdkResolverLoader"/> for each
        /// <see cref="SdkResolverService" /> instance.
        /// </remarks>
        public static CachingSdkResolverLoader Instance = new CachingSdkResolverLoader();
 
        /// <summary>
        /// Initializes a new instance by setting <see cref="_defaultResolvers"/>.
        /// </summary>
        public CachingSdkResolverLoader()
        {
            _defaultResolvers = base.GetDefaultResolvers();
        }
 
        /// <summary>
        /// Resets the cached state, intended for tests only.
        /// </summary>
        internal static void ResetStateForTests()
        {
            // Re-create the singleton to pick up environmental changes.
            Instance = new CachingSdkResolverLoader();
        }
 
        #region SdkResolverLoader overrides
 
        /// <inheritdoc />
        internal override IReadOnlyList<SdkResolver> GetDefaultResolvers() => _defaultResolvers;
 
        /// <inheritdoc />
        internal override IReadOnlyList<SdkResolver> LoadAllResolvers(ElementLocation location)
        {
            lock (_lock)
            {
                return _allResolvers ??= base.LoadAllResolvers(location);
            }
        }
 
        /// <inheritdoc />
        internal override IReadOnlyList<SdkResolverManifest> GetResolversManifests(ElementLocation location)
        {
            lock (_lock)
            {
                return _resolversManifests ??= base.GetResolversManifests(location);
            }
        }
 
        /// <inheritdoc />
        protected internal override IReadOnlyList<SdkResolver> LoadResolversFromManifest(SdkResolverManifest manifest, ElementLocation location)
        {
            return _resolversByManifest.GetOrAdd(manifest, (manifest) => base.LoadResolversFromManifest(manifest, location));
        }
 
        #endregion
 
    }
}