Skip to content

Commit

Permalink
Merge pull request #68701 from sharwell/lazy-service
Browse files Browse the repository at this point in the history
Ensure VisualStudioOptionPersister is only created once
  • Loading branch information
sharwell authored Jun 21, 2023
2 parents 6df76ec + bcab3f2 commit 91d9cb2
Showing 1 changed file with 20 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ internal sealed class VisualStudioOptionPersisterProvider : IOptionPersisterProv
// maps config name to a read fallback:
private readonly ImmutableDictionary<string, Lazy<IVisualStudioStorageReadFallback, OptionNameMetadata>> _readFallbacks;

private VisualStudioOptionPersister? _lazyPersister;
// Use vs-threading's JTF-aware AsyncLazy<T>. Ensure only one persister instance is created (even in the face of
// parallel requests for the value) because the constructor registers global event handler callbacks.
private readonly Threading.AsyncLazy<IOptionPersister> _lazyPersister;

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
Expand All @@ -46,29 +48,28 @@ public VisualStudioOptionPersisterProvider(
_featureFlagsService = featureFlagsService;
_legacyGlobalOptions = legacyGlobalOptions;
_readFallbacks = readFallbacks.ToImmutableDictionary(item => item.Metadata.ConfigName, item => item);
_lazyPersister = new Threading.AsyncLazy<IOptionPersister>(() => CreatePersisterAsync(threadingContext.DisposalToken), threadingContext.JoinableTaskFactory);
}

public async ValueTask<IOptionPersister> GetOrCreatePersisterAsync(CancellationToken cancellationToken)
{
return _lazyPersister ??= await CreatePersisterAsync(cancellationToken).ConfigureAwait(true);
public ValueTask<IOptionPersister> GetOrCreatePersisterAsync(CancellationToken cancellationToken)
=> new(_lazyPersister.GetValueAsync(cancellationToken));

async Task<VisualStudioOptionPersister> CreatePersisterAsync(CancellationToken cancellationToken)
{
// Obtain services before creating instances. This avoids state corruption in the event cancellation is
// requested (some of the constructors register event handlers that could leak if cancellation occurred
// in the middle of construction).
var settingsManager = await _settingsManagerService.GetValueAsync(cancellationToken).ConfigureAwait(true);
var localRegistry = await _localRegistryService.GetValueAsync(cancellationToken).ConfigureAwait(true);
var featureFlags = await _featureFlagsService.GetValueOrNullAsync(cancellationToken).ConfigureAwait(true);
private async Task<IOptionPersister> CreatePersisterAsync(CancellationToken cancellationToken)
{
// Obtain services before creating instances. This avoids state corruption in the event cancellation is
// requested (some of the constructors register event handlers that could leak if cancellation occurred
// in the middle of construction).
var settingsManager = await _settingsManagerService.GetValueAsync(cancellationToken).ConfigureAwait(true);
var localRegistry = await _localRegistryService.GetValueAsync(cancellationToken).ConfigureAwait(true);
var featureFlags = await _featureFlagsService.GetValueOrNullAsync(cancellationToken).ConfigureAwait(true);

// Cancellation is not allowed after this point
cancellationToken = CancellationToken.None;
// Cancellation is not allowed after this point
cancellationToken = CancellationToken.None;

return new VisualStudioOptionPersister(
new VisualStudioSettingsOptionPersister(RefreshOption, _readFallbacks, settingsManager),
LocalUserRegistryOptionPersister.Create(localRegistry),
new FeatureFlagPersister(featureFlags));
}
return new VisualStudioOptionPersister(
new VisualStudioSettingsOptionPersister(RefreshOption, _readFallbacks, settingsManager),
LocalUserRegistryOptionPersister.Create(localRegistry),
new FeatureFlagPersister(featureFlags));
}

private void RefreshOption(OptionKey2 optionKey, object? newValue)
Expand Down

0 comments on commit 91d9cb2

Please sign in to comment.