Keyboard shortcuts

/ or ⌘/Ctrl K
Find a note
j / k
Next / previous section or linked note
h / l
Collapse or go to parent / expand or enter
e or Alt-click
Read a linked note here
o
Open focused note on its own
g g / G
First / last section or linked note
g h / g a
Home / all notes
g b / g t
Backlinks / table of contents
t
Cycle System, Light, Dark
? / Esc
Show / close this reference

Search: ↑/↓ or Ctrl N/P, Enter to open. Shortcuts pause while typing.

erlang hot reloading post [2b66ab05]

drafts, erlang

Hot reloading in the BEAM VM

  • Counter v1
  -module(counter).
  -behaviour(gen_server).
  -vsn("1.0.0").
  -export([start_link/0, get_count/0, increment/0]).
  -export([init/1, handle_call/3, handle_cast/2, code_change/3, terminate/2, handle_info/2]).

  %% -- API --
  start_link() ->
      gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

  get_count() ->
      gen_server:call(?MODULE, get_count).

  increment() ->
      gen_server:cast(?MODULE, increment).

  %% -- Callbacks --
  init([]) ->
      {ok, 0}.

  handle_cast(increment, Count) ->
      {noreply, Count + 1}.

  handle_call(get_count, _From, Count) ->
      {reply, Count, Count}.

  code_change(_OldVsn, State, _Extra) ->
      {ok, State}.

  handle_info(_Info, State) -> {noreply, State}.
  terminate(_Reason, _State) -> ok.
  • Counter v2

-module(counter). -behaviour(gen_server). -vsn("2.0.0"). -export([start_link/0, get_count/0, increment/0, get_info/0]). -export([init/1, handle_call/3, handle_cast/2, code_change/3, terminate/2, handle_info/2]).

%% State map -record(state, { count = 0, increment_count = 0, last_updated }).

%% – API – start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

get_count() -> gen_server:call(?MODULE, get_count).

increment() -> gen_server:cast(?MODULE, increment).

get_info() -> sys:get_state(?MODULE).

%% – Callbacks –

init([]) -> {ok, #state{}}.

handle_cast(increment, State) -> {noreply, State#state{count = State#state.count + 1, increment_count = 1, last_updated = erlang:timestamp() }}.

handle_call(get_count, _From, State) -> {reply, State#state.count, State}.

code_change("1.0.0", OldState, _Extra) when is_integer(OldState) -> NewState = #state{ count = OldState, increment_count = OldState, last_updated = erlang:timestamp()}, {ok, NewState};

code_change(_OldVsn, State, _Extra) -> {ok, State}.

handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok.

  • Running
  peixian@Mac ~/c/erl-hot-reloading> erl
  Erlang/OTP 27 [erts-15.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

  Eshell V15.2 (press Ctrl+G to abort, type help(). for help)
  1> file:copy("counter_v1.erl", "counter.erl"), c(counter).
  {ok,counter}
  2> counter:start_link().
  {ok,<0.95.0>}
  3> counter:increment().
  ok
  4> counter:increment().
  ok
  5> counter:get_count().
  2
  6> file:copy("counter_v2.erl", "counter.erl"), c(counter).
  {ok,counter}
  7> counter:get_info().
  2
  8> sys:change_code(counter, counter, "1.0.0", []).
  {error,{unknown_system_msg,{change_code,counter,"1.0.0",
                                          []}}}
  9> sys:suspend(counter).
  ok
  10> sys:change_code(counter, counter, "1.0.0", []).
  ok
  11> sys:resume(counter).
  ok
  12> counter:get_info().
  {state,2,2,{1769,908677,996152}}
  13>
  • Basically

    1. Code server keeps two versions
    2. Local calls within an old version of the genserver route to the old version (via VSN)
    3. New calls route in the newer version
    4. Need to be very careful about suspending a process
    5. But you can also track processes, and loop over them to reload one by one
    6. code_change itself takes callbacks, so you can write arbitrary migration code
    7. You can do this without even removing dependencies, such as TCP socket open
    8. TCP socket demo