How to perform Hot Code Swapping using Distillery— #2 — A (Live Demo) GenServer State update.

Focus on code & make releases easily.

Photo by rawpixel on Unsplash

If you are unaware of basics on Hot Code Swapping, read the part 1 below to get some idea before you take a long step on code swapping or hot plugging.

How to perform Hot Code Swapping in Elixir — #1.

This article will guide you to perform hot code swapping in Elixir using the Elixir package distillery-2.0 (at the moment of writing this article). A live demo will stand out.

Brief Intro about Distillery

The distillery helps us to simplify deployments in Elixir with OTP releases.

Who wants to fall into hassle of dependencies these days. We look for things to run independently with out depending on any required environment. It feels me to write a line which I read in many books “write once, and run anywhere”
In a simple way, we need a complete package which is ready to deploy anywhere & independent of running environment.

This is exactly what distillery is used for.

Let’s code it and taste the fruit.

Create a new mix project

$ mix new hotcode
$ cd hotcode

Add distillery as a project dependency inside the file mix.exs under dependencies section.

#file: mix.exs
defp deps do
[{:distillery, "~> 2.0"}]
end

Fetch the dependency

$ mix deps.get

Let’s add a file demo_server.ex in lib folder and copy the following lines of code to the file.

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

If you need explanation of the code, check the part-1.

It’s a GenServer module which puts the initial state to the passed employee details %{name: "some_good_name", money: some_big_number} e.g. %{name: "blackode", money: 999999} .

Don’t forget to save the file demo_server.ex after copying the code. Don’t feel shame to copy, it saves your time.

Releasing first version

Just run the command mix release from the project root directory.

$ mix release

To your surprise, you’ll hit with an error saying release config file is missing and asks you to run the mix task release.init

$ mix release.init

It creates an example config file rel/config.exs review the file, make changes as you desired, and then run the command mix release to build your first release.

The file itself is enough to use. You can edit the cookie in environment section.

environment :prod do
set cookie: :crypto.hash(:sha256, System.get_env("COOKIE")) |> Base.encod16(:lower) |> String.to_atom
........
end

You can also see the release section in the file.

release :hotcode do
set version: current_version(:hotcode)
set applications: [
:runtime_tools
]
end

You can define one or more releases in the rel/config.exs file.

If the default_release option is not specified and running mix release , the first release in the file will be used by default or else the specified release is used.

Let’s add another release in the file rel/config.exs and check the releases.

release :hotcode2 do
set version: "2"
set applications: [
:runtime_tools
]
end

After adding the release, you need change the value of default_release. By default its value is :default . Now, change this to :hotcode2 and run mix release

$ mix release

It uses default environment for building the release. You can find a line in console like Building release hotcode2:2 using environment dev . However, you can still mention the environment to use by setting environment on the fly MIX_ENV=prod mix release .

The build is placed inside a directory _build . At the moment this folder contains only dev directory.

Now, we change the default_release option to :default and will make another release using MIX_ENV=prod . This time it uses the release :hotcode as it is the first release available in the file rel/config.exs .

Let’s check that.

default_release: :default

After changing the release now run the release.

$ MIX_ENV=prod mix release

You can see a line called Building release hotcode:0.1.0 using environment prod in your terminal. At this moment there will be two folders inside your _build directory based on the environment we used for building.

The package is released in the directory _build/prod/rel/hotcode/bin/hotcode . The bin folder is the place for the scripts related to Erlang entry. The releases folder contains all the version releases. Each version folder will have its own rel file, boot scripts, and tarball file which we use for deploying in remote systems.

Starting Releases

Now change your directory to _build/prod/rel/hotcode/bin and run the command ./hotcode console .

$ cd _build/prod/rel/hotcode/bin
$ ./hotcode console

It opens shell environment similar to iex -S mix

Let’s do some computations over the DemoServer.

iex(hotcode@127.0.0.1)1> {:ok, pid} = DemoServer.start_link %{name: "blackode", money: 3_00_000}
{:ok, #PID<0.773.0>}
iex(hotcode@127.0.0.1)3> DemoServer.add_money pid, 400000
"400000 added to blackode "
iex(hotcode@127.0.0.1)4> :sys.get_state pid              
%{money: 700000, name: "blackode"}

This is one way of starting elixir. 
You can also start somewhere as daemon and attach to it. Before attaching, you need to start first.

$ ./hotcode start
$ ./hotcode attach
Attaching to /home/john/mycode/elixir/hotcode/_build/prod/rel/hotcode/var/erl_pipes/hotcode@127.0.0.1/erlang.pipe.1 (^D to exit)
iex(hotcode@127.0.0.1)1> {:ok, pid} = DemoServer.start_link %{name: "blackode", money: 34000}
{:ok, #PID<0.773.0>}

Code Deployment

As I mentioned earlier, when you run mix release, it produces a tarball, which contains our application and required stuff to run the application inside the releases directory. _build/prod/rel/hotcode/releases or _build/dev/rel/hotcode/releases based on the environment used for releases.

This directory contains the folders with version names. For example, if we are releasing 0.1.0, then you will find a folder with same name 0.1.0 .

Inside every, version folder, you will find a tarball with the name of the release. In our case it is hotcode . So, our tarball name would be hotcode.tar.gz.

Let’s deploy the release in remote system.

Copying tarball to remote system

Copy the tarball to remote system using scp. The scp is secure copy (remote file copy program).

$ cd  _build/prod/rel/hotcode/releases
$ cd 0.1.0
$ scp hotcode.tar.gz user@host:/home/user/.

Here, replace user with your username and host with your hostname. This copies the tarball file to your remote system inside your home directory if you hold the privileges to your remote system.

Extracting the tarball in your remote system

Login to your remote system using ssh.

ssh user@host

Enter password if it asks you so.

After login, extract the hotcode.tar.gz file using tar command. As we copied the tarball to our home directory, you can simply run the following commands.

use@host$ mkdir hotcode
use@host$ tar -xzcf ./hotcode.tar.gz --directory hotcode

Start at the remote system

use@host$ cd hotcode/bin
use@host$ ./hotcode start

This will get you the Erlang Virtual Machine and the application on the remote system.

Now, make some changes to the DemoServer.ex file and update the version value in mix.exs file to "0.2.0".

Changes I made to demo_server.ex file are following

I updated the state and added code_change function to match and update the current state to new state of GenServer. Otherwise, we end up with a miss match on state pattern matching.

You can read part1, where I explained about the changes. Just by looking at the code we can understand them easily.

Next, after making some application changes and bumping the project version, we can generate an upgrade release using the following command:

$ MIX_ENV=prod mix release --upgrade

This again generates a regular release. As we updated the project version from 0.1.0 to 0.2.0, a new folder with name 0.2.0 is created in the releases directory.

Change the directory to new folder cd 0.2.0/ and you will see a new file called relup for upgrade. Just read yourself about this file.

Copying the upgraded tarball

You need to make a directory releases/0.2.0 in the previous extracted hotcode folder and secure copy the tarball file to this directory. You don’t need to extract this time.

Upgrading the release 0.2.0

After it’s copied, upgrading the release can be done by stop and start again will upgrade to latest release

use@host$ cd hotcode/bin
use@host$ ./hotcode stop
use@host$ ./hotcode start

We can also upgrade by running the upgrade command like below.

user@host$ ./hotcode upgrade "0.2.0"

The desired version need to be specified to upgrade. Here, 0.2.0 is our upgraded version.

Let’s check our new state. We changed the key money to salary

iex(user@host)1> {:ok, pid} = DemoServer.start_link %{name: "blackode", salary: 50000}
{:ok, #PID<0.773.0>}
iex(hotcode@127.0.0.1)6> DemoServer.add_money pid, 50000
%{name: "blackode", salary: 100000}
# In the previous it returns a string, now it gives you the map as the state got updated.
iex(hotcode@127.0.0.1)7> :sys.get_state pid             
%{name: "blackode", salary: 100000}

Boom!! We have Upgraded the code successfully.

Conclusion

Managing releases is really a tough job and without restarting is really a night mare. It is just like opening the door for the run-time bugs unless you are cautious about what you are doing.

Hope you liked it. Feel free to exchange your ideas here…

Thanks for reading.

Join Our Telegram Channel and support us.

https://t.me/blackoders
if worth_clapping, do: “clap”, else: nil

Happy Coding :)