1

I write a makefile for my erlang project and it works. But while project is running, if i change a file and make it by makefile, the new version of code not works and i must exit shell and start it again to run the new version. How can solve it?

makefile:

# Makefile

SRC_DIR = src
BIN_DIR = ebin
DB_DIR = db


ERL = erl
ERLC = erlc
ERLC_FLAGS=

SOURCES=$(wildcard ${SRC_DIR}/*.erl)
HEADERS=$(wildcard ${SRC_DIR}/*.hrl)
OBJECTS=$(SOURCES:${SRC_DIR}/%.erl=${BIN_DIR}/%.beam)

all: $(OBJECTS)

ebin/%.beam : src/%.erl $(HEADERS) Makefile
    ${ERLC} $(ERLC_FLAGS) -o ${BIN_DIR}/ $<

drop_db:
    rm -r ${DB_DIR}

clean:
    -rm $(OBJECTS)

run:
    ${ERL} -pa ${BIN_DIR} -s god run
Amin
  • 755
  • 6
  • 21
  • Running `make run` doesn't run the new code or your previously running shell doesn't magically see the new compiled code? Are you loading the new code? Do you have the code in place to switch out old running code in your erlang code? – Etan Reisner Sep 24 '15 at 15:46
  • How can i load new module after i compiled it? – Amin Sep 24 '15 at 15:50
  • I haven't used erlang in too long to be able to say that for sure but I think just compiling it again from the shell is enough. Having compiled it externally you may just need to tell the shell to load it (with `m()` or something like that). But I really don't know. I would imagine most good erlang guides would cover this though. – Etan Reisner Sep 24 '15 at 15:52
  • possible duplicate of [Hot code replacement in erlang](http://stackoverflow.com/questions/11968809/hot-code-replacement-in-erlang) – Lol4t0 Sep 24 '15 at 19:32

1 Answers1

3

Erlang allows to modify the application code in run easily, but it doesn't mean that it is transparent to the user.

Hopefully it isn't transparent, otherwise the feature couldn't be used, or only restricted to trivial use cases. Here are the first 3 reasons that come to my mind to justify that code change must be under the user responsibility and control:

  • from one version to another one, the data may need to be adapted (modified record, modified structure, new data ...). So on transition, the new version of code must update the state data; it even must verify that it is able to do it (does it have the transformation code to go from version X to version Y). The OTP behaviors provides special callback for this.

  • An application modification may involve changes in multiple modules, and a module may be run by many processes (a web client for example). But the code is updated at the process level at very specific time (fully qualified call) and it cannot occur for all processes simultaneously. So you must control the order of module upgrade. The function code:load_file(Module) allows this.

  • Only (:o) 2 versions of a module may live concurrently in one node. If you run a module and then make 2 modifications loading the module twice, the oldest code "disappears" and any process still using this version simply dies. You need to synchronize the upgrade. The function code:soft_purge(Module) helps you for this.

After all this warnings come back to the fun. You can easily play with code upgrade to get more familiar with it. Only 2 steps and one condition are required:

  • the new beam file (compiled code) must be in the code path of your Erlang VM (or you will have to use code:load_abs(Filename) in the next step).

  • You must load the new code in the VM. If you compile the code from the shell using c(my_module). it is done automatically. If you compile it by other means, you will have to explicitly load the new code in the VM using for example code:load_file(my_module).

  • Then you must ensure that each process using this module performs a fully qualified call to an exported function and thats it, for example: my_module:code_change(State). if you use OTP behavior, this callback exists and provide you also the old version of the code in parameter.

Lets use the following module:

-module(one).

-compile([export_all]).

-define (VERSION,1).

start() ->
  rserver,spawn(?MODULE,init,[]).

init() ->
  loop(0).

loop(N) ->
  receive
    print ->
      io:format("received ~p message(s) so far~n",[N+1]),
      loop(N+1);
    load ->
      io:format("received ~p message(s) so far~n reload the code~n",[N+1]),
      ?MODULE:loop(N+1);
    version ->
      io:format("received ~p message(s) so far~n version is ~p~n",[N+1,?VERSION]),
      loop(N+1);
    M ->
      io:format("received unexpected message ~p: ignored~n",[M]),
      loop(N)
  end.

I compile it in the shell, start 2 processes and play with them:

1> c(one).
{ok,one}
2> P1 = one:start().
<0.40.0>
3> P2 = one:start().
<0.42.0>
4> P1 ! print.
received 1 message(s) so far
print
5> P1 ! print.
received 2 message(s) so far
print
6> P1 ! version.
received 3 message(s) so far
 version is 1
version
7> P1 ! reset.  
received unexpected message reset: ignored
reset
8> P2 ! print.  
received 1 message(s) so far
print
9>

Now I modify the code to:

-module(one).

-compile([export_all]).

-define (VERSION,2).

start() ->
  rserver,spawn(?MODULE,init,[]).

init() ->
  loop(0).

loop(N) ->
  receive
    print ->
      io:format("received ~p message(s) so far~n",[N+1]),
      loop(N+1);
    load ->
      io:format("received ~p message(s) so far~n reload the code~n",[N+1]),
      ?MODULE:loop(N+1);
    version ->
      io:format("received ~p message(s) so far~n version is ~p~n",[N+1,?VERSION]),
      loop(N+1);
    reset ->
      io:format("received ~p message(s) so far, reset message count~n",[N+1]),
      loop(0);
    M ->
      io:format("received unexpected message ~p: ignored~n",[M]),
      loop(N)
  end.

Compile it outside the VM and test:

9> P1 ! version.
received 4 message(s) so far
 version is 1
version
10> P1 ! load.       
received 5 message(s) so far
 reload the code
load
11> P1 ! version.
received 6 message(s) so far
 version is 1
version
12> P1 ! reset.  
received unexpected message reset: ignored
reset
13> P2 ! print.  
received 2 message(s) so far
print
14> 

As I didn't load the code, there is no update: the VM will not loose time to search in the code path if a new version exists at each external call!

14> code:load_file(one).
{module,one}
15> P1 ! version.       
received 7 message(s) so far
 version is 1
version
16> P2 ! version.       
received 3 message(s) so far
 version is 1
version
17> P1 ! load.          
received 8 message(s) so far
 reload the code
load
18> P1 ! version.
received 9 message(s) so far
 version is 2
version
19> P1 ! reset.         
received 10 message(s) so far, reset message count
reset
20> P1 ! print.         
received 1 message(s) so far
print
21> P2 ! version.       
received 4 message(s) so far
 version is 1
version
22> P2 ! print.         
received 5 message(s) so far
print
23> 

after loading the new code, I have been able to upgrade the version of P1, while P2 is still on version 1.

Now I make a new modification, simply going to version 3 and compile in in the shell to force the code loading:

23> c(one).
{ok,one}
24> P1 ! version.
received 2 message(s) so far
 version is 2
version
25> P1 ! load.   
received 3 message(s) so far
 reload the code
load
26> P1 ! version.
received 4 message(s) so far
 version is 3
version
27> P2 ! print.  
print
28> 

I have been able tu upgrade the process P1 from version 2 to 3. But the process P2 which was still using the version 1 is died.

Pascal
  • 13,977
  • 2
  • 24
  • 32