Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions YamlDotNet.Test/Serialization/DeserializerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
// SOFTWARE.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using FluentAssertions;
using Xunit;
using YamlDotNet.Core;
Expand Down Expand Up @@ -343,6 +345,60 @@ public void SerializeStateMethodsGetCalledOnce()
Assert.Equal(1, test.OnDeserializedCallCount);
Assert.Equal(1, test.OnDeserializingCallCount);
}

[Fact]
public void DeserializeConcurrently()
{
var exceptions = new ConcurrentStack<Exception>();
var runCount = 10;

for (var i = 0; i < runCount; i++)
{
// Failures don't occur consistently - running repeatedly increases the chances
RunTest();
}

Assert.Empty(exceptions);

void RunTest()
{
var threadCount = 100;
var threads = new List<Thread>();
var control = new SemaphoreSlim(0, threadCount);

var yaml = "Test: Hi";
var deserializer = new DeserializerBuilder().Build();

for (var i = 0; i < threadCount; i++)
{
threads.Add(new Thread(Deserialize));
}

threads.ForEach(t => t.Start());
// Each thread will wait for the semaphore before proceeding.
// Release them all simultaneously to maximise concurrency
control.Release(threadCount);
threads.ForEach(t => t.Join());

Assert.Empty(exceptions);
return;

void Deserialize()
{
control.Wait();

try
{
var result = deserializer.Deserialize<TestState>(yaml);
result.Test.Should().Be("Hi");
}
catch (Exception e)
{
exceptions.Push(e.InnerException ?? e);
}
}
}
}

public class TestState
{
Expand Down
52 changes: 52 additions & 0 deletions YamlDotNet.Test/Serialization/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
Expand All @@ -31,6 +32,7 @@
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using FakeItEasy;
using FluentAssertions;
using FluentAssertions.Common;
Expand Down Expand Up @@ -2407,6 +2409,56 @@ public void SerializeStateMethodsGetCalledOnce()
Assert.Equal(1, test.OnSerializedCallCount);
Assert.Equal(1, test.OnSerializingCallCount);
}

[Fact]
public void SerializeConcurrently()
{
var exceptions = new ConcurrentStack<Exception>();
var runCount = 10;

for (var i = 0; i < runCount; i++)
{
// Failures don't occur consistently - running repeatedly increases the chances
RunTest();
}

Assert.Empty(exceptions);

void RunTest()
{
var threadCount = 100;
var threads = new List<Thread>();
var control = new SemaphoreSlim(0, threadCount);

var serializer = new SerializerBuilder().Build();

for (var i = 0; i < threadCount; i++)
{
threads.Add(new Thread(Serialize));
}

threads.ForEach(t => t.Start());
// Each thread will wait for the semaphore before proceeding.
// Release them all simultaneously to try to maximise concurrency
control.Release(threadCount);
threads.ForEach(t => t.Join());

void Serialize()
{
control.Wait();

try
{
var test = new TestState();
serializer.Serialize(test);
}
catch (Exception e)
{
exceptions.Push(e.InnerException ?? e);
}
}
}
}

[Fact]
public void SerializeEnumAsNumber()
Expand Down
27 changes: 12 additions & 15 deletions YamlDotNet/Serialization/ObjectFactories/DefaultObjectFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand All @@ -33,12 +34,12 @@ namespace YamlDotNet.Serialization.ObjectFactories
/// </summary>
public sealed class DefaultObjectFactory : ObjectFactoryBase
{
private readonly Dictionary<Type, Dictionary<Type, MethodInfo[]>> _stateMethods = new Dictionary<Type, Dictionary<Type, MethodInfo[]>>
private readonly Dictionary<Type, ConcurrentDictionary<Type, MethodInfo[]>> _stateMethods = new Dictionary<Type, ConcurrentDictionary<Type, MethodInfo[]>>
{
{ typeof(OnDeserializedAttribute), new Dictionary<Type, MethodInfo[]>() },
{ typeof(OnDeserializingAttribute), new Dictionary<Type, MethodInfo[]>() },
{ typeof(OnSerializedAttribute), new Dictionary<Type, MethodInfo[]>() },
{ typeof(OnSerializingAttribute), new Dictionary<Type, MethodInfo[]>() },
{ typeof(OnDeserializedAttribute), new ConcurrentDictionary<Type, MethodInfo[]>() },
{ typeof(OnDeserializingAttribute), new ConcurrentDictionary<Type, MethodInfo[]>() },
{ typeof(OnSerializedAttribute), new ConcurrentDictionary<Type, MethodInfo[]>() },
{ typeof(OnSerializingAttribute), new ConcurrentDictionary<Type, MethodInfo[]>() },
};

private readonly Dictionary<Type, Type> DefaultGenericInterfaceImplementations = new Dictionary<Type, Type>
Expand Down Expand Up @@ -147,17 +148,13 @@ private MethodInfo[] GetStateMethods(Type attributeType, Type valueType)
{
var stateDictionary = _stateMethods[attributeType];

if (stateDictionary.TryGetValue(valueType, out var methods))
return stateDictionary.GetOrAdd(valueType, type =>
{
return methods;
}

methods = valueType.GetMethods(BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.NonPublic);
methods = methods.Where(x => x.GetCustomAttributes(attributeType, true).Any()).ToArray();
stateDictionary[valueType] = methods;
return methods;
var methods = type.GetMethods(BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.NonPublic);
return methods.Where(x => x.GetCustomAttributes(attributeType, true).Any()).ToArray();
});
}
}
}