Skip to content

meck:unload fails with not_purged #261

@sharyari

Description

@sharyari

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions