[erlang-bugs] funs and code loading don't always behave as expected

Matthias Lang matthias@REDACTED
Wed Jun 13 23:43:29 CEST 2007


Hi,

As far as I know, a process is only supposed to switch from running
old code to running new code when a call of the style

   module:function/n

is made. 

I think there is a hole in the way funs are loaded which allows new
code to be executed when it should not be. Demonstration module:

   -module(load). 
   -export([f/0]). 
   f() -> fun() -> g() end. 
   g() -> io:fwrite("first version~n").

If you compile it and bind the result of f(), i.e.

   1> c(load).
   {ok,load}
   2> F = load:f().
   #Fun<load.0.68346246>
   3> F().
   first version
   ok

and then edit the definition of g/0, WITHOUT TOUCHING f/0, you
unexpectedly get the new code:

   % g() -> io:fwrite("second version~n").

   4> c(load).     
   {ok,load}
   5> F().
   second version     %% unexpected, should have been "first version"

yet if you edit both g/0 and f/0, you get the old code:

   % f() -> fun() -> io:fwrite("side effect\n"), g() end.
   % g() -> io:fwrite("third version~n").
   6> c(load).
   {ok,load}
   7> F().         
   second version     %% as expected

I have attached an automatic example of this happening.

A guess, based on a quick look at beam_load.c and erl_fun.c: the root
cause is that a fun with the same hash as an existing fun will always
overwrite the existing fun on code load. I.e. the "is the hash the
same" test is too weak. But that is just a guess.

Matthias

----------------------------------------------------------------------
%% Demonstrate unexpected behaviour in hot code loading of a module 
%% containing a fun().
%%
%% The expected output from uce:go() is
%%
%%    first version
%%    first version
%%
%% The actual output is
%%
%%    first version
%%    second version
%%
%% i.e. the switch to the newly loaded code is unexpected.
%%
-module(uce).
-export([go/0]).

common() -> "-module(load). -export([f/0]). f() -> fun() -> g() end. ".
first() -> common() ++ " g() -> io:fwrite(\"first version~n\").".
second() -> common() ++ "g() -> io:fwrite(\"second version~n\").".

filename() -> "/tmp/load.erl".

load_code(String) ->
    file:write_file(filename(), String),
    {ok, _, Binary} = compile:file(filename(), [binary]),
    {module, _} = code:load_binary(load, filename(), Binary).

go() ->
    load_code(first()),
    F = load:f(),
    F(),
    load_code(second()),
    F().



More information about the erlang-bugs mailing list