-
-
Notifications
You must be signed in to change notification settings - Fork 231
Description
Issue
Unloading a module can intermittently cause a not_purged error. I believe the reason for this is that there is race condition during terminate where the erlang automatically loads the module just before meck attempts to do the same.
I made a small reproduction for this by adding a dummy module:
-module(dummy_target).
-export([do_work/0]).
do_work() -> ok.
Since this is intermittent, I used a sleep to increase the timestamp where I believe the issue occurs:
diff --git a/src/meck_proc.erl b/src/meck_proc.erl
index f14d448..df425a9 100644
--- a/src/meck_proc.erl
+++ b/src/meck_proc.erl
@@ -363,6 +363,7 @@ terminate(_Reason, #state{mod = Mod, original = OriginalState,
restore_original(Mod, OriginalState, WasSticky, BackupCover),
case Restore andalso false =:= code:is_loaded(Mod) of
true ->
+ timer:sleep(500),
% We make a best effort to reload the module here. Since this runs
% in a terminating process there is nothing we can do to recover if
% the loading fails.
I can then call this sequence to reproduce:
1> dummy_target:do_work(),
meck:new(dummy_target),
spawn(fun() -> meck:unload(dummy_target) end),
timer:sleep(200),
dummy_target:do_work().
ok
=ERROR REPORT==== 16-Mar-2026::16:38:46.728600 ===
Loading of /Users/myname/meck/dummy_target.beam failed: not_purged
The snippet has a shorter sleep in order to hit the 500ms window above. The call to dummy_target:do_work will then cause the module to be loaded, leading to the failure when meck calls code:load_file(Mod)
Suggested fix
Erlang has a function code:ensure_loaded with this description:
Tries to load a module in the same way as `load_file/1`, unless the module is
already loaded.
If called concurrently, this function ensures that only one process
attempts to load said module at a given time.
The following change resolves the problem in the reproduction
diff --git a/src/meck_proc.erl b/src/meck_proc.erl
index f14d448..c23b9e5 100644
--- a/src/meck_proc.erl
+++ b/src/meck_proc.erl
@@ -366,7 +366,7 @@ terminate(_Reason, #state{mod = Mod, original = OriginalState,
% We make a best effort to reload the module here. Since this runs
% in a terminating process there is nothing we can do to recover if
% the loading fails.
- _ = code:load_file(Mod),
+ _ = code:ensure_loaded(Mod),
ok;
_ ->
ok