Parallel.ForEach and NMock3

Sep 21, 2010 at 1:36 PM

In .net 4.0 you have the ability to use parallel APIs for simple for and foreach statements quite easy. After trying this, I ran into problems with MockObject.Invoke because it is not synchronized in any way and it's using collection writes without locking.

In order to fully benefit from NMock3 and the new parallel APIs I think we need thread safe mock objects. Possibly an IMockObjectFactory that uses a thread safe IInterceptor implementation.

IMHO it doesn't have to be thread safe while creating the mock objects and setting the expectations. But it has to be thread safe when calling the mock objects methods.

What do you think? It there an easy way of implementing something like this?

Sep 23, 2010 at 3:29 AM

Hi Sockenfresser,

I saw this request come across the NMock2 mailing list.  Glad you found us here at NMock3.  I should be able to implement it.  Can you write a failing unit test and post it here?

2 things to consider:
1. The NMock API will be changing with our next release and it will not be backward compatible with NMock2.  The migration will be straight forward but not automatic.  I will not be doing any modifications to the NMock2 release (our last release).  If that release is important to you, you can get the code on the "Source Code" tab.
2. If the problem is in the Castle.DynamicProxy code, I will forward the request to that team.  I will then wait for a fix from them.

Sep 23, 2010 at 7:03 AM

Hi pwolfe,

here is the unit test you asked for.

        // Interface to create a stub for
        public interface ITestElement
            bool Matches(int toMatch);

        public void Stub_CallingStubMethodFromDifferentThreads_NoExceptionShouldBeThrown()
            Stub<ITestElement> stubElement1 = factory.CreateStub<ITestElement>();
            Stub<ITestElement> stubElement2 = factory.CreateStub<ITestElement>();
            stubElement1.Out.MethodWith(e => e.Matches(1)).WillReturn(true);
            stubElement2.Out.MethodWith(e => e.Matches(1)).WillReturn(true);
            stubElement1.Out.MethodWith(e => e.Matches(2)).WillReturn(true);
            stubElement2.Out.MethodWith(e => e.Matches(2)).WillReturn(true);
            stubElement1.Out.MethodWith(e => e.Matches(3)).WillReturn(true);
            stubElement2.Out.MethodWith(e => e.Matches(3)).WillReturn(true);
            stubElement1.Out.MethodWith(e => e.Matches(5)).WillReturn(true);
            stubElement2.Out.MethodWith(e => e.Matches(5)).WillReturn(true);
            stubElement1.Out.MethodWith(e => e.Matches(7)).WillReturn(true);
            stubElement2.Out.MethodWith(e => e.Matches(7)).WillReturn(true);
            stubElement1.Out.MethodWith(e => e.Matches(8)).WillReturn(true);
            stubElement2.Out.MethodWith(e => e.Matches(9)).WillReturn(true);

            System.Threading.Tasks.Parallel.ForEach(new int[]{1,2,3,4,5,6,7,8,9,10},
                testElement =>

            // No Exception should be thrown

It's .net 4.0 only test, because of the use of the Parallel API. I don't know if this one will fail on every machine. It's more like an integration test and the behaviour will depend on the number of cores maybe. I have an i7 with 4 cores plus HT. So maybe it only happens with a lot of threads. I know the problem itself is depending on timing. On my machine it fails every time. Please try it yourself.

Sep 24, 2010 at 2:22 PM

What do you think of these lines?

stubElement1.Stub.Out.Method(e => e.Matches(1)).WithArguments(Is.In(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 })).WillReturn(true);

stubElement2.Stub.Out.Method(e => e.Matches(1)).WithArguments(Is.In(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 })).WillReturn(true);

Because you don't know which stub will get which integer in the ForEach loop, you would have to add all integers to both stubs.  By doing this both stubs are ready for all integers and will return true.  I updated the test to be like this and it does not throw an exception.  I was expecting an exception when a collection internally was referenced but I have not been able to create that.  I will get a checkin today and you can see the test and the new bits.