File: System\Configuration\Internal\InternalConfigRoot.cs
Web Access
Project: src\src\libraries\System.Configuration.ConfigurationManager\src\System.Configuration.ConfigurationManager.csproj (System.Configuration.ConfigurationManager)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Threading;
 
namespace System.Configuration.Internal
{
    // InternalConfigRoot holds the root of a configuration hierarchy.
    // It managed creation, removal, and the search for BaseConfigurationRecord's.
    // in a thread-safe manner.
    //
    // The BaseConfigurationRecord hierarchy is protected with the
    // _hierarchyLock. Functions that assume that the lock as been
    // taken begin with the prefix "hl", for example, "hlFindConfigRecord".
    internal sealed class InternalConfigRoot : IInternalConfigRoot
    {
        private ReaderWriterLock _hierarchyLock;
        private bool _isDesignTime;
 
        internal InternalConfigRoot()
        {
        }
 
        internal InternalConfigRoot(Configuration currentConfiguration, UpdateConfigHost host)
        {
            CurrentConfiguration = currentConfiguration;
            UpdateConfigHost = host;
        }
 
        internal IInternalConfigHost Host { get; private set; }
 
        internal UpdateConfigHost UpdateConfigHost { get; private set; }
 
        internal BaseConfigurationRecord RootConfigRecord { get; private set; }
 
        internal Configuration CurrentConfiguration { get; }
 
        public event InternalConfigEventHandler ConfigChanged;
        public event InternalConfigEventHandler ConfigRemoved;
 
        void IInternalConfigRoot.Init(IInternalConfigHost host, bool isDesignTime)
        {
            Host = host;
            _isDesignTime = isDesignTime;
            _hierarchyLock = new ReaderWriterLock();
 
            // Dummy record to hold _children for root
            if (_isDesignTime) RootConfigRecord = MgmtConfigurationRecord.Create(this, null, string.Empty, null);
            else
            {
                RootConfigRecord =
                    (BaseConfigurationRecord)RuntimeConfigurationRecord.Create(this, null, string.Empty);
            }
        }
 
        bool IInternalConfigRoot.IsDesignTime => _isDesignTime;
 
        public object GetSection(string section, string configPath)
        {
            BaseConfigurationRecord configRecord = (BaseConfigurationRecord)GetUniqueConfigRecord(configPath);
            object result = configRecord.GetSection(section);
            return result;
        }
 
        // Get the nearest ancestor path (including self) which contains unique configuration information.
        public string GetUniqueConfigPath(string configPath)
        {
            IInternalConfigRecord configRecord = GetUniqueConfigRecord(configPath);
 
            return configRecord?.ConfigPath;
        }
 
        // Get the nearest ancestor record (including self) which contains unique configuration information.
        public IInternalConfigRecord GetUniqueConfigRecord(string configPath)
        {
            BaseConfigurationRecord configRecord = (BaseConfigurationRecord)GetConfigRecord(configPath);
            while (configRecord.IsEmpty)
            {
                BaseConfigurationRecord parentConfigRecord = configRecord.Parent;
 
                // If all config records are empty, return the immediate child of the
                // root placeholder (e.g. machine.config)
                if (parentConfigRecord.IsRootConfig) break;
 
                configRecord = parentConfigRecord;
            }
 
            return configRecord;
        }
 
        // Get the config record for a path.
        // If the record does not exist, create it if it is needed.
        public IInternalConfigRecord GetConfigRecord(string configPath)
        {
            if (!ConfigPathUtility.IsValid(configPath)) throw ExceptionUtil.ParameterInvalid(nameof(configPath));
 
            string[] parts = ConfigPathUtility.GetParts(configPath);
 
            // First search under the reader lock, so that multiple searches
            // can proceed in parallel.
            try
            {
                int index;
                BaseConfigurationRecord currentRecord;
 
                AcquireHierarchyLockForRead();
 
                HlFindConfigRecord(parts, out index, out currentRecord);
 
                // check if found
                if ((index == parts.Length) || !currentRecord.HlNeedsChildFor(parts[index])) return currentRecord;
            }
            finally
            {
                ReleaseHierarchyLockForRead();
            }
 
            // Not found, so search again under exclusive writer lock so that
            // we can create the record.
            try
            {
                int index;
                BaseConfigurationRecord currentRecord;
 
                AcquireHierarchyLockForWrite();
 
                HlFindConfigRecord(parts, out index, out currentRecord);
 
                if (index == parts.Length) return currentRecord;
 
                string currentConfigPath = string.Join(BaseConfigurationRecord.ConfigPathSeparatorString, parts, 0,
                    index);
 
                // Create new records
                while ((index < parts.Length) && currentRecord.HlNeedsChildFor(parts[index]))
                {
                    string configName = parts[index];
                    currentConfigPath = ConfigPathUtility.Combine(currentConfigPath, configName);
                    BaseConfigurationRecord childRecord;
 
                    childRecord = _isDesignTime
                        ? MgmtConfigurationRecord.Create(this, currentRecord, currentConfigPath, null)
                        : (BaseConfigurationRecord)RuntimeConfigurationRecord.Create(this, currentRecord, currentConfigPath);
 
                    currentRecord.HlAddChild(configName, childRecord);
 
                    index++;
                    currentRecord = childRecord;
                }
 
                return currentRecord;
            }
            finally
            {
                ReleaseHierarchyLockForWrite();
            }
        }
 
        // Find and remove the config record and all its children for the config path.
        public void RemoveConfig(string configPath)
        {
            RemoveConfigImpl(configPath, null);
        }
 
        private void AcquireHierarchyLockForRead()
        {
            // Protect against unexpected recursive entry on this thread.
            // We do this in retail, too, because the results would be very bad if this were to fail,
            // and the testing for this is not easy for all scenarios.
            if (_hierarchyLock.IsReaderLockHeld)
                throw ExceptionUtil.UnexpectedError(
                    "System.Configuration.Internal.InternalConfigRoot::AcquireHierarchyLockForRead - reader lock already held by this thread");
 
            if (_hierarchyLock.IsWriterLockHeld)
                throw ExceptionUtil.UnexpectedError(
                    "System.Configuration.Internal.InternalConfigRoot::AcquireHierarchyLockForRead - writer lock already held by this thread");
 
            _hierarchyLock.AcquireReaderLock(-1);
        }
 
        private void ReleaseHierarchyLockForRead()
        {
            Debug.Assert(!_hierarchyLock.IsWriterLockHeld, "!_hierarchyLock.IsWriterLockHeld");
 
            if (_hierarchyLock.IsReaderLockHeld) _hierarchyLock.ReleaseReaderLock();
        }
 
        private void AcquireHierarchyLockForWrite()
        {
            // Protect against unexpected recursive entry on this thread.
            // We do this in retail, too, because the results would be very bad if this were to fail,
            // and the testing for this is not easy for all scenarios.
            if (_hierarchyLock.IsReaderLockHeld)
                throw ExceptionUtil.UnexpectedError(
                    "System.Configuration.Internal.InternalConfigRoot::AcquireHierarchyLockForWrite - reader lock already held by this thread");
 
            if (_hierarchyLock.IsWriterLockHeld)
                throw ExceptionUtil.UnexpectedError(
                    "System.Configuration.Internal.InternalConfigRoot::AcquireHierarchyLockForWrite - writer lock already held by this thread");
 
            _hierarchyLock.AcquireWriterLock(-1);
        }
 
        private void ReleaseHierarchyLockForWrite()
        {
            Debug.Assert(!_hierarchyLock.IsReaderLockHeld, "!_hierarchyLock.IsReaderLockHeld");
 
            if (_hierarchyLock.IsWriterLockHeld)
                _hierarchyLock.ReleaseWriterLock();
        }
 
        // Find a config record.
        // If found, nextIndex == parts.Length and the resulting record is in currentRecord.
        // If not found, nextIndex is the index of the part of the path not found, and currentRecord
        // is the record that has been found so far (nexIndex - 1).
        private void HlFindConfigRecord(string[] parts, out int nextIndex, out BaseConfigurationRecord currentRecord)
        {
            currentRecord = RootConfigRecord;
            nextIndex = 0;
            for (; nextIndex < parts.Length; nextIndex++)
            {
                BaseConfigurationRecord childRecord = currentRecord.HlGetChild(parts[nextIndex]);
                if (childRecord == null)
                    break;
 
                currentRecord = childRecord;
            }
        }
 
        // Find and remove the config record and all its children for the config path.
        // Optionally ensure the config record matches a desired config record.
        private void RemoveConfigImpl(string configPath, BaseConfigurationRecord configRecord)
        {
            if (!ConfigPathUtility.IsValid(configPath)) throw ExceptionUtil.ParameterInvalid(nameof(configPath));
 
            string[] parts = ConfigPathUtility.GetParts(configPath);
 
            BaseConfigurationRecord currentRecord;
 
            // search under exclusive writer lock
            try
            {
                int index;
                AcquireHierarchyLockForWrite();
                HlFindConfigRecord(parts, out index, out currentRecord);
 
                // Return if not found, or does not match the one we are trying to remove.
                if ((index != parts.Length) || ((configRecord != null) && !ReferenceEquals(configRecord, currentRecord)))
                    return;
 
                // Remove it from the hierarchy.
                currentRecord.Parent.HlRemoveChild(parts[parts.Length - 1]);
            }
            finally
            {
                ReleaseHierarchyLockForWrite();
            }
 
            OnConfigRemoved(new InternalConfigEventArgs(configPath));
 
            // Close the record. This is safe to do outside the lock.
            currentRecord.CloseRecursive();
        }
 
        // Remove the config record and all its children for the config path.
        public void RemoveConfigRecord(BaseConfigurationRecord configRecord)
        {
            RemoveConfigImpl(configRecord.ConfigPath, configRecord);
        }
 
        // Clear the result of a configSection evaluation at a particular point
        // in the hierarchy.
        public void ClearResult(BaseConfigurationRecord configRecord, string configKey, bool forceEvaluation)
        {
            string[] parts = ConfigPathUtility.GetParts(configRecord.ConfigPath);
 
            try
            {
                int index;
                BaseConfigurationRecord currentRecord;
                AcquireHierarchyLockForRead();
                HlFindConfigRecord(parts, out index, out currentRecord);
 
                // clear result only if configRecord it is still in the hierarchy
                if ((index == parts.Length) && ReferenceEquals(configRecord, currentRecord))
                    currentRecord.HlClearResultRecursive(configKey, forceEvaluation);
            }
            finally
            {
                ReleaseHierarchyLockForRead();
            }
        }
 
        private void OnConfigRemoved(InternalConfigEventArgs e)
        {
            ConfigRemoved?.Invoke(this, e);
        }
 
        internal void FireConfigChanged(string configPath)
        {
            OnConfigChanged(new InternalConfigEventArgs(configPath));
        }
 
        private void OnConfigChanged(InternalConfigEventArgs e)
        {
            ConfigChanged?.Invoke(this, e);
        }
    }
}