Skip to content

Latest commit

 

History

History
100 lines (88 loc) · 3.49 KB

File metadata and controls

100 lines (88 loc) · 3.49 KB

Exercise: OTP-Applications-3

Our boss notices that after we applied our version-0-to-version-1 code change, the delta indeed works as specified. However, she also notices that if the server crashes, the delta is forgotten -- only the current number is retained. Write a new update that stashes both values.

Solution

See the sequence directory for the full project.

So, to accomplish this, we're not actually changing the sequence server's state struct at all. There are really only three necessary code changes. We want to store the delta when we terminate:

def terminate(_reason, state) do
  Sequence.Stash.save_value(state.stash_pid, {state.current_number, state.delta})
end

And we want to load the delta when we start:

def init(stash_pid) do
  {current_number, delta} = Sequence.Stash.get_value(stash_pid)
  {:ok, %State{current_number: current_number, stash_pid: stash_pid, delta: delta}}
end

Lastly, for future runs, we will need to initialize the Stash with a delta of 1:

def start(_type, _args) do
  {:ok, _pid} = Sequence.Supervisor.start_link({123, 1})
end

I'm not sure if this is necessary, but the code_change function just needs to set the new state using the values from the old state:

def code_change("1", old_state = %{current_number: current_number, stash_pid: stash_pid, delta: delta}, _extra) do
  new_state = %State{current_number: current_number, stash_pid: stash_pid, delta: delta}
  Sequence.Stash.save_value(stash_pid, {current_number, delta})
  Logger.info "Changing code from 1 to 2"
  Logger.info inspect(old_state)
  Logger.info inspect(new_state)
  {:ok, new_state}
end

I wanted to be explicit, but we could probably just write it like:

def code_change("1", old_state, _extra) do
  new_state = old_state
  Sequence.Stash.save_value(stash_pid, {new_state.current_number, new_state.delta})
  Logger.info "Changing code from 1 to 2"
  Logger.info inspect(old_state)
  Logger.info inspect(new_state)
  {:ok, new_state}
end

Everything's looking good; let's see if after we apply the code change it maintains the delta across crashes:

iex> Sequence.Server.next_number
123
iex> Sequence.Server.next_number
124
iex> Sequence.Server.increment_number(5)
:ok
iex> Sequence.Server.next_number        
130
iex> :sys.suspend(Sequence.Server)
:ok
iex> c("new_server.ex")
new_server.ex:1: warning: redefining module Sequence.Server
new_server.ex:5: warning: redefining module Sequence.Server.State
[Sequence.Server, Sequence.Server.State]
iex> :sys.change_code(Sequence.Server, Sequence.Server, "1", [])

11:00:34.235 [info]  Changing code from 1 to 2

11:00:34.238 [info]  %Sequence.Server.State{current_number: 135, delta: 5, stash_pid: #PID<0.136.0>}

11:00:34.238 [info]  %Sequence.Server.State{current_number: 135, delta: 5, stash_pid: #PID<0.136.0>}
:ok
iex> :sys.resume(Sequence.Server)
:ok
iex> Sequence.Server.next_number
135
iex> Sequence.Server.increment_number("abc")
:ok
iex>
11:01:14.052 [error] GenServer Sequence.Server terminating
** (ArithmeticError) bad argument in arithmetic expression
    (sequence) new_server.ex:43: Sequence.Server.handle_cast/2
    (stdlib) gen_server.erl:601: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:667: :gen_server.handle_msg/5
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", {:increment_number, "abc"}}
State: %Sequence.Server.State{current_number: 140, delta: 5, stash_pid: #PID<0.136.0>}

nil
iex> Sequence.Server.next_number
140
iex> Sequence.Server.next_number
145

Radical.