ocmock
ocmock copied to clipboard
Make expectation recording thread-safe.
To handle a mocked invocation, we do the following:
- Get the first stub that matches the invocation.
- If the stub is an expectation, remove the stub from the arrays of stubs and expectations.
- Pass the invocation to the stub.
The first two steps are currently not atomic, so there is a race condition. For example:
- Add expectation 1 for method A.
- Add expectation 2 for same method A.
- [Thread 1] Call method A.
- [Thread 2] Call method A.
- [Thread 1] Get expectation 1 since it matches the invocation.
- [Thread 2] Get expectation 1 since it matches the invocation.
- [Thread 1] Remove expectation 1 from the arrays.
- [Thread 2] Remove expectation 1 from the arrays.
In the above example, the invocations of the same method in Thread 1 and Thread 2 both match expectation 1; expectation 2 does not get matched.
The solution is to make the matching and the removal atomic. We can do so by wrapping the relevant code in @synchronized(stubs). (Even though stubForInvocation: also performs @synchronized(stubs), we can feel free to do the same in the caller without fear of deadlocks since @synchronized uses a recursive lock.)
Added a new unit test that was failing before the fix and is now passing after the fix.