Skip to content

Commit 6b7a3e8

Browse files
Copilotgrokys
andcommitted
Fix thread-safety issue by replacing Dictionary with ConcurrentDictionary
Co-authored-by: grokys <1775141+grokys@users.noreply.github.com>
1 parent e5f0d64 commit 6b7a3e8

2 files changed

Lines changed: 45 additions & 8 deletions

File tree

src/Avalonia.Remote.Protocol/MetsysBson.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
3030

3131
using System;
3232
using System.Collections;
33+
using System.Collections.Concurrent;
3334
using System.Collections.Generic;
3435
using System.Diagnostics;
3536
using System.Diagnostics.CodeAnalysis;
@@ -695,7 +696,7 @@ namespace Metsys.Bson
695696
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = "Bson uses reflection")]
696697
internal class TypeHelper
697698
{
698-
private static readonly IDictionary<Type, TypeHelper> _cachedTypeLookup = new Dictionary<Type, TypeHelper>();
699+
private static readonly ConcurrentDictionary<Type, TypeHelper> _cachedTypeLookup = new ConcurrentDictionary<Type, TypeHelper>();
699700
private static readonly BsonConfiguration _configuration = BsonConfiguration.Instance;
700701

701702
private readonly IDictionary<string, MagicProperty> _properties;
@@ -725,13 +726,7 @@ public MagicProperty FindProperty(string name)
725726

726727
public static TypeHelper GetHelperForType(Type type)
727728
{
728-
TypeHelper helper;
729-
if (!_cachedTypeLookup.TryGetValue(type, out helper))
730-
{
731-
helper = new TypeHelper(type);
732-
_cachedTypeLookup[type] = helper;
733-
}
734-
return helper;
729+
return _cachedTypeLookup.GetOrAdd(type, t => new TypeHelper(t));
735730
}
736731

737732
public static string FindProperty(LambdaExpression lambdaExpression)

tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,48 @@ void RemoteProtocolShouldBeBackwardsCompatible()
162162

163163
}
164164

165+
[Fact]
166+
[SuppressMessage("Usage", "xUnit1031:Do not use blocking task operations in test method", Justification = "Sync context is explicitly disabled")]
167+
void BsonSerializationIsThreadSafe()
168+
{
169+
Init();
170+
// This test verifies that concurrent serialization doesn't cause infinite loops
171+
// or corruption in the TypeHelper cache
172+
var messages = Enumerable.Range(0, 100).Select(i => new MeasureViewportMessage
173+
{
174+
Width = i,
175+
Height = i * 2
176+
}).ToArray();
177+
178+
var tasks = new List<Task>();
179+
var exceptions = new ConcurrentBag<Exception>();
180+
181+
// Spawn multiple threads that all try to serialize messages concurrently
182+
for (int i = 0; i < 10; i++)
183+
{
184+
var task = Task.Run(() =>
185+
{
186+
try
187+
{
188+
foreach (var message in messages)
189+
{
190+
_client.Send(message).Wait(TimeoutInMs);
191+
}
192+
}
193+
catch (Exception ex)
194+
{
195+
exceptions.Add(ex);
196+
}
197+
});
198+
tasks.Add(task);
199+
}
200+
201+
Task.WaitAll(tasks.ToArray(), TimeoutInMs * messages.Length * 10);
202+
203+
// Verify no exceptions occurred
204+
Assert.Empty(exceptions);
205+
}
206+
165207
public void Dispose()
166208
{
167209
_disposables.ForEach(d => d.Dispose());

0 commit comments

Comments
 (0)