Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consolidate Interlocked initialize implementations #69017

Merged
merged 3 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1549,7 +1549,7 @@ internal override ObsoleteAttributeData ObsoleteAttributeData
{
var result = uncommonFields._lazyObsoleteAttributeData;
return ReferenceEquals(result, ObsoleteAttributeData.Uninitialized)
? InterlockedOperations.Initialize(ref uncommonFields._lazyObsoleteAttributeData, null, ObsoleteAttributeData.Uninitialized)
? InterlockedOperations.Initialize(ref uncommonFields._lazyObsoleteAttributeData, initializedValue: null, ObsoleteAttributeData.Uninitialized)
Copy link
Contributor

@RikkiGibson RikkiGibson Jul 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may overlap/conflict with changes in #68930. Heads up @cston.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find any overlap/conflict

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there actually isn't any. I think the linked PR is using Interlocked.CompareExchange, not any InterlockedOperations or similar APIs that changed in this PR.

: result;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ ImmutableArray<ITypeSymbol> IMethodSymbol.TypeArguments
{
get
{
return InterlockedOperations.InterlockedInitialize(
return InterlockedOperations.Initialize(
ref _lazyTypeArguments,
static underlying => underlying.TypeArgumentsWithAnnotations.GetPublicSymbols(),
_underlying);
Expand All @@ -123,7 +123,7 @@ ImmutableArray<IParameterSymbol> IMethodSymbol.Parameters
{
get
{
return InterlockedOperations.InterlockedInitialize(
return InterlockedOperations.Initialize(
ref _lazyParameters,
static underlying => underlying.Parameters.GetPublicSymbols(),
_underlying);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal abstract partial class CompilerDiagnosticAnalyzer : DiagnosticAnalyzer
internal abstract ImmutableArray<int> GetSupportedErrorCodes();

public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> InterlockedOperations.InterlockedInitialize(
=> InterlockedOperations.Initialize(
ref _supportedDiagnostics,
static @this =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,106 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;

namespace Roslyn.Utilities
{
internal static class InterlockedOperations
{
private static T GetOrStore<T>([NotNull] ref T? target, T value) where T : class
=> Interlocked.CompareExchange(ref target, value, null) ?? value;

private static int GetOrStore(ref int target, int value, int uninitializedValue)
{
var existingValue = Interlocked.CompareExchange(ref target, value, uninitializedValue);
return existingValue == uninitializedValue ? value : existingValue;
}

/// <summary>
/// Ensure that the given target value is initialized (not null) in a thread-safe manner.
/// </summary>
/// <typeparam name="T">The type of the target value. Must be a reference type.</typeparam>
/// <param name="target">The target to initialize.</param>
/// <param name="valueFactory">A factory delegate to create a new instance of the target value. Note that this delegate may be called
/// more than once by multiple threads, but only one of those values will successfully be written to the target.</param>
/// <returns>The target value.</returns>
public static T Initialize<T>([NotNull] ref T? target, Func<T> valueFactory) where T : class
=> Volatile.Read(ref target!) ?? GetOrStore(ref target, valueFactory());

/// <summary>
/// Ensure that the given target value is initialized (not null) in a thread-safe manner.
/// </summary>
/// <typeparam name="T">The type of the target value. Must be a reference type.</typeparam>
/// <param name="target">The target to initialize.</param>
/// <typeparam name="TArg">The type of the <paramref name="arg"/> argument passed to the value factory.</typeparam>
/// <param name="valueFactory">A factory delegate to create a new instance of the target value. Note that this delegate may be called
/// more than once by multiple threads, but only one of those values will successfully be written to the target.</param>
/// <param name="arg">An argument passed to the value factory.</param>
/// <returns>The target value.</returns>
public static T Initialize<T, TArg>([NotNull] ref T? target, Func<TArg, T> valueFactory, TArg arg)
where T : class
{
return Volatile.Read(ref target!) ?? GetOrStore(ref target, valueFactory(arg));
}

/// <summary>
/// Ensure that the given target value is initialized in a thread-safe manner.
/// </summary>
/// <param name="target">The target to initialize.</param>
/// <param name="uninitializedValue">The value indicating <paramref name="target"/> is not yet initialized.</param>
/// <param name="valueFactory">A factory delegate to create a new instance of the target value. Note that this delegate may be called
/// more than once by multiple threads, but only one of those values will successfully be written to the target.</param>
/// <param name="arg">An argument passed to the value factory.</param>
/// <typeparam name="TArg">The type of the <paramref name="arg"/> argument passed to the value factory.</typeparam>
/// <remarks>
/// If <paramref name="valueFactory"/> returns a value equal to <paramref name="uninitializedValue"/>, future
/// calls to the same method may recalculate the target value.
/// </remarks>
/// <returns>The target value.</returns>
public static int Initialize<TArg>(ref int target, int uninitializedValue, Func<TArg, int> valueFactory, TArg arg)
{
var existingValue = Volatile.Read(ref target);
if (existingValue != uninitializedValue)
return existingValue;

return GetOrStore(ref target, valueFactory(arg), uninitializedValue);
}

/// <summary>
/// Ensure that the given target value is initialized in a thread-safe manner. This overload supports the
/// initialization of value types, and reference type fields where <see langword="null"/> is considered an
/// initialized value.
/// </summary>
/// <typeparam name="T">The type of the target value.</typeparam>
/// <param name="target">A target value box to initialize.</param>
/// <param name="valueFactory">A factory delegate to create a new instance of the target value. Note that this delegate may be called
/// more than once by multiple threads, but only one of those values will successfully be written to the target.</param>
/// <returns>The target value.</returns>
public static T? Initialize<T>([NotNull] ref StrongBox<T?>? target, Func<T?> valueFactory)
{
var box = Volatile.Read(ref target!) ?? GetOrStore(ref target, new StrongBox<T?>(valueFactory()));
return box.Value;
}

/// <summary>
/// Ensure that the given target value is initialized in a thread-safe manner. This overload supports the
/// initialization of value types, and reference type fields where <see langword="null"/> is considered an
/// initialized value.
/// </summary>
/// <typeparam name="T">The type of the target value.</typeparam>
/// <param name="target">A target value box to initialize.</param>
/// <typeparam name="TArg">The type of the <paramref name="arg"/> argument passed to the value factory.</typeparam>
/// <param name="valueFactory">A factory delegate to create a new instance of the target value. Note that this delegate may be called
/// more than once by multiple threads, but only one of those values will successfully be written to the target.</param>
/// <param name="arg">An argument passed to the value factory.</param>
/// <returns>The target value.</returns>
public static T? Initialize<T, TArg>([NotNull] ref StrongBox<T?>? target, Func<TArg, T?> valueFactory, TArg arg)
{
var box = Volatile.Read(ref target!) ?? GetOrStore(ref target, new StrongBox<T?>(valueFactory(arg)));
return box.Value;
}

/// <summary>
/// Initialize the value referenced by <paramref name="target"/> in a thread-safe manner.
/// The value is changed to <paramref name="value"/> only if the current value is null.
Expand All @@ -25,7 +119,7 @@ internal static class InterlockedOperations
public static T Initialize<T>([NotNull] ref T? target, T value) where T : class
{
RoslynDebug.Assert((object?)value != null);
return Interlocked.CompareExchange(ref target, value, null) ?? value;
return GetOrStore(ref target, value);
}

/// <summary>
Expand Down Expand Up @@ -73,20 +167,30 @@ public static ImmutableArray<T> Initialize<T>(ref ImmutableArray<T> target, Immu
/// called if 'target' is already not 'default' at the time this is called.</param>
/// <returns>The value of <paramref name="target"/> after initialization. If <paramref name="target"/> is
/// already initialized, that value value will be returned.</returns>
public static ImmutableArray<T> InterlockedInitialize<T>(ref ImmutableArray<T> target, Func<ImmutableArray<T>> createArray)
=> InterlockedInitialize(ref target, static createArray => createArray(), createArray);
public static ImmutableArray<T> Initialize<T>(ref ImmutableArray<T> target, Func<ImmutableArray<T>> createArray)
=> Initialize(ref target, static createArray => createArray(), createArray);

public static ImmutableArray<T> InterlockedInitialize<T, TArg>(ref ImmutableArray<T> target, Func<TArg, ImmutableArray<T>> createArray, TArg arg)
/// <summary>
/// Initialize the immutable array referenced by <paramref name="target"/> in a thread-safe manner.
/// </summary>
/// <typeparam name="T">Elemental type of the array.</typeparam>
/// <typeparam name="TArg">The type of the <paramref name="arg"/> argument passed to the value factory.</typeparam>
/// <param name="createArray">Callback to produce the array if <paramref name="target"/> is 'default'. May be
/// called multiple times in the event of concurrent initialization of <paramref name="target"/>. Will not be
/// called if 'target' is already not 'default' at the time this is called.</param>
/// <returns>The value of <paramref name="target"/> after initialization. If <paramref name="target"/> is
/// already initialized, that value value will be returned.</returns>
public static ImmutableArray<T> Initialize<T, TArg>(ref ImmutableArray<T> target, Func<TArg, ImmutableArray<T>> createArray, TArg arg)
{
if (!target.IsDefault)
{
return target;
}

return InterlockedInitialize_Slow(ref target, createArray, arg);
return Initialize_Slow(ref target, createArray, arg);
}

private static ImmutableArray<T> InterlockedInitialize_Slow<T, TArg>(ref ImmutableArray<T> target, Func<TArg, ImmutableArray<T>> createArray, TArg arg)
private static ImmutableArray<T> Initialize_Slow<T, TArg>(ref ImmutableArray<T> target, Func<TArg, ImmutableArray<T>> createArray, TArg arg)
{
ImmutableInterlocked.Update(
ref target,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1259,7 +1259,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE
Else
Dim result = uncommonFields._lazyObsoleteAttributeData
Return If(result Is ObsoleteAttributeData.Uninitialized,
InterlockedOperations.Initialize(uncommonFields._lazyObsoleteAttributeData, Nothing, ObsoleteAttributeData.Uninitialized),
InterlockedOperations.Initialize(uncommonFields._lazyObsoleteAttributeData, initializedValue:=Nothing, ObsoleteAttributeData.Uninitialized),
result)
End If
End Get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using InterlockedOperations = Roslyn.Utilities.InterlockedOperations;

namespace Microsoft.CodeAnalysis.Navigation
{
Expand All @@ -28,7 +28,7 @@ private class SymbolLocationNavigableItem(
/// <summary>
/// Lazily-initialized backing field for <see cref="Document"/>.
/// </summary>
/// <seealso cref="LazyInitialization.EnsureInitialized{T, U}(ref StrongBox{T}, Func{U, T}, U)"/>
/// <seealso cref="InterlockedOperations.Initialize{T, U}(ref StrongBox{T}, Func{U, T}, U)"/>
private StrongBox<INavigableItem.NavigableDocument> _lazyDocument;

public bool DisplayFileLocation => true;
Expand All @@ -43,7 +43,7 @@ public INavigableItem.NavigableDocument Document
{
get
{
return LazyInitialization.EnsureInitialized(
return InterlockedOperations.Initialize(
ref _lazyDocument,
static self =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public RazorDocumentServiceProviderWrapper(IRazorDocumentServiceProvider innerDo
var serviceType = typeof(TService);
if (serviceType == typeof(ISpanMappingService))
{
var spanMappingService = LazyInitialization.EnsureInitialized(
var spanMappingService = InterlockedOperations.Initialize(
ref _lazySpanMappingService,
static documentServiceProvider =>
{
Expand All @@ -46,7 +46,7 @@ public RazorDocumentServiceProviderWrapper(IRazorDocumentServiceProvider innerDo

if (serviceType == typeof(IDocumentExcerptService))
{
var excerptService = LazyInitialization.EnsureInitialized(
var excerptService = InterlockedOperations.Initialize(
ref _lazyExcerptService,
static documentServiceProvider =>
{
Expand All @@ -60,7 +60,7 @@ public RazorDocumentServiceProviderWrapper(IRazorDocumentServiceProvider innerDo

if (serviceType == typeof(DocumentPropertiesService))
{
var documentPropertiesService = LazyInitialization.EnsureInitialized(
var documentPropertiesService = InterlockedOperations.Initialize(
ref _lazyDocumentPropertiesService,
static documentServiceProvider =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private sealed partial class RecoverableText
/// </summary>
private WeakReference<SourceText>? _weakReference;

private SemaphoreSlim Gate => LazyInitialization.EnsureInitialized(ref _lazyGate, SemaphoreSlimFactory.Instance);
private SemaphoreSlim Gate => InterlockedOperations.Initialize(ref _lazyGate, SemaphoreSlimFactory.Instance);

/// <summary>
/// Attempts to get the value, but only through the weak reference. This will only succeed *after* the value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ internal partial class SolutionState

private ProjectId? GetOriginatingProjectIdWorker(ISymbol symbol)
{
LazyInitialization.EnsureInitialized(ref _unrootedSymbolToProjectId, s_createTable);
InterlockedOperations.Initialize(ref _unrootedSymbolToProjectId, s_createTable);

// Walk up the symbol so we can get to the containing namespace/assembly that will be used to map
// back to a project.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\Compilers\Core\Portable\InternalUtilities\Debug.cs" Link="InternalUtilities\Debug.cs" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\Compilers\Core\Portable\InternalUtilities\EnumerableExtensions.cs" Link="InternalUtilities\EnumerableExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\Compilers\Core\Portable\InternalUtilities\ExceptionUtilities.cs" Link="InternalUtilities\ExceptionUtilities.cs" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\Compilers\Core\Portable\InternalUtilities\InterlockedOperations.cs" Link="InternalUtilities\InterlockedOperations.cs" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\Compilers\Core\Portable\InternalUtilities\InterpolatedStringHandlerArgumentAttribute.cs" Link="InternalUtilities\InterpolatedStringHandlerArgumentAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\Compilers\Core\Portable\InternalUtilities\InterpolatedStringHandlerAttribute.cs" Link="InternalUtilities\InterpolatedStringHandlerAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\Compilers\Core\Portable\InternalUtilities\IReadOnlySet.cs" Link="InternalUtilities\IReadOnlySet.cs" />
Expand Down Expand Up @@ -500,7 +501,6 @@
<Compile Include="$(MSBuildThisFileDirectory)Utilities\IReadOnlyDictionaryExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\IReadOnlyListExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\IReferenceCountedDisposable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\LazyInitialization.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\Matcher.ChoiceMatcher.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\Matcher.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\Matcher.RepeatMatcher.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public SyntaxToken EndToken

private int GetOrComputeIndentationDelta()
{
return LazyInitialization.EnsureInitialized(
return InterlockedOperations.Initialize(
ref _lazyIndentationDelta,
UninitializedIndentationDelta,
static self => self._indentationDeltaGetter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void AddOrReplace(int key, TriviaData triviaInfo)
// PERF: Set the concurrency level to 1 because, while the dictionary has to be thread-safe,
// there is very little contention in formatting. A lower concurrency level reduces object
// allocations which are used internally by ConcurrentDictionary for locking.
var map = LazyInitialization.EnsureInitialized(ref _map, () => new ConcurrentDictionary<int, TriviaData>(concurrencyLevel: 1, capacity: 8));
var map = InterlockedOperations.Initialize(ref _map, () => new ConcurrentDictionary<int, TriviaData>(concurrencyLevel: 1, capacity: 8));
map[key] = triviaInfo;
}

Expand Down
Loading