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.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -695,7 +696,7 @@ namespace Metsys.Bson
|
||||
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = "Bson uses reflection")]
|
||||
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 readonly IDictionary<string, MagicProperty> _properties;
|
||||
@@ -725,13 +726,7 @@ namespace Metsys.Bson
|
||||
|
||||
public static TypeHelper GetHelperForType(Type type)
|
||||
{
|
||||
TypeHelper helper;
|
||||
if (!_cachedTypeLookup.TryGetValue(type, out helper))
|
||||
{
|
||||
helper = new TypeHelper(type);
|
||||
_cachedTypeLookup[type] = helper;
|
||||
}
|
||||
return helper;
|
||||
return _cachedTypeLookup.GetOrAdd(type, t => new TypeHelper(t));
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
_disposables.ForEach(d => d.Dispose());
|
||||
|
||||
Reference in New Issue
Block a user