Skip to content

Commit e22e948

Browse files
committed
Implement copy-on-write for flags enum caches to fix race condition.
1 parent 7727946 commit e22e948

File tree

3 files changed

+42
-18
lines changed

3 files changed

+42
-18
lines changed

ClassLibraries/Macross.Json.Extensions/Code/Macross.Json.Extensions.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
2727
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
2828
<VersionPrefix>2.1.0</VersionPrefix>
29-
<VersionSuffix>beta3</VersionSuffix>
29+
<VersionSuffix>beta4</VersionSuffix>
3030
<FileVersion>$(VersionPrefix)</FileVersion>
3131
<PackageProjectUrl>https://github.com/Macross-Software/core/tree/develop/ClassLibraries/Macross.Json.Extensions</PackageProjectUrl>
3232
</PropertyGroup>

ClassLibraries/Macross.Json.Extensions/Code/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010

1111
[assembly: InternalsVisibleTo("Macross.Json.Extensions.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051b7a480b13cecfa44862449486c6884bd6168c325445a0848f48deca9643657c5ae85df3cf6ffdb24d5bd3e9b71dc074ca602544b83511fbce1f83f1d06bb8b7b564414c9d8c719e4e39b95643dfc8e9ce997b5e2a1542a8ff6379186f87b8b695fee82c506170c4fb8ffcbf2e68f4b5d270083f8909c67916500608ce747e9")]
1212

13-
[assembly: AssemblyVersion("2.1.0.21178")]
13+
[assembly: AssemblyVersion("2.1.0.21206")]

ClassLibraries/Macross.Json.Extensions/Code/System.Text.Json.Serialization/JsonStringEnumMemberConverterHelper.cs

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public EnumInfo(string name, TEnum enumValue, ulong rawValue)
2727
}
2828

2929
private const BindingFlags EnumBindings = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static;
30+
private const int MaximumAutoGrowthCacheSize = 64;
3031

3132
#if NETSTANDARD2_0
3233
private static readonly string[] s_Split = new string[] { ", " };
@@ -38,8 +39,10 @@ public EnumInfo(string name, TEnum enumValue, ulong rawValue)
3839
private readonly Type _EnumType;
3940
private readonly TypeCode _EnumTypeCode;
4041
private readonly bool _IsFlags;
41-
private readonly Dictionary<TEnum, EnumInfo> _RawToTransformed;
42-
private readonly Dictionary<string, EnumInfo> _TransformedToRaw;
42+
private readonly object _TransformedToRawCopyLockObject = new();
43+
private readonly object _RawToTransformedCopyLockObject = new();
44+
private Dictionary<TEnum, EnumInfo> _RawToTransformed;
45+
private Dictionary<string, EnumInfo> _TransformedToRaw;
4346

4447
public JsonStringEnumMemberConverterHelper(JsonStringEnumMemberConverterOptions? options)
4548
{
@@ -58,10 +61,12 @@ public JsonStringEnumMemberConverterHelper(JsonStringEnumMemberConverterOptions?
5861
string[] builtInNames = _EnumType.GetEnumNames();
5962
Array builtInValues = _EnumType.GetEnumValues();
6063

61-
_RawToTransformed = new Dictionary<TEnum, EnumInfo>();
62-
_TransformedToRaw = new Dictionary<string, EnumInfo>();
64+
int numberOfBuiltInNames = builtInNames.Length;
6365

64-
for (int i = 0; i < builtInNames.Length; i++)
66+
_RawToTransformed = new Dictionary<TEnum, EnumInfo>(numberOfBuiltInNames);
67+
_TransformedToRaw = new Dictionary<string, EnumInfo>(numberOfBuiltInNames);
68+
69+
for (int i = 0; i < numberOfBuiltInNames; i++)
6570
{
6671
Enum? enumValue = (Enum?)builtInValues.GetValue(i);
6772
if (enumValue == null)
@@ -101,8 +106,10 @@ public TEnum Read(ref Utf8JsonReader reader)
101106
{
102107
string enumString = reader.GetString()!;
103108

109+
Dictionary<string, EnumInfo> transformedToRaw = _TransformedToRaw;
110+
104111
// Case sensitive search attempted first.
105-
if (_TransformedToRaw.TryGetValue(enumString, out EnumInfo? enumInfo))
112+
if (transformedToRaw.TryGetValue(enumString, out EnumInfo? enumInfo))
106113
return enumInfo.EnumValue;
107114

108115
if (_IsFlags)
@@ -117,15 +124,15 @@ public TEnum Read(ref Utf8JsonReader reader)
117124
foreach (string flagValue in flagValues)
118125
{
119126
// Case sensitive search attempted first.
120-
if (_TransformedToRaw.TryGetValue(flagValue, out enumInfo))
127+
if (transformedToRaw.TryGetValue(flagValue, out enumInfo))
121128
{
122129
calculatedValue |= enumInfo.RawValue;
123130
}
124131
else
125132
{
126133
// Case insensitive search attempted second.
127134
bool matched = false;
128-
foreach (KeyValuePair<string, EnumInfo> enumItem in _TransformedToRaw)
135+
foreach (KeyValuePair<string, EnumInfo> enumItem in transformedToRaw)
129136
{
130137
if (string.Equals(enumItem.Key, flagValue, StringComparison.OrdinalIgnoreCase))
131138
{
@@ -146,15 +153,23 @@ public TEnum Read(ref Utf8JsonReader reader)
146153
}
147154

148155
TEnum enumValue = (TEnum)Enum.ToObject(_EnumType, calculatedValue);
149-
if (_TransformedToRaw.Count < 64)
156+
if (transformedToRaw.Count < MaximumAutoGrowthCacheSize)
150157
{
151-
_TransformedToRaw[enumString] = new EnumInfo(enumString, enumValue, calculatedValue);
158+
lock (_TransformedToRawCopyLockObject)
159+
{
160+
if (!_TransformedToRaw.ContainsKey(enumString) && _TransformedToRaw.Count < MaximumAutoGrowthCacheSize)
161+
{
162+
Dictionary<string, EnumInfo> transformedToRawCopy = new(_TransformedToRaw);
163+
transformedToRawCopy[enumString] = new EnumInfo(enumString, enumValue, calculatedValue);
164+
_TransformedToRaw = transformedToRawCopy;
165+
}
166+
}
152167
}
153168
return enumValue;
154169
}
155170

156171
// Case insensitive search attempted second.
157-
foreach (KeyValuePair<string, EnumInfo> enumItem in _TransformedToRaw)
172+
foreach (KeyValuePair<string, EnumInfo> enumItem in transformedToRaw)
158173
{
159174
if (string.Equals(enumItem.Key, enumString, StringComparison.OrdinalIgnoreCase))
160175
{
@@ -232,7 +247,8 @@ public TEnum Read(ref Utf8JsonReader reader)
232247

233248
public void Write(Utf8JsonWriter writer, TEnum value)
234249
{
235-
if (_RawToTransformed.TryGetValue(value, out EnumInfo? enumInfo))
250+
Dictionary<TEnum, EnumInfo> rawToTransformed = _RawToTransformed;
251+
if (rawToTransformed.TryGetValue(value, out EnumInfo? enumInfo))
236252
{
237253
writer.WriteStringValue(enumInfo.Name);
238254
return;
@@ -244,8 +260,8 @@ public void Write(Utf8JsonWriter writer, TEnum value)
244260
{
245261
ulong calculatedValue = 0;
246262

247-
StringBuilder Builder = new StringBuilder();
248-
foreach (KeyValuePair<TEnum, EnumInfo> enumItem in _RawToTransformed)
263+
StringBuilder Builder = new();
264+
foreach (KeyValuePair<TEnum, EnumInfo> enumItem in rawToTransformed)
249265
{
250266
enumInfo = enumItem.Value;
251267
if (!value.HasFlag(enumInfo.EnumValue)
@@ -264,9 +280,17 @@ public void Write(Utf8JsonWriter writer, TEnum value)
264280
if (calculatedValue == rawValue)
265281
{
266282
string finalName = Builder.ToString();
267-
if (_RawToTransformed.Count < 64)
283+
if (rawToTransformed.Count < MaximumAutoGrowthCacheSize)
268284
{
269-
_RawToTransformed[value] = new EnumInfo(finalName, value, rawValue);
285+
lock (_RawToTransformedCopyLockObject)
286+
{
287+
if (!_RawToTransformed.ContainsKey(value) && _RawToTransformed.Count < MaximumAutoGrowthCacheSize)
288+
{
289+
Dictionary<TEnum, EnumInfo> rawToTransformedCopy = new(_RawToTransformed);
290+
rawToTransformedCopy[value] = new EnumInfo(finalName, value, rawValue);
291+
_RawToTransformed = rawToTransformedCopy;
292+
}
293+
}
270294
}
271295
writer.WriteStringValue(finalName);
272296
return;

0 commit comments

Comments
 (0)