Fix thread-safety issue causing infinite loops in TypeHelper cache (#20113)
* Initial plan * Fix thread-safety issue by replacing Dictionary with ConcurrentDictionary Co-authored-by: grokys <1775141+grokys@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: grokys <1775141+grokys@users.noreply.github.com>
This commit is contained in:
@@ -30,6 +30,7 @@ Code imported from https://github.com/elaberge/Metsys.Bson without any changes
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
@@ -695,7 +696,7 @@ namespace Metsys.Bson
|
|||||||
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = "Bson uses reflection")]
|
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = "Bson uses reflection")]
|
||||||
internal class TypeHelper
|
internal class TypeHelper
|
||||||
{
|
{
|
||||||
private static readonly IDictionary<Type, TypeHelper> _cachedTypeLookup = new Dictionary<Type, TypeHelper>();
|
private static readonly ConcurrentDictionary<Type, TypeHelper> _cachedTypeLookup = new ConcurrentDictionary<Type, TypeHelper>();
|
||||||
private static readonly BsonConfiguration _configuration = BsonConfiguration.Instance;
|
private static readonly BsonConfiguration _configuration = BsonConfiguration.Instance;
|
||||||
|
|
||||||
private readonly IDictionary<string, MagicProperty> _properties;
|
private readonly IDictionary<string, MagicProperty> _properties;
|
||||||
@@ -725,13 +726,7 @@ namespace Metsys.Bson
|
|||||||
|
|
||||||
public static TypeHelper GetHelperForType(Type type)
|
public static TypeHelper GetHelperForType(Type type)
|
||||||
{
|
{
|
||||||
TypeHelper helper;
|
return _cachedTypeLookup.GetOrAdd(type, t => new TypeHelper(t));
|
||||||
if (!_cachedTypeLookup.TryGetValue(type, out helper))
|
|
||||||
{
|
|
||||||
helper = new TypeHelper(type);
|
|
||||||
_cachedTypeLookup[type] = helper;
|
|
||||||
}
|
|
||||||
return helper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string FindProperty(LambdaExpression lambdaExpression)
|
public static string FindProperty(LambdaExpression lambdaExpression)
|
||||||
|
|||||||
@@ -162,6 +162,48 @@ namespace Avalonia.DesignerSupport.Tests
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[SuppressMessage("Usage", "xUnit1031:Do not use blocking task operations in test method", Justification = "Sync context is explicitly disabled")]
|
||||||
|
void BsonSerializationIsThreadSafe()
|
||||||
|
{
|
||||||
|
Init();
|
||||||
|
// This test verifies that concurrent serialization doesn't cause infinite loops
|
||||||
|
// or corruption in the TypeHelper cache
|
||||||
|
var messages = Enumerable.Range(0, 100).Select(i => new MeasureViewportMessage
|
||||||
|
{
|
||||||
|
Width = i,
|
||||||
|
Height = i * 2
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
var exceptions = new ConcurrentBag<Exception>();
|
||||||
|
|
||||||
|
// Spawn multiple threads that all try to serialize messages concurrently
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
var task = Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var message in messages)
|
||||||
|
{
|
||||||
|
_client.Send(message).Wait(TimeoutInMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
exceptions.Add(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tasks.Add(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.WaitAll(tasks.ToArray(), TimeoutInMs * messages.Length * 10);
|
||||||
|
|
||||||
|
// Verify no exceptions occurred
|
||||||
|
Assert.Empty(exceptions);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_disposables.ForEach(d => d.Dispose());
|
_disposables.ForEach(d => d.Dispose());
|
||||||
|
|||||||
Reference in New Issue
Block a user