Add elixir article
This commit is contained in:
parent
70820ecf01
commit
dab639e246
1 changed files with 301 additions and 0 deletions
301
content/post/04-production-iexing.md
Normal file
301
content/post/04-production-iexing.md
Normal file
|
@ -0,0 +1,301 @@
|
|||
---
|
||||
title: "How to debug a GenServer like Sherlock using IEx"
|
||||
subtitle: "A Sherlock Holmes approved guide 🕵"
|
||||
date: 2019-11-08
|
||||
draft: false
|
||||
tags: [dev, programming, elixir, production, debug]
|
||||
---
|
||||
|
||||
# Elixir, the path to functional programming ⚗️
|
||||
|
||||
With my [**Rust** rediscovery](https://blog.papey.fr/post/03-bites-the-rust/)
|
||||
a lot of _functional programming_ concepts became less obscure. In order to
|
||||
go deeper into this paradigm I chose _Elixir_ a **powerful** general purpose, _functional
|
||||
programming_ language, compiled and executed inside the _Erlang_ virtual
|
||||
machine (**BEAM**).
|
||||
|
||||
But why _Elixir_, if _Erlang_ exists ? Because :
|
||||
|
||||
Elixir, is Erlang with underpants - Athoune
|
||||
|
||||
Unlike _Erlang_, the _Elixir_ syntax is clear and elegant. The language comes
|
||||
with everything you need, included : interactive shell, deps tooling, building tooling
|
||||
and more…
|
||||
|
||||
Even if _José Valim_, the _Elixir_ creator was a core _Ruby_ dev, _Elixir_ is
|
||||
completely different since this is a _functional programming_ language. No
|
||||
mutation, no inheritance, no classes, only **pure** functions. Pray your only
|
||||
true god, the pipe operator `|>` operator, used to compose functions.
|
||||
|
||||
Now, I know, the only thing you want is code and examples, so let's take a
|
||||
quick look at some basic _Elixir_ stuff using the interactive shell **IEx**.
|
||||
|
||||
A simple addition :
|
||||
|
||||
{{< highlight exs >}}
|
||||
iex(1)> 2 + 2
|
||||
4
|
||||
{{< /highlight >}}
|
||||
|
||||
A simple addition, inside a list
|
||||
|
||||
{{< highlight exs >}}
|
||||
iex(1)> 2 + 2
|
||||
iex(5)> list = [1,2,3,4][1, 2, 3, 4]
|
||||
iex(6)> Enum.map(list, fn e -> e + 1 end)
|
||||
[2, 3, 4, 5]
|
||||
{{< /highlight >}}
|
||||
|
||||
List splitting (head and tail)
|
||||
|
||||
{{< highlight exs >}}
|
||||
iex(7)> [head | tail] = list
|
||||
[1, 2, 3, 4]
|
||||
iex(8)> head
|
||||
1
|
||||
iex(9)> tail
|
||||
[2, 3, 4]
|
||||
{{< /highlight >}}
|
||||
|
||||
Another important aspect of Elixir is _pattern matching_ used to match
|
||||
values, data structures and much more, let's try it :
|
||||
|
||||
{{< highlight exs >}}
|
||||
iex(11)> x = {:this, :is, :a, :test}
|
||||
{:this, :is, :a, :test}
|
||||
iex(12)> {a, b, c, d} = x
|
||||
{:this, :is, :a, :test}
|
||||
iex(13)> a
|
||||
:this
|
||||
iex(14)> b
|
||||
:is
|
||||
iex(15)> c
|
||||
:a
|
||||
iex(16)> d
|
||||
:test
|
||||
{{< /highlight >}}
|
||||
|
||||
Using _pattern matching_ you can assign values but also destructure data to
|
||||
simplify interaction with it.
|
||||
|
||||
Feels the hype growing ? Nice !
|
||||
|
||||
As usual, I like a real project to experiment on something. In the _Elixir_
|
||||
case, I started a Discord bot project called
|
||||
[o2m](https://github.com/papey/o2m). The main idea of this bot is to send
|
||||
alert messages on a specific channel when a new episode from a selected
|
||||
podcast is available. Today, I was struggling with a bug on a production
|
||||
instance of _o2m_. The last episode was not fetched correctly and the action
|
||||
that write a _"There is a new episode_" message was not triggered correctly.
|
||||
To debug and inspect the current state of the application I used IEx **in
|
||||
production**.
|
||||
|
||||
# Base ingredient of a good Elixir : Mix 🧙
|
||||
|
||||
Combined with IEx, there is _Mix_. Shipped with _Elixir_, this a build tool
|
||||
used for the following application related tasks :
|
||||
|
||||
- creating
|
||||
- setting up needed deps
|
||||
- testing
|
||||
- compiling
|
||||
|
||||
The combo killer here is to start IEx inside the _Mix_ project, in order to have all
|
||||
the dependencies imported inside the interactive shell
|
||||
|
||||
{{< highlight exs >}}
|
||||
iex -S mix
|
||||
{{< /highlight >}}
|
||||
|
||||
After that, there is a lot of useful commands like `recompile` to refresh and
|
||||
recompile new code **while your code is running**. Yes, this is something
|
||||
that _Elixir_ do by default, hot code reloading.
|
||||
|
||||
When your code is ready, you want to deploy it. With _Mix_ the standard way
|
||||
is to used the `release` command
|
||||
|
||||
{{< highlight sh >}}
|
||||
MIX_ENV=production mix release
|
||||
{{< /highlight >}}
|
||||
|
||||
This will create a precompile and packaged unit, with runtime included. With
|
||||
this, you do not have to install _Erlang_ or _Elixir_ on your production
|
||||
server. Boom.
|
||||
|
||||
If you look carefully, there is an interesting message and the end of the command output :
|
||||
|
||||
{{< highlight sh >}}
|
||||
Release created at \_build/prod/rel/o2m!
|
||||
|
||||
# To start your system
|
||||
_build/prod/rel/o2m/bin/o2m start
|
||||
|
||||
Once the release is running:
|
||||
|
||||
# To connect to it remotely
|
||||
_build/prod/rel/o2m/bin/o2m remote
|
||||
|
||||
# To stop it gracefully (you may also send SIGINT/SIGTERM)
|
||||
_build/prod/rel/o2m/bin/o2m stop
|
||||
|
||||
To list all commands:
|
||||
|
||||
_build/prod/rel/o2m/bin/o2m
|
||||
|
||||
{{< /highlight >}}
|
||||
|
||||
Particularly,
|
||||
|
||||
To connect to it remotely
|
||||
|
||||
This means I can have access to an interactive shell on production while my
|
||||
code is running, hooray 🎆
|
||||
|
||||
# Sherlocking a GenServer 🔎
|
||||
|
||||
Did you read the title ? We are talking about _GenServer_ here ! So _what the fuck_ is this ?
|
||||
|
||||
Taken from the [documentation](https://hexdocs.pm/elixir/GenServer.html) :
|
||||
|
||||
A GenServer is a process like any other Elixir process and it can be used
|
||||
to keep state, execute code asynchronously and so on. The advantage of using
|
||||
a generic server process (GenServer) implemented using this module is that it
|
||||
will have a standard set of interface functions and include functionality for
|
||||
tracing and error reporting. It will also fit into a supervision tree.
|
||||
|
||||
In _Elixir_, this is the default and common module used to implement client-server behaviors.
|
||||
|
||||
Here is the example from the documentation
|
||||
|
||||
{{< highlight elixir >}}
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
# Callbacks
|
||||
|
||||
@impl true
|
||||
def init(stack) do
|
||||
{:ok, stack}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:pop, \_from, [head | tail]) do
|
||||
{:reply, head, tail}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:push, element}, state) do
|
||||
{:noreply, [element | state]}
|
||||
end
|
||||
|
||||
end
|
||||
{{< / highlight >}}
|
||||
|
||||
Basically, this is a data structure with a state responding to triggers using
|
||||
handlers that update or retrieve the current state
|
||||
|
||||
To launch and interact with the server, in IEx :
|
||||
|
||||
{{< highlight exs >}}
|
||||
|
||||
iex(41)> {:ok, pid} = GenServer.start_link(Stack, [:hello])
|
||||
|
||||
iex(42)> GenServer.call(pid, :pop)
|
||||
:hello
|
||||
|
||||
iex(43)> GenServer.cast(pid, {:push, :world})
|
||||
:ok
|
||||
|
||||
iex(44)> GenServer.call(pid, :pop)
|
||||
:world
|
||||
{{< /highlight >}}
|
||||
|
||||
What if, I want to access the current state, of the GenServer ?
|
||||
|
||||
In _Erlang_, there is the `sys` module with the `get_state/2`
|
||||
[function](http://erlang.org/doc/man/sys.html#get_state-2) available
|
||||
|
||||
But, this is _Erlang_, and we use _Elixir_ ! We're doomed ? No ! Because
|
||||
everything available in _Erlang_ is available inside _Elixir_ using the `:`
|
||||
operator.
|
||||
|
||||
{{< highlight erl >}}
|
||||
iex(18)> :io.format("Hello World~n")
|
||||
Hello World
|
||||
:ok
|
||||
{{< /highlight >}}
|
||||
|
||||
So, with our `sys` example :
|
||||
|
||||
{{< highlight exs >}}
|
||||
iex(19)> :sys.get_state(pid)
|
||||
[:!, :world]
|
||||
{{< /highlight >}}
|
||||
|
||||
Nice ! Now we can get state of a GenServer using the PID !
|
||||
|
||||
Now, let's move from local machine to prod. In this case, processus are
|
||||
handled by a **Supervisor** but first things first, we want a IEx shell
|
||||
inside our running app. In order to illustrate this last part, I will be
|
||||
using one of my prod instances of _o2m_
|
||||
|
||||
{{< highlight sh >}}
|
||||
o2m@16557a733b59:/opt/o2m\$ ./prod/rel/o2m/bin/o2m remote
|
||||
Erlang/OTP 22 [erts-10.5.3][source] [64-bit][smp:2:2] [ds:2:2:10][async-threads:1] [hipe]
|
||||
|
||||
Interactive Elixir (1.9.2) - press Ctrl+C to exit (type h() ENTER for help)
|
||||
iex(o2m@16557a733b59)1>
|
||||
{{< /highlight >}}
|
||||
|
||||
Ok but, what is the _pid_ of the _GenServer_ I want to inspect ? I don't know !
|
||||
Here the first solution could be outputting _pid_ to _stdout_ but, imagine that
|
||||
our application kills and restart GenServer, on demand or our application is
|
||||
flooding _stdout_ because of some errors, this is not a viable solution.
|
||||
|
||||
First, we could list all processus,
|
||||
|
||||
{{< highlight exs >}}
|
||||
iex(o2m@16557a733b59)3> Supervisor.which_children(O2M.Supervisor)
|
||||
[
|
||||
{O2M, #PID<0.2368.0>, :worker, [O2M]},
|
||||
{"jobs-https://feed.ausha.co/bj5li17QPONy", #PID<0.2365.0>, :worker, [Jobs]},
|
||||
{"jobs-https://feed.ausha.co/yJeEUGlLVq0o", #PID<0.2362.0>, :worker, [Jobs]},
|
||||
{"jobs-https://anchor.fm/s/b3b7468/podcast/rss", #PID<0.2358.0>, :worker,
|
||||
[Jobs]}
|
||||
]
|
||||
{{< /highlight >}}
|
||||
|
||||
Where `O2M.Supervisor` is the dedicated _Supervisor_ of my application
|
||||
|
||||
Now I can identify the GenServer I want to inspect, let's take the
|
||||
`jobs-https://anchor.fm/s/b3b7468/podcast/rss` one, with associated pid
|
||||
`0.2358.0` (the last element of the list, here), here comes the fun
|
||||
|
||||
{{< highlight exs >}}
|
||||
iex(o2m@16557a733b59)13> {_, pid, _, \_} = Supervisor.which_children(O2M.Supervisor) |> List.last
|
||||
{"jobs-https://anchor.fm/s/b3b7468/podcast/rss", #PID<0.2358.0>, :worker,
|
||||
[Jobs]}
|
||||
iex(o2m@16557a733b59)14> pid
|
||||
#PID<0.2358.0>
|
||||
{{< /highlight >}}
|
||||
|
||||
Then, I can use the pid value to get _GenServer_ state and inspect it to see
|
||||
if everything is ok :
|
||||
|
||||
{{< highlight exs >}}
|
||||
iex(o2m@16557a733b59)15> :sys.get_state(pid)
|
||||
{"https://anchor.fm/s/b3b7468/podcast/rss",
|
||||
%{
|
||||
date: "Mon, 04 Nov 2019 09:00:00 GMT",
|
||||
show: "Harry Cover, le podcast des meilleures reprises",
|
||||
title: "Yes we can work it out !",
|
||||
url: "https://anchor.fm/leotot8/episodes/Yes-we-can-work-it-out-e8n4d3"
|
||||
}}
|
||||
{{< /highlight >}}
|
||||
|
||||
How ! Impressive ! It was a little bit Sherlock Holmes oriented debugging but
|
||||
that was fun. Keep in mind that this is a really simple operation here, from
|
||||
IEx everything is possible from starting new GenServers to modify state of a
|
||||
specific one and much more.
|
||||
|
||||
I said it at the beginning, **Elixir is powerful**.
|
Loading…
Reference in a new issue