<commit_before>using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.CommandLine;
using Microsoft.Extensions.Configuration.EnvironmentVariables;
using Newtonsoft.Json.Linq;
using OrchardCore.Environment.Shell.Configuration;
using OrchardCore.Environment.Shell.Models;

namespace OrchardCore.Environment.Shell
{
    public class ShellSettingsManager : IShellSettingsManager
    {
        private readonly IConfiguration _applicationConfiguration;
        private readonly IShellsConfigurationSources _tenantsConfigSources;
        private readonly IShellConfigurationSources _tenantConfigSources;
        private readonly IShellsSettingsSources _settingsSources;

        private IConfiguration _configuration;
        private IEnumerable<string> _configuredTenants;
        private Func<string, Task<IConfigurationBuilder>> _configBuilderFactory;

        private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);

        public ShellSettingsManager(
            IConfiguration applicationConfiguration,
            IShellsConfigurationSources tenantsConfigSources,
            IShellConfigurationSources tenantConfigSources,
            IShellsSettingsSources settingsSources)
        {
            _applicationConfiguration = applicationConfiguration;
            _tenantsConfigSources = tenantsConfigSources;
            _tenantConfigSources = tenantConfigSources;
            _settingsSources = settingsSources;
        }

        public ShellSettings CreateDefaultSettings()
        {
            return new ShellSettings
            (
                new ShellConfiguration(_configuration),
                new ShellConfiguration(_configuration)
            );
        }

        public async Task<IEnumerable<ShellSettings>> LoadSettingsAsync()
        {
            await EnsureConfigurationAsync();

            await _semaphore.WaitAsync();
            try
            {
                var tenantsSettings = (await new ConfigurationBuilder()
                    .AddSourcesAsync(_settingsSources))
                    .Build();

                var tenants = tenantsSettings.GetChildren().Select(section => section.Key);
                var allTenants = _configuredTenants.Concat(tenants).Distinct().ToArray();

                var allSettings = new List<ShellSettings>();

                foreach (var tenant in allTenants)
                {
                    var tenantSettings = new ConfigurationBuilder()
                        .AddConfiguration(_configuration)
                        .AddConfiguration(_configuration.GetSection(tenant))
                        .AddConfiguration(tenantsSettings.GetSection(tenant))
                        .Build();

                    var settings = new ShellConfiguration(tenantSettings);
                    var configuration = new ShellConfiguration(tenant, _configBuilderFactory);

                    var shellSettings = new ShellSettings(settings, configuration)
                    {
                        Name = tenant,
                    };

                    allSettings.Add(shellSettings);
                };

                return allSettings;
            }
            finally
            {
                _semaphore.Release();
            }
        }

        public async Task<ShellSettings> LoadSettingsAsync(string tenant)
        {
            await EnsureConfigurationAsync();

            await _semaphore.WaitAsync();
            try
            {
                var tenantsSettings = (await new ConfigurationBuilder()
                    .AddSourcesAsync(_settingsSources))
                    .Build();

                var tenantSettings = new ConfigurationBuilder()
                    .AddConfiguration(_configuration)
                    .AddConfiguration(_configuration.GetSection(tenant))
                    .AddConfiguration(tenantsSettings.GetSection(tenant))
                    .Build();

                var settings = new ShellConfiguration(tenantSettings);
                var configuration = new ShellConfiguration(tenant, _configBuilderFactory);

                return new ShellSettings(settings, configuration)
                {
                    Name = tenant,
                };
            }
            finally
            {
                _semaphore.Release();
            }
        }

        public async Task SaveSettingsAsync(ShellSettings settings)
        {
            await EnsureConfigurationAsync();

            await _semaphore.WaitAsync();
            try
            {
                if (settings == null)
                {
                    throw new ArgumentNullException(nameof(settings));
                }

                var configuration = new ConfigurationBuilder()
                    .AddConfiguration(_configuration)
                    .AddConfiguration(_configuration.GetSection(settings.Name))
                    .Build();

                var shellSettings = new ShellSettings()
                {
                    Name = settings.Name
                };

                configuration.Bind(shellSettings);

                var configSettings = JObject.FromObject(shellSettings);
                var tenantSettings = JObject.FromObject(settings);

                foreach (var property in configSettings)
                {
                    var tenantValue = tenantSettings.Value<string>(property.Key);
                    var configValue = configSettings.Value<string>(property.Key);

                    if (tenantValue != configValue)
                    {
                        tenantSettings[property.Key] = tenantValue;
                    }
                    else
                    {
                        tenantSettings[property.Key] = null;
                    }
                }

                tenantSettings.Remove("Name");

                await _settingsSources.SaveAsync(settings.Name, tenantSettings.ToObject<Dictionary<string, string>>());

                var tenantConfig = new JObject();

                var sections = settings.ShellConfiguration.GetChildren()
                    .Where(s => !s.GetChildren().Any())
                    .ToArray();

                foreach (var section in sections)
                {
                    if (settings[section.Key] != configuration[section.Key])
                    {
                        tenantConfig[section.Key] = settings[section.Key];
                    }
                    else
                    {
                        tenantConfig[section.Key] = null;
                    }
                }

                tenantConfig.Remove("Name");

                await _tenantConfigSources.SaveAsync(settings.Name, tenantConfig.ToObject<Dictionary<string, string>>());
            }
            finally
            {
                _semaphore.Release();
            }
        }

        private async Task EnsureConfigurationAsync()
        {
            if (_configuration != null)
            {
                return;
            }

            await _semaphore.WaitAsync();
            try
            {
                if (_configuration != null)
                {
                    return;
                }

                var lastProviders = (_applicationConfiguration as IConfigurationRoot)?.Providers
                    .Where(p => p is EnvironmentVariablesConfigurationProvider ||
                                p is CommandLineConfigurationProvider)
                    .ToArray();

                var configurationBuilder = await new ConfigurationBuilder()
                    .AddConfiguration(_applicationConfiguration)
                    .AddSourcesAsync(_tenantsConfigSources);

                if (lastProviders.Count() > 0)
                {
                    configurationBuilder.AddConfiguration(new ConfigurationRoot(lastProviders));
                }

                var configuration = configurationBuilder.Build().GetSection("OrchardCore");

                _configuredTenants = configuration.GetChildren()
                    .Where(section => Enum.TryParse<TenantState>(section["State"], ignoreCase: true, out var result))
                    .Select(section => section.Key)
                    .Distinct()
                    .ToArray();

                _configBuilderFactory = async (tenant) =>
                {
                    await _semaphore.WaitAsync();
                    try
                    {
                        var builder = new ConfigurationBuilder().AddConfiguration(_configuration);
                        builder.AddConfiguration(configuration.GetSection(tenant));
                        return await builder.AddSourcesAsync(tenant, _tenantConfigSources);
                    }
                    finally
                    {
                        _semaphore.Release();
                    }
                };

                _configuration = configuration;
            }
            finally
            {
                _semaphore.Release();
            }
        }
    }
}<commit_msg>Fixes tenant lazy config dead lock (#7032)<commit_after>using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.CommandLine;
using Microsoft.Extensions.Configuration.EnvironmentVariables;
using Newtonsoft.Json.Linq;
using OrchardCore.Environment.Shell.Configuration;
using OrchardCore.Environment.Shell.Models;

namespace OrchardCore.Environment.Shell
{
    public class ShellSettingsManager : IShellSettingsManager
    {
        private readonly IConfiguration _applicationConfiguration;
        private readonly IShellsConfigurationSources _tenantsConfigSources;
        private readonly IShellConfigurationSources _tenantConfigSources;
        private readonly IShellsSettingsSources _settingsSources;

        private IConfiguration _configuration;
        private IEnumerable<string> _configuredTenants;
        private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);

        private Func<string, Task<IConfigurationBuilder>> _tenantConfigBuilderFactory;
        private readonly SemaphoreSlim _tenantConfigSemaphore = new SemaphoreSlim(1);

        public ShellSettingsManager(
            IConfiguration applicationConfiguration,
            IShellsConfigurationSources tenantsConfigSources,
            IShellConfigurationSources tenantConfigSources,
            IShellsSettingsSources settingsSources)
        {
            _applicationConfiguration = applicationConfiguration;
            _tenantsConfigSources = tenantsConfigSources;
            _tenantConfigSources = tenantConfigSources;
            _settingsSources = settingsSources;
        }

        public ShellSettings CreateDefaultSettings()
        {
            return new ShellSettings
            (
                new ShellConfiguration(_configuration),
                new ShellConfiguration(_configuration)
            );
        }

        public async Task<IEnumerable<ShellSettings>> LoadSettingsAsync()
        {
            await _semaphore.WaitAsync();
            try
            {
                await EnsureConfigurationAsync();

                var tenantsSettings = (await new ConfigurationBuilder()
                    .AddSourcesAsync(_settingsSources))
                    .Build();

                var tenants = tenantsSettings.GetChildren().Select(section => section.Key);
                var allTenants = _configuredTenants.Concat(tenants).Distinct().ToArray();

                var allSettings = new List<ShellSettings>();

                foreach (var tenant in allTenants)
                {
                    var tenantSettings = new ConfigurationBuilder()
                        .AddConfiguration(_configuration)
                        .AddConfiguration(_configuration.GetSection(tenant))
                        .AddConfiguration(tenantsSettings.GetSection(tenant))
                        .Build();

                    var settings = new ShellConfiguration(tenantSettings);
                    var configuration = new ShellConfiguration(tenant, _tenantConfigBuilderFactory);

                    var shellSettings = new ShellSettings(settings, configuration)
                    {
                        Name = tenant,
                    };

                    allSettings.Add(shellSettings);
                };

                return allSettings;
            }
            finally
            {
                _semaphore.Release();
            }
        }

        public async Task<ShellSettings> LoadSettingsAsync(string tenant)
        {
            await _semaphore.WaitAsync();
            try
            {
                await EnsureConfigurationAsync();

                var tenantsSettings = (await new ConfigurationBuilder()
                    .AddSourcesAsync(_settingsSources))
                    .Build();

                var tenantSettings = new ConfigurationBuilder()
                    .AddConfiguration(_configuration)
                    .AddConfiguration(_configuration.GetSection(tenant))
                    .AddConfiguration(tenantsSettings.GetSection(tenant))
                    .Build();

                var settings = new ShellConfiguration(tenantSettings);
                var configuration = new ShellConfiguration(tenant, _tenantConfigBuilderFactory);

                return new ShellSettings(settings, configuration)
                {
                    Name = tenant,
                };
            }
            finally
            {
                _semaphore.Release();
            }
        }

        public async Task SaveSettingsAsync(ShellSettings settings)
        {
            await _semaphore.WaitAsync();
            try
            {
                await EnsureConfigurationAsync();

                if (settings == null)
                {
                    throw new ArgumentNullException(nameof(settings));
                }

                var configuration = new ConfigurationBuilder()
                    .AddConfiguration(_configuration)
                    .AddConfiguration(_configuration.GetSection(settings.Name))
                    .Build();

                var shellSettings = new ShellSettings()
                {
                    Name = settings.Name
                };

                configuration.Bind(shellSettings);

                var configSettings = JObject.FromObject(shellSettings);
                var tenantSettings = JObject.FromObject(settings);

                foreach (var property in configSettings)
                {
                    var tenantValue = tenantSettings.Value<string>(property.Key);
                    var configValue = configSettings.Value<string>(property.Key);

                    if (tenantValue != configValue)
                    {
                        tenantSettings[property.Key] = tenantValue;
                    }
                    else
                    {
                        tenantSettings[property.Key] = null;
                    }
                }

                tenantSettings.Remove("Name");

                await _settingsSources.SaveAsync(settings.Name, tenantSettings.ToObject<Dictionary<string, string>>());

                var tenantConfig = new JObject();

                var sections = settings.ShellConfiguration.GetChildren()
                    .Where(s => !s.GetChildren().Any())
                    .ToArray();

                foreach (var section in sections)
                {
                    if (settings[section.Key] != configuration[section.Key])
                    {
                        tenantConfig[section.Key] = settings[section.Key];
                    }
                    else
                    {
                        tenantConfig[section.Key] = null;
                    }
                }

                tenantConfig.Remove("Name");

                await _tenantConfigSemaphore.WaitAsync();
                try
                {
                    await _tenantConfigSources.SaveAsync(settings.Name, tenantConfig.ToObject<Dictionary<string, string>>());
                }
                finally
                {
                    _tenantConfigSemaphore.Release();
                }
            }
            finally
            {
                _semaphore.Release();
            }
        }

        private async Task EnsureConfigurationAsync()
        {
            if (_configuration != null)
            {
                return;
            }

            var lastProviders = (_applicationConfiguration as IConfigurationRoot)?.Providers
                .Where(p => p is EnvironmentVariablesConfigurationProvider ||
                            p is CommandLineConfigurationProvider)
                .ToArray();

            var configurationBuilder = await new ConfigurationBuilder()
                .AddConfiguration(_applicationConfiguration)
                .AddSourcesAsync(_tenantsConfigSources);

            if (lastProviders.Count() > 0)
            {
                configurationBuilder.AddConfiguration(new ConfigurationRoot(lastProviders));
            }

            var configuration = configurationBuilder.Build().GetSection("OrchardCore");

            _configuredTenants = configuration.GetChildren()
                .Where(section => Enum.TryParse<TenantState>(section["State"], ignoreCase: true, out var result))
                .Select(section => section.Key)
                .Distinct()
                .ToArray();

            _tenantConfigBuilderFactory = async (tenant) =>
            {
                await _tenantConfigSemaphore.WaitAsync();
                try
                {
                    var builder = new ConfigurationBuilder().AddConfiguration(_configuration);
                    builder.AddConfiguration(configuration.GetSection(tenant));
                    return await builder.AddSourcesAsync(tenant, _tenantConfigSources);
                }
                finally
                {
                    _tenantConfigSemaphore.Release();
                }
            };

            _configuration = configuration;
        }
    }
}
{'fraction_non_alphanumeric': 0.06888646288209607, 'fraction_numerical': 0.0004912663755458515, 'mean_word_length': 1.4912972531955397, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4737495', 'n_tokens_mistral': 3888, 'n_tokens_neox': 3634, 'n_words': 724}