-
-
Notifications
You must be signed in to change notification settings - Fork 231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix race condition between meck:unload/1 and calls to the mocked module #150
Fix race condition between meck:unload/1 and calls to the mocked module #150
Conversation
Hi! I agree this edge case needs to be handled. I looked at the solution, and I'll try to summarize how the test case works. Correct me if I'm wrong.
Isn't there a race condition, because both processes are spawned, and then check for Also, what does this test in practice? The order in the message queue is not guaranteed (because of the above problem) and it is not guaranteed that the test will actually call the mock in the state where the process is gone but the module not yet reloaded? |
You are correct in bullet points 1-4, however there's a mistake in your conclusion.
So this construct will guarantee that there will be two messages in the Regarding the what do we test in practice question: there are many possible ordering of events that could lead to this race condition. The test picks one possible ordering (which we can reproduce deterministically thanks to the suspend/resume mechanism):
But the following scenario would also lead to the same result:
Or:
Or:
And so on. Basically the only constraints for the issue to turn up are that:
Btw. you can check that this test deterministically fails in 6b3eaf9, where the fix is not yet implemented. Which sort of proves the correctness of the test. 😄 |
I think I get it: the call to |
I think turning invalidate into a cast is fine, if the process is down by that point getting the state of the mock won't work anyway. Now on to the more philosophical question: Is this actually a nice behavior? Getting the |
I think the nice behaviour would be that while Please also consider that once I also doubt this change would produce "false" test results. First of all, most tests are synchronous, they execute all operations in the same process that calls In my particular case when this issue comes up I start application A to test application B. I change A's behaviour slightly with And my final argument is that before this patch if your test happens to make a concurrent call to a module being unloaded three things may occur:
This patch would only eliminate the option 3, the weird timing issue. You may still have timing issues (if your test depends on specifically either option 1 or option 2), but that would be more likely to turn up and easier to debug. |
True, it is a good point that the behavior is the same after the module is fully unloaded anyway. |
Fix race condition between meck:unload/1 and calls to the mocked module
💯 |
When
meck:unload(M)
and a call to the mocked module being unloaded (e.g.M:foo()
) happens concurrently, the call may fail with error{not_mocked, M}
(similar to e.g. #144, although that may be a different issue).The correct behaviour would be for
M:foo()
to either procude the mocked result or execute the original, unmocked code, but it shouldn't fail.This could be achieved by
meck_code_gen:exec/4
catching the error thrown whenmeck_proc
is down, and failing back to a regular apply into the (no longer mocked) code instead. (Turningmeck_proc:invalidate/1
into a cast from a call ensures onlymeck_proc:get_result_spec/3
could fail with said error from the functions used withinmeck_code_gen:exec/4
.)