in src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs [204:461]
public async Task RefreshAsync(CancellationToken cancellationToken)
{
// Ensure that concurrent threads do not simultaneously execute refresh operation.
if (Interlocked.Exchange(ref _networkOperationsInProgress, 1) == 0)
{
try
{
// FeatureManagement assemblies may not be loaded on provider startup, so version information is gathered upon first refresh for tracing
EnsureAssemblyInspected();
var utcNow = DateTimeOffset.UtcNow;
IEnumerable<KeyValueWatcher> refreshableIndividualKvWatchers = _options.IndividualKvWatchers.Where(kvWatcher => utcNow >= kvWatcher.NextRefreshTime);
IEnumerable<KeyValueWatcher> refreshableFfWatchers = _options.FeatureFlagWatchers.Where(ffWatcher => utcNow >= ffWatcher.NextRefreshTime);
bool isRefreshDue = _options.RegisterAllEnabled && utcNow >= _nextCollectionRefreshTime;
// Skip refresh if mappedData is loaded, but none of the watchers or adapters are refreshable.
if (_mappedData != null &&
!refreshableIndividualKvWatchers.Any() &&
!refreshableFfWatchers.Any() &&
!isRefreshDue &&
!_options.Adapters.Any(adapter => adapter.NeedsRefresh()))
{
return;
}
IEnumerable<ConfigurationClient> clients = _configClientManager.GetClients();
if (_requestTracingOptions != null)
{
_requestTracingOptions.ReplicaCount = clients.Count() - 1;
}
//
// Filter clients based on their backoff status
clients = clients.Where(client =>
{
Uri endpoint = _configClientManager.GetEndpointForClient(client);
if (!_configClientBackoffs.TryGetValue(endpoint, out ConfigurationClientBackoffStatus clientBackoffStatus))
{
clientBackoffStatus = new ConfigurationClientBackoffStatus();
_configClientBackoffs[endpoint] = clientBackoffStatus;
}
return clientBackoffStatus.BackoffEndTime <= utcNow;
}
);
if (!clients.Any())
{
_configClientManager.RefreshClients();
_logger.LogDebug(LogHelper.BuildRefreshSkippedNoClientAvailableMessage());
return;
}
using Activity activity = _activitySource.StartActivity(ActivityNames.Refresh);
// Check if initial configuration load had failed
if (_mappedData == null)
{
if (InitializationCacheExpires < utcNow)
{
InitializationCacheExpires = utcNow.Add(MinRefreshInterval);
await InitializeAsync(clients, cancellationToken).ConfigureAwait(false);
}
return;
}
//
// Avoid instance state modification
Dictionary<KeyValueSelector, IEnumerable<MatchConditions>> kvEtags = null;
Dictionary<KeyValueSelector, IEnumerable<MatchConditions>> ffEtags = null;
HashSet<string> ffKeys = null;
Dictionary<KeyValueIdentifier, ConfigurationSetting> watchedIndividualKvs = null;
List<KeyValueChange> keyValueChanges = null;
Dictionary<string, ConfigurationSetting> data = null;
Dictionary<string, ConfigurationSetting> ffCollectionData = null;
bool ffCollectionUpdated = false;
bool refreshAll = false;
StringBuilder logInfoBuilder = new StringBuilder();
StringBuilder logDebugBuilder = new StringBuilder();
await ExecuteWithFailOverPolicyAsync(clients, async (client) =>
{
kvEtags = null;
ffEtags = null;
ffKeys = null;
watchedIndividualKvs = null;
keyValueChanges = new List<KeyValueChange>();
data = null;
ffCollectionData = null;
ffCollectionUpdated = false;
refreshAll = false;
logDebugBuilder.Clear();
logInfoBuilder.Clear();
Uri endpoint = _configClientManager.GetEndpointForClient(client);
if (_options.RegisterAllEnabled)
{
// Get key value collection changes if RegisterAll was called
if (isRefreshDue)
{
refreshAll = await HaveCollectionsChanged(
_options.Selectors.Where(selector => !selector.IsFeatureFlagSelector),
_kvEtags,
client,
cancellationToken).ConfigureAwait(false);
}
}
else
{
refreshAll = await RefreshIndividualKvWatchers(
client,
keyValueChanges,
refreshableIndividualKvWatchers,
endpoint,
logDebugBuilder,
logInfoBuilder,
cancellationToken).ConfigureAwait(false);
}
if (refreshAll)
{
// Trigger a single load-all operation if a change was detected in one or more key-values with refreshAll: true,
// or if any key-value collection change was detected.
kvEtags = new Dictionary<KeyValueSelector, IEnumerable<MatchConditions>>();
ffEtags = new Dictionary<KeyValueSelector, IEnumerable<MatchConditions>>();
ffKeys = new HashSet<string>();
data = await LoadSelected(client, kvEtags, ffEtags, _options.Selectors, ffKeys, cancellationToken).ConfigureAwait(false);
watchedIndividualKvs = await LoadKeyValuesRegisteredForRefresh(client, data, cancellationToken).ConfigureAwait(false);
logInfoBuilder.AppendLine(LogHelper.BuildConfigurationUpdatedMessage());
return;
}
// Get feature flag changes
ffCollectionUpdated = await HaveCollectionsChanged(
refreshableFfWatchers.Select(watcher => new KeyValueSelector
{
KeyFilter = watcher.Key,
LabelFilter = watcher.Label,
TagFilters = watcher.Tags,
IsFeatureFlagSelector = true
}),
_ffEtags,
client,
cancellationToken).ConfigureAwait(false);
if (ffCollectionUpdated)
{
ffEtags = new Dictionary<KeyValueSelector, IEnumerable<MatchConditions>>();
ffKeys = new HashSet<string>();
ffCollectionData = await LoadSelected(
client,
new Dictionary<KeyValueSelector, IEnumerable<MatchConditions>>(),
ffEtags,
_options.Selectors.Where(selector => selector.IsFeatureFlagSelector),
ffKeys,
cancellationToken).ConfigureAwait(false);
logInfoBuilder.Append(LogHelper.BuildFeatureFlagsUpdatedMessage());
}
else
{
logDebugBuilder.AppendLine(LogHelper.BuildFeatureFlagsUnchangedMessage(endpoint.ToString()));
}
},
cancellationToken)
.ConfigureAwait(false);
if (refreshAll)
{
_mappedData = await MapConfigurationSettings(data).ConfigureAwait(false);
// Invalidate all the cached KeyVault secrets
foreach (IKeyValueAdapter adapter in _options.Adapters)
{
adapter.OnChangeDetected();
}
// Update the next refresh time for all refresh registered settings and feature flags
foreach (KeyValueWatcher changeWatcher in _options.IndividualKvWatchers.Concat(_options.FeatureFlagWatchers))
{
UpdateNextRefreshTime(changeWatcher);
}
}
else
{
watchedIndividualKvs = new Dictionary<KeyValueIdentifier, ConfigurationSetting>(_watchedIndividualKvs);
await ProcessKeyValueChangesAsync(keyValueChanges, _mappedData, watchedIndividualKvs).ConfigureAwait(false);
if (ffCollectionUpdated)
{
// Remove all feature flag keys that are not present in the latest loading of feature flags, but were loaded previously
foreach (string key in _ffKeys.Except(ffKeys))
{
_mappedData.Remove(key);
}
Dictionary<string, ConfigurationSetting> mappedFfData = await MapConfigurationSettings(ffCollectionData).ConfigureAwait(false);
foreach (KeyValuePair<string, ConfigurationSetting> kvp in mappedFfData)
{
_mappedData[kvp.Key] = kvp.Value;
}
}
//
// update the next refresh time for all refresh registered settings and feature flags
foreach (KeyValueWatcher changeWatcher in refreshableIndividualKvWatchers.Concat(refreshableFfWatchers))
{
UpdateNextRefreshTime(changeWatcher);
}
}
if (_options.RegisterAllEnabled && isRefreshDue)
{
_nextCollectionRefreshTime = DateTimeOffset.UtcNow.Add(_options.KvCollectionRefreshInterval);
}
if (_options.Adapters.Any(adapter => adapter.NeedsRefresh()) || keyValueChanges.Any() || refreshAll || ffCollectionUpdated)
{
_watchedIndividualKvs = watchedIndividualKvs ?? _watchedIndividualKvs;
_ffEtags = ffEtags ?? _ffEtags;
_kvEtags = kvEtags ?? _kvEtags;
_ffKeys = ffKeys ?? _ffKeys;
if (logDebugBuilder.Length > 0)
{
_logger.LogDebug(logDebugBuilder.ToString().Trim());
}
if (logInfoBuilder.Length > 0)
{
_logger.LogInformation(logInfoBuilder.ToString().Trim());
}
// PrepareData makes calls to KeyVault and may throw exceptions. But, we still update watchers before
// SetData because repeating appconfig calls (by not updating watchers) won't help anything for keyvault calls.
// As long as adapter.NeedsRefresh is true, we will attempt to update keyvault again the next time RefreshAsync is called.
SetData(await PrepareData(_mappedData, cancellationToken).ConfigureAwait(false));
}
}
finally
{
Interlocked.Exchange(ref _networkOperationsInProgress, 0);
}
}
}