This is a very strange problem that I spent this day trying to track. I'm not sure if this is a mistake, but it would be great to get some perspective and thoughts on why this is happening.
I use xUnit (2.0) to run my unit tests. The beauty of xUnit is that it automatically runs tests in parallel for you. However, the problem that I discovered is that Constructor.GetParameters
does not seem to be thread safe when ConstructorInfo
marked as a threadlike type. That is, if two threads reach Constructor.GetParameters
at the same time, two results are generated, and subsequent calls to this method return the second result that was created (regardless of the thread that calls it).
I created code to demonstrate this unexpected behavior ( I also posted it on GitHub if you want to download and try the project locally).
Here is the code:
public class OneClass { readonly ITestOutputHelper output; public OneClass( ITestOutputHelper output ) { this.output = output; } [Fact] public void OutputHashCode() { Support.Add( typeof(SampleObject).GetTypeInfo() ); output.WriteLine( "Initialized:" ); Support.Output( output ); Support.Add( typeof(SampleObject).GetTypeInfo() ); output.WriteLine( "After Initialized:" ); Support.Output( output ); } } public class AnotherClass { readonly ITestOutputHelper output; public AnotherClass( ITestOutputHelper output ) { this.output = output; } [Fact] public void OutputHashCode() { Support.Add( typeof(SampleObject).GetTypeInfo() ); output.WriteLine( "Initialized:" ); Support.Output( output ); Support.Add( typeof(SampleObject).GetTypeInfo() ); output.WriteLine( "After Initialized:" ); Support.Output( output ); } } public static class Support { readonly static ICollection<int> Numbers = new List<int>(); public static void Add( TypeInfo info ) { var code = info.DeclaredConstructors.Single().GetParameters().Single().GetHashCode(); Numbers.Add( code ); } public static void Output( ITestOutputHelper output ) { foreach ( var number in Numbers.ToArray() ) { output.WriteLine( number.ToString() ); } } } public class SampleObject { public SampleObject( object parameter ) {} }
Two test classes ensure that two threads are created and executed in parallel. After running these tests, you should get results that look like this:
Initialized: 39053774 <---- Different! 45653674 After Initialized: 39053774 <---- Different! 45653674 45653674 45653674
(NOTE: I added "<---- Different!" To indicate an unexpected value. This will not be visible in the test results.)
As you can see, the result of the very first GetParameters
call returns a different value than all subsequent calls.
I have long had a nose in .NET, but I have never seen anything like it. Is this expected behavior? Is there a preferred / known way to initialize a system like .NET to prevent this from happening?
Finally, if anyone is interested, I ran into this problem when using xUnit with MEF 2, where the ParameterInfo parameter used as the key in the dictionary is not returned as equal to ParameterInfo passed from the previously saved value . This, of course, leads to unexpected behavior and leads to unsuccessful tests while running at the same time.
EDIT: After some good feedback with the answers, I (hopefully) clarified this question and scenario. The core of the problem is Thread Safety, such as Thead-Safe, and a better knowledge of exactly what this means.
ANSWER:. This problem ended up being caused by several factors, one of which is related to the fact that I am infinitely unfamiliar with multi-threaded scripts, which, it seems, I will study forever without end in the foreseeable future. Again, I thank xUnit for being in such a way as to explore this territory in this way.
Another problem seems to be inconsistency with the way a .NET type system is initialized. With TypeInfo / Type, you get the same type / reference / hashcode no matter which thread accesses it, how many times. For MemberInfo / MethodInfo / ParameterInfo this is not the case. Prevent file access.
Finally, it seems that I'm not the only person with this confusion, and this is indeed recognized as an unacceptable assumption about the .NET Core GitHub repository issue presented .
So, the problem is solved, basically. I would like to thank everyone who participated in my ignorance in this matter and help me find out (what I find) this is a very difficult problem space.