This article will guide you to perform hot code swapping using GenServer
& :sys.change_code
function.
The world is moving at a high speed. Every one is on race knowingly or unknowingly. If you stop or wait then you are letting yourself down. OK! I’ll stop being more dramatic here. It is just to make you understand how important the time is in the modern world especially in software development.
Hot code swapping is replacing or adding components without stopping or shutting down the system. It is frequently called as hot plugging.
In software development, hot swapping is used to upgrade or update the system without interrupting the current running system. It helps us with less downtime at the time of upgrading.
OK! Stories are always good to hear. Let’s dive into code.
Step 1 — Creating a GenServer
The hot code swapping can be achieved using the behavior module GenServer
. Save the following lines of code into a file called demo_server.ex
.
defmodule DemoServer do
use GenServer
@vsn "1"
## Client API
def start_link employee do
GenServer.start_link __MODULE__, employee, []
end
def add_money(pid, value) do
GenServer.call(pid, {:add, value})
end
## Server API
def init(employee) do # initiating state with passed employee details
{:ok, employee}
end
# add the value to the state and returns :ok
def handle_call({:add, value},_from, %{name: name, money: money} = state) do
{:reply, "#{value} added to #{name} ", Map.put(state, :money, money+value)}
end
end
This DemoServer
does nothing interesting as it is intentionally limited to receive only one kind of messages to handle. It can only add the money to the employee account. I know it is silly. But, it gives you the clear idea of what we are for.
warning
I did not register this server with a name. So, any transaction should be performed using pid the process identifier of the server not by the DemoServer module name.
If you really need to register, just update the module line from GenServer.start_link __MODULE__, employee, []
to GenServer.start_link __MODULE__, employee, [name: __MODULE__]
. It serves you the purpose.
I just want you to know the idea behind how this GenServer
can perform hot code swapping here. That’s why I took simple real world example.
Code Analysis
-
@vsn "1"
will let us to identify the code versions. You need to update this value in your next version. We will see that in later in this article itself. -
start_link/1
andadd_money/2
are provided to the client to initiate the server and adding money to that specific employee here. -
%{name: name, money: money}
this is the default state of ourDemoServer
-
“#{value} added to #{name}”
Every time you add money, you will get this sentence as a response. ex"30000 added to blackode"
. -
Map.put(state, :money, money+value)
at the end, we are updating the state by adding thevalue
tomoney
.
Before going to update our GenServer
, it is customary to check the current version is working fine. Let’s check that.
Load and Ensure DemoServer
$iex demo_server.ex
iex> DemoServer
DemoServer
Ensure it is Working
iex> employee = %{name: "blackode", money: 30000}
%{money: 30000, name: "blackode"}
iex> {:ok, pid} = DemoServer.start_link employee
{:ok, #PID<0.110.0>}
iex> DemoServer.add_money pid, 30000
"30000 added to blackode "
iex> :sys.get_state pid
%{money: 60000, name: "blackode"}
Step2 — Creating Version 2 for DemoServer
Here comes our actual implementation of the idea that we have been looking for “Hot Code Swapping”.
What are the things we are updating in the version 2 ?
Updating key `money` to `salary`.
After adding `money`, instead of responding with `String.t()` , we return the new state `Map` as a `reply` .
Though the changes are atomic here, it well serve our idea. I don’t want to confuse you with the complex code logic. You can make as many changes as you like.
Let’s code it.
Create a new file demo_server_2.ex
$ touch demo_server_2.ex
Code update @vsn “2”
Make sure you updated the @vsn
value here. You can observe the changes in the following lines of code
Code Analysis
In the previous version lines of code, the state
was pattern matched to {name: name: money: money}
. But here, we updated the pattern matching with {name: name, salary: salary}
; the key money
transformed to salary
.
The {:add, value}
message handler now reply with new_state
which is a map instead of a string like before "500 added to blackode"
If we directly load this module, we will get an exception of no function clause to handle error. Because, though we updated the code, the old state still contains only {name: name, money: money}
.
So, we need to update the state
here. GenServer provides a server callback function called code_change
which helps us here. It receives old_version
, old_state
and some useful information.
The code_change
callback is sole responsible for updating the state here.
In the above definition, we are pattern matching to old_state
and returning a tuple with {:ok, new_state}
.
Step 3 — Hot Code Swapping
This again involves three more steps.
Step 3.1 — Suspending Current Running Process
Now, with out closing the iex
session run the following line of code in iex
.
iex> :sys.suspend pid
:ok
# pid is the process identifier for DemoServer
Why we need to suspend the process? When you suspend a process, it will no more receive messages to handle. As we are going to update it’s state, we should not allow others to make requests for a while.
Step 3.2 — Code Changing from Version 1 to Version 2
Before changing the code, you need to load the new_version code here. Our new version code lies inside the file demo_server_v2.ex
.
iex> c "demo_server_v2.ex"
warning: redefining module DemoServer (current version defined in memory)
demo_server_v2.ex:2
[DemoServer]
It gives you a warning about re-defining the module.
After loading the new version code, you need to update the state using :sys.change_code
.
You need to pass following information to :sys.change_code
function.
- pid — Process Identifier for
DemoServer
. - Module Name — The Name of the module where our callback lies. Here, it is
DemoServer
. - Old_version — The previous version, here ti is “1”.
- nil — Other extra information. We don’t care here now. So passing nil here.
iex> :sys.change_code pid, DemoServer, "1", nil
:ok
The :sys.change_code
function triggers the callback function code_change
which updates the state of the DemoServer
Don’t get confuse here with :sys.change_code
and a callback code_change
inside the DemoServer
module.
Step 3.3 — Resuming the Process and Testing
iex> :sys.resume pid
:ok
So far our employee has salary of 60000
. Now we add another 100_000
. So, with out any error it should give a map
instead of string.
iex> DemoServer.add_money(pid, 100000)
%{name: "blackode", salary: 160000}
Boom!
The code was hot swapped with new state and new style of response. You have hot plugged the updates to your existing running system without shutting it down.
Hope you liked it. Feel free exchange your ideas here…
How to perform Hot Code Swapping using Distillery — #2 — A (Live Demo) GenServer State update
The second part of Hot Code Swapping using Distillery, explains all about releases and hot code plugging to your remote system. Check the following link.
Check out the GitHub repository on Killer Elixir Tips
Glad if you can contribute with a ★