Skip to content

Commit

Permalink
Merge pull request #65780 from CyrusNajmabadi/loadableText
Browse files Browse the repository at this point in the history
Move off of the final usage of an uncached AsyncLazy
  • Loading branch information
CyrusNajmabadi authored Apr 14, 2023
2 parents 4230b3e + b473dff commit 9f323e7
Showing 1 changed file with 81 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -16,12 +17,27 @@ internal sealed class LoadableTextAndVersionSource : ITextAndVersionSource
private sealed class LazyValueWithOptions
{
public readonly LoadableTextAndVersionSource Source;
public readonly AsyncLazy<TextAndVersion> LazyValue;
public readonly LoadTextOptions Options;

private readonly SemaphoreSlim _gate = new(initialCount: 1);

/// <summary>
/// Strong reference to the loaded text and version. Only held onto once computed if <see cref="Source"/>.<see
/// cref="CacheResult"/> is <see langword="true"/>. Once held onto, this will be returned from all calls to
/// <see cref="TryGetValue"/>, <see cref="GetValue"/> or <see cref="GetValueAsync"/>. Once non-null will always
/// remain non-null.
/// </summary>
private TextAndVersion? _instance;

/// <summary>
/// Weak reference to the loaded text and version that we create whenever the value is computed. We will
/// attempt to return from this if still alive when clients call back into this. If neither this, nor <see
/// cref="_instance"/> are available, the value will be reloaded. Once non-null, this will always be non-null.
/// </summary>
private WeakReference<TextAndVersion>? _weakInstance;

public LazyValueWithOptions(LoadableTextAndVersionSource source, LoadTextOptions options)
{
LazyValue = new AsyncLazy<TextAndVersion>(LoadAsync, LoadSynchronously, source.CacheResult);
Source = source;
Options = options;
}
Expand All @@ -31,6 +47,67 @@ private Task<TextAndVersion> LoadAsync(CancellationToken cancellationToken)

private TextAndVersion LoadSynchronously(CancellationToken cancellationToken)
=> Source.Loader.LoadTextSynchronously(Options, cancellationToken);

public bool TryGetValue([MaybeNullWhen(false)] out TextAndVersion value)
{
value = _instance;
if (value != null)
return true;

return _weakInstance?.TryGetTarget(out value) == true && value != null;
}

public TextAndVersion GetValue(CancellationToken cancellationToken)
{
if (!TryGetValue(out var textAndVersion))
{
using (_gate.DisposableWait(cancellationToken))
{
if (!TryGetValue(out textAndVersion))
{
textAndVersion = LoadSynchronously(cancellationToken);
UpdateWeakAndStrongReferences_NoLock(textAndVersion);
}
}
}

return textAndVersion;
}

public async Task<TextAndVersion> GetValueAsync(CancellationToken cancellationToken)
{
if (!TryGetValue(out var textAndVersion))
{
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
if (!TryGetValue(out textAndVersion))
{
textAndVersion = await LoadAsync(cancellationToken).ConfigureAwait(false);
UpdateWeakAndStrongReferences_NoLock(textAndVersion);
}
}
}

return textAndVersion;
}

private void UpdateWeakAndStrongReferences_NoLock(TextAndVersion textAndVersion)
{
Contract.ThrowIfTrue(_gate.CurrentCount != 0);

if (this.Source.CacheResult)
{
// if our source wants us to hold onto the value strongly, do so. No need to involve the weak-refs as
// this will now hold onto the value forever.
_instance = textAndVersion;
}
else
{
// Update the weak ref, so we can return the same instance if anything else is holding onto it.
_weakInstance ??= new WeakReference<TextAndVersion>(textAndVersion);
_weakInstance.SetTarget(textAndVersion);
}
}
}

public readonly TextLoader Loader;
Expand All @@ -47,7 +124,7 @@ public LoadableTextAndVersionSource(TextLoader loader, bool cacheResult)
public bool CanReloadText
=> Loader.CanReloadText;

private AsyncLazy<TextAndVersion> GetLazyValue(LoadTextOptions options)
private LazyValueWithOptions GetLazyValue(LoadTextOptions options)
{
var lazy = _lazyValue;

Expand All @@ -57,7 +134,7 @@ private AsyncLazy<TextAndVersion> GetLazyValue(LoadTextOptions options)
_lazyValue = lazy = new LazyValueWithOptions(this, options);
}

return lazy.LazyValue;
return lazy;
}

public TextAndVersion GetValue(LoadTextOptions options, CancellationToken cancellationToken)
Expand Down

0 comments on commit 9f323e7

Please sign in to comment.