Skip to content

Commit bc0380f

Browse files
authored
Remove some volatile use on objects in corelib (#100969)
From a review of the use, all of this use for lazy initialization now appears to be superfluous, given our documented memory model.
1 parent 907eff8 commit bc0380f

16 files changed

+55
-86
lines changed

src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs

+12-32
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,13 @@ internal sealed partial class CultureData
111111
private string? _sAM1159; // (user can override) AM designator
112112
private string? _sPM2359; // (user can override) PM designator
113113
private string? _sTimeSeparator;
114-
private volatile string[]? _saLongTimes; // (user can override) time format
115-
private volatile string[]? _saShortTimes; // (user can override) short time format
116-
private volatile string[]? _saDurationFormats; // time duration format
114+
private string[]? _saLongTimes; // (user can override) time format
115+
private string[]? _saShortTimes; // (user can override) short time format
117116

118117
// Calendar specific data
119118
private int _iFirstDayOfWeek = undef; // (user can override) first day of week (gregorian really)
120119
private int _iFirstWeekOfYear = undef; // (user can override) first week of year (gregorian really)
121-
private volatile CalendarId[]? _waCalendars; // all available calendar type(s). The first one is the default calendar
120+
private CalendarId[]? _waCalendars; // all available calendar type(s). The first one is the default calendar
122121

123122
// Store for specific data about each calendar
124123
private CalendarData?[]? _calendars; // Store for specific calendar data
@@ -150,7 +149,7 @@ internal sealed partial class CultureData
150149
/// </remarks>
151150
private static Dictionary<string, string> RegionNames =>
152151
s_regionNames ??=
153-
new Dictionary<string, string>(257 /* prime */, StringComparer.OrdinalIgnoreCase)
152+
new Dictionary<string, string>(255, StringComparer.OrdinalIgnoreCase)
154153
{
155154
{ "001", "en-001" },
156155
{ "029", "en-029" },
@@ -411,7 +410,7 @@ internal sealed partial class CultureData
411410

412411
// Cache of regions we've already looked up
413412
private static volatile Dictionary<string, CultureData>? s_cachedRegions;
414-
private static volatile Dictionary<string, string>? s_regionNames;
413+
private static Dictionary<string, string>? s_regionNames;
415414

416415
/// <summary>
417416
/// The culture name to use to interop with the underlying native globalization libraries like ICU or Windows NLS APIs.
@@ -621,7 +620,6 @@ private static CultureData CreateCultureWithInvariantData()
621620
invariant._sPM2359 = "PM"; // PM designator
622621
invariant._saLongTimes = new string[] { "HH:mm:ss" }; // time format
623622
invariant._saShortTimes = new string[] { "HH:mm", "hh:mm tt", "H:mm", "h:mm tt" }; // short time format
624-
invariant._saDurationFormats = new string[] { "HH:mm:ss" }; // time duration format
625623

626624
// Calendar specific data
627625
invariant._iFirstDayOfWeek = 0; // first day of week
@@ -661,7 +659,7 @@ private static CultureData CreateCultureWithInvariantData()
661659
/// We need an invariant instance, which we build hard-coded
662660
/// </summary>
663661
internal static CultureData Invariant => s_Invariant ??= CreateCultureWithInvariantData();
664-
private static volatile CultureData? s_Invariant;
662+
private static CultureData? s_Invariant;
665663

666664
// Cache of cultures we've already looked up
667665
private static volatile Dictionary<string, CultureData>? s_cachedCultures;
@@ -1381,18 +1379,10 @@ internal string[] LongTimes
13811379
{
13821380
if (_saLongTimes == null && !GlobalizationMode.Invariant)
13831381
{
1384-
Debug.Assert(!GlobalizationMode.Invariant);
1385-
13861382
string[]? longTimes = GetTimeFormatsCore(shortFormat: false);
1387-
if (longTimes == null || longTimes.Length == 0)
1388-
{
1389-
_saLongTimes = Invariant._saLongTimes!;
1390-
}
1391-
else
1392-
{
1393-
_saLongTimes = longTimes;
1394-
}
1383+
_saLongTimes = longTimes != null && longTimes.Length != 0 ? longTimes : Invariant._saLongTimes!;
13951384
}
1385+
13961386
return _saLongTimes!;
13971387
}
13981388
}
@@ -1407,23 +1397,13 @@ internal string[] ShortTimes
14071397
{
14081398
if (_saShortTimes == null && !GlobalizationMode.Invariant)
14091399
{
1410-
Debug.Assert(!GlobalizationMode.Invariant);
1411-
14121400
// Try to get the short times from the OS/culture.dll
1401+
// If we couldn't find short times, then compute them from long times
1402+
// (eg: CORECLR on < Win7 OS & fallback for missing culture.dll)
14131403
string[]? shortTimes = GetTimeFormatsCore(shortFormat: true);
1414-
1415-
if (shortTimes == null || shortTimes.Length == 0)
1416-
{
1417-
//
1418-
// If we couldn't find short times, then compute them from long times
1419-
// (eg: CORECLR on < Win7 OS & fallback for missing culture.dll)
1420-
//
1421-
shortTimes = DeriveShortTimesFromLong();
1422-
}
1423-
1424-
// Found short times, use them
1425-
_saShortTimes = shortTimes;
1404+
_saShortTimes = shortTimes != null && shortTimes.Length != 0 ? shortTimes : DeriveShortTimesFromLong();
14261405
}
1406+
14271407
return _saShortTimes!;
14281408
}
14291409
}

src/libraries/System.Private.CoreLib/src/System/Globalization/CultureInfo.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ private static void AsyncLocalSetCurrentUICulture(AsyncLocalValueChangedArgs<Cul
126126
s_currentThreadUICulture = args.CurrentValue;
127127
}
128128

129-
private static volatile Dictionary<string, CultureInfo>? s_cachedCulturesByName;
130-
private static volatile Dictionary<int, CultureInfo>? s_cachedCulturesByLcid;
129+
private static Dictionary<string, CultureInfo>? s_cachedCulturesByName;
130+
private static Dictionary<int, CultureInfo>? s_cachedCulturesByLcid;
131131

132132
// The parent culture.
133133
private CultureInfo? _parent;

src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1914,8 +1914,8 @@ internal bool YearMonthAdjustment(ref int year, ref int month, bool parsedMonthN
19141914
internal const string JapaneseLangName = "ja";
19151915
internal const string EnglishLangName = "en";
19161916

1917-
private static volatile DateTimeFormatInfo? s_jajpDTFI;
1918-
private static volatile DateTimeFormatInfo? s_zhtwDTFI;
1917+
private static DateTimeFormatInfo? s_jajpDTFI;
1918+
private static DateTimeFormatInfo? s_zhtwDTFI;
19191919

19201920
/// <summary>
19211921
/// Create a Japanese DTFI which uses JapaneseCalendar. This is used to parse

src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendar.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class GregorianCalendar : Calendar
2626

2727
internal static ReadOnlySpan<int> DaysToMonth366 => [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
2828

29-
private static volatile Calendar? s_defaultInstance;
29+
private static Calendar? s_defaultInstance;
3030

3131
public override DateTime MinSupportedDateTime => DateTime.MinValue;
3232

src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.cs

+6-7
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public partial class JapaneseCalendar : Calendar
4242

4343
// Using a field initializer rather than a static constructor so that the whole class can be lazy
4444
// init.
45-
private static volatile EraInfo[]? s_japaneseEraInfo;
45+
private static EraInfo[]? s_japaneseEraInfo;
4646

4747
// m_EraInfo must be listed in reverse chronological order. The most recent era
4848
// should be the first element.
@@ -67,20 +67,19 @@ public partial class JapaneseCalendar : Calendar
6767
internal static EraInfo[] GetEraInfo()
6868
{
6969
// See if we need to build it
70-
return s_japaneseEraInfo ??
71-
(s_japaneseEraInfo = GlobalizationMode.UseNls ? NlsGetJapaneseEras() : IcuGetJapaneseEras()) ??
70+
return s_japaneseEraInfo ??=
71+
(GlobalizationMode.UseNls ? NlsGetJapaneseEras() : IcuGetJapaneseEras()) ??
7272
// See if we have to use the built-in eras
73-
(s_japaneseEraInfo = new EraInfo[]
74-
{
73+
[
7574
new EraInfo(5, 2019, 5, 1, 2018, 1, GregorianCalendar.MaxYear - 2018, "\x4ee4\x548c", "\x4ee4", "R"),
7675
new EraInfo(4, 1989, 1, 8, 1988, 1, 2019 - 1988, "\x5e73\x6210", "\x5e73", "H"),
7776
new EraInfo(3, 1926, 12, 25, 1925, 1, 1989 - 1925, "\x662d\x548c", "\x662d", "S"),
7877
new EraInfo(2, 1912, 7, 30, 1911, 1, 1926 - 1911, "\x5927\x6b63", "\x5927", "T"),
7978
new EraInfo(1, 1868, 1, 1, 1867, 1, 1912 - 1867, "\x660e\x6cbb", "\x660e", "M")
80-
});
79+
];
8180
}
8281

83-
internal static volatile Calendar? s_defaultInstance;
82+
internal static Calendar? s_defaultInstance;
8483
internal GregorianCalendarHelper _helper;
8584

8685
internal static Calendar GetDefaultInstance() => s_defaultInstance ??= new JapaneseCalendar();

src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ namespace System.Globalization
3939
/// </remarks>
4040
public sealed class NumberFormatInfo : IFormatProvider, ICloneable
4141
{
42-
private static volatile NumberFormatInfo? s_invariantInfo;
42+
private static NumberFormatInfo? s_invariantInfo;
4343
internal static readonly string[] s_asciiDigits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
4444
internal static readonly int[] s_intArrayWithElement3 = [3];
4545

src/libraries/System.Private.CoreLib/src/System/Globalization/RegionInfo.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class RegionInfo
2121
private readonly CultureData _cultureData;
2222

2323
// The RegionInfo for our current region
24-
internal static volatile RegionInfo? s_currentRegionInfo;
24+
internal static RegionInfo? s_currentRegionInfo;
2525

2626
public RegionInfo(string name)
2727
{
@@ -84,7 +84,6 @@ internal RegionInfo(CultureData cultureData)
8484

8585
/// <summary>
8686
/// This instance provides methods based on the current user settings.
87-
/// These settings are volatile and may change over the lifetime of the
8887
/// </summary>
8988
public static RegionInfo CurrentRegion
9089
{

src/libraries/System.Private.CoreLib/src/System/Globalization/TaiwanCalendar.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class TaiwanCalendar : Calendar
2727
new EraInfo(1, 1912, 1, 1, 1911, 1, GregorianCalendar.MaxYear - 1911) // era #, start year/month/day, yearOffset, minEraYear
2828
};
2929

30-
private static volatile Calendar? s_defaultInstance;
30+
private static Calendar? s_defaultInstance;
3131

3232
private readonly GregorianCalendarHelper _helper;
3333

src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs

+1-3
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ public abstract class Stream : MarshalByRefObject, IDisposable, IAsyncDisposable
2222
private protected SemaphoreSlim EnsureAsyncActiveSemaphoreInitialized() =>
2323
// Lazily-initialize _asyncActiveSemaphore. As we're never accessing the SemaphoreSlim's
2424
// WaitHandle, we don't need to worry about Disposing it in the case of a race condition.
25-
#pragma warning disable CS8774 // We lack a NullIffNull annotation for Volatile.Read
26-
Volatile.Read(ref _asyncActiveSemaphore) ??
27-
#pragma warning restore CS8774
25+
_asyncActiveSemaphore ??
2826
Interlocked.CompareExchange(ref _asyncActiveSemaphore, new SemaphoreSlim(1, 1), null) ??
2927
_asyncActiveSemaphore;
3028

src/libraries/System.Private.CoreLib/src/System/IO/StringWriter.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace System.IO
1212
// the resulting sequence of characters to be presented as a string.
1313
public class StringWriter : TextWriter
1414
{
15-
private static volatile UnicodeEncoding? s_encoding;
15+
private static UnicodeEncoding? s_encoding;
1616

1717
private readonly StringBuilder _sb;
1818
private bool _isOpen;

src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ private enum InternalState
3030
Unloading
3131
}
3232

33-
private static volatile Dictionary<long, WeakReference<AssemblyLoadContext>>? s_allContexts;
33+
private static Dictionary<long, WeakReference<AssemblyLoadContext>>? s_allContexts;
3434
private static long s_nextId;
3535

3636
[MemberNotNull(nameof(s_allContexts))]

src/libraries/System.Private.CoreLib/src/System/Text/EncodingProvider.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,10 @@ internal static void AddProvider(EncodingProvider provider)
118118

119119
internal static Encoding? GetEncodingFromProvider(string encodingName)
120120
{
121-
if (s_providers == null)
121+
EncodingProvider[]? providers = s_providers;
122+
if (providers == null)
122123
return null;
123124

124-
EncodingProvider[] providers = s_providers;
125125
foreach (EncodingProvider provider in providers)
126126
{
127127
Encoding? enc = provider.GetEncoding(encodingName);
@@ -134,10 +134,10 @@ internal static void AddProvider(EncodingProvider provider)
134134

135135
internal static Encoding? GetEncodingFromProvider(int codepage, EncoderFallback enc, DecoderFallback dec)
136136
{
137-
if (s_providers == null)
137+
EncodingProvider[]? providers = s_providers;
138+
if (providers == null)
138139
return null;
139140

140-
EncodingProvider[] providers = s_providers;
141141
foreach (EncodingProvider provider in providers)
142142
{
143143
Encoding? encoding = provider.GetEncoding(codepage, enc, dec);
@@ -150,10 +150,10 @@ internal static void AddProvider(EncodingProvider provider)
150150

151151
internal static Encoding? GetEncodingFromProvider(string encodingName, EncoderFallback enc, DecoderFallback dec)
152152
{
153-
if (s_providers == null)
153+
EncodingProvider[]? providers = s_providers;
154+
if (providers == null)
154155
return null;
155156

156-
EncodingProvider[] providers = s_providers;
157157
foreach (EncodingProvider provider in providers)
158158
{
159159
Encoding? encoding = provider.GetEncoding(encodingName, enc, dec);

src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace System.Threading
2020
public sealed class ExecutionContext : IDisposable, ISerializable
2121
{
2222
internal static readonly ExecutionContext Default = new ExecutionContext();
23-
private static volatile ExecutionContext? s_defaultFlowSuppressed;
23+
private static ExecutionContext? s_defaultFlowSuppressed;
2424

2525
private readonly IAsyncLocalValueMap? m_localValues;
2626
private readonly IAsyncLocal[]? m_localChangeNotifications;

src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs

+8-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Runtime.Versioning;
67

78
namespace System.Threading
@@ -35,7 +36,7 @@ public class ManualResetEventSlim : IDisposable
3536
// These are the default spin counts we use on single-proc and MP machines.
3637
private const int DEFAULT_SPIN_SP = 1;
3738

38-
private volatile object? m_lock;
39+
private object? m_lock;
3940
// A lock used for waiting and pulsing. Lazily initialized via EnsureLockObjectCreated()
4041

4142
private volatile ManualResetEvent? m_eventObj; // A true Win32 event used for waiting.
@@ -199,13 +200,13 @@ private void Initialize(bool initialState, int spinCount)
199200
/// <summary>
200201
/// Helper to ensure the lock object is created before first use.
201202
/// </summary>
203+
[MemberNotNull(nameof(m_lock))]
202204
private void EnsureLockObjectCreated()
203205
{
204-
if (m_lock != null)
205-
return;
206-
207-
object newObj = new object();
208-
Interlocked.CompareExchange(ref m_lock, newObj, null); // failure is benign. Someone else set the value.
206+
if (m_lock is null)
207+
{
208+
Interlocked.CompareExchange(ref m_lock, new object(), null); // failure is benign. Someone else set the value.
209+
}
209210
}
210211

211212
/// <summary>
@@ -538,7 +539,7 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
538539
// We must register and unregister the token outside of the lock, to avoid deadlocks.
539540
using (cancellationToken.UnsafeRegister(s_cancellationTokenCallback, this))
540541
{
541-
lock (m_lock!)
542+
lock (m_lock)
542543
{
543544
// Loop to cope with spurious wakeups from other waits being canceled
544545
while (!IsSet)

src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,23 @@ internal sealed partial class ThreadPoolWorkQueue
2121
internal static class WorkStealingQueueList
2222
{
2323
#pragma warning disable CA1825 // avoid the extra generic instantiation for Array.Empty<T>(); this is the only place we'll ever create this array
24-
private static volatile WorkStealingQueue[] _queues = new WorkStealingQueue[0];
24+
private static WorkStealingQueue[] s_queues = new WorkStealingQueue[0];
2525
#pragma warning restore CA1825
2626

27-
public static WorkStealingQueue[] Queues => _queues;
27+
public static WorkStealingQueue[] Queues => s_queues;
2828

2929
public static void Add(WorkStealingQueue queue)
3030
{
3131
Debug.Assert(queue != null);
3232
while (true)
3333
{
34-
WorkStealingQueue[] oldQueues = _queues;
34+
WorkStealingQueue[] oldQueues = s_queues;
3535
Debug.Assert(Array.IndexOf(oldQueues, queue) < 0);
3636

3737
var newQueues = new WorkStealingQueue[oldQueues.Length + 1];
3838
Array.Copy(oldQueues, newQueues, oldQueues.Length);
3939
newQueues[^1] = queue;
40-
if (Interlocked.CompareExchange(ref _queues, newQueues, oldQueues) == oldQueues)
40+
if (Interlocked.CompareExchange(ref s_queues, newQueues, oldQueues) == oldQueues)
4141
{
4242
break;
4343
}
@@ -49,7 +49,7 @@ public static void Remove(WorkStealingQueue queue)
4949
Debug.Assert(queue != null);
5050
while (true)
5151
{
52-
WorkStealingQueue[] oldQueues = _queues;
52+
WorkStealingQueue[] oldQueues = s_queues;
5353
if (oldQueues.Length == 0)
5454
{
5555
return;
@@ -77,7 +77,7 @@ public static void Remove(WorkStealingQueue queue)
7777
Array.Copy(oldQueues, pos + 1, newQueues, pos, newQueues.Length - pos);
7878
}
7979

80-
if (Interlocked.CompareExchange(ref _queues, newQueues, oldQueues) == oldQueues)
80+
if (Interlocked.CompareExchange(ref s_queues, newQueues, oldQueues) == oldQueues)
8181
{
8282
break;
8383
}

src/libraries/System.Private.CoreLib/src/System/Type.cs

+5-13
Original file line numberDiff line numberDiff line change
@@ -705,20 +705,12 @@ public override int GetHashCode()
705705
[Obsolete(Obsoletions.ReflectionOnlyLoadingMessage, DiagnosticId = Obsoletions.ReflectionOnlyLoadingDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
706706
public static Type? ReflectionOnlyGetType(string typeName, bool throwIfNotFound, bool ignoreCase) => throw new PlatformNotSupportedException(SR.PlatformNotSupported_ReflectionOnly);
707707

708-
public static Binder DefaultBinder
709-
{
710-
get
711-
{
712-
if (s_defaultBinder == null)
713-
{
714-
DefaultBinder binder = new DefaultBinder();
715-
Interlocked.CompareExchange<Binder?>(ref s_defaultBinder, binder, null);
716-
}
717-
return s_defaultBinder!;
718-
}
719-
}
708+
public static Binder DefaultBinder =>
709+
s_defaultBinder ??
710+
Interlocked.CompareExchange(ref s_defaultBinder, new DefaultBinder(), null) ??
711+
s_defaultBinder;
720712

721-
private static volatile Binder? s_defaultBinder;
713+
private static Binder? s_defaultBinder;
722714

723715
public static readonly char Delimiter = '.';
724716
public static readonly Type[] EmptyTypes = Array.Empty<Type>();

0 commit comments

Comments
 (0)