GenServer
is essential part of OTP, which simplifies repeating tasks, letting programmer concentrate on logic of the application, and not on handling edge cases and repeated error handling.
The idea behind GenServer
is simple - you start separate process, that holds some state, then on each incoming message(be that call
or cast
) it may change it internal state and also generate some response(in case of call
)
In this manual calling process
is named Alice
and newly process is Bob
.
Programming without GenServer, as you would done it manually
1 | defmodule SimpleGenServerMock do |
Code initial_state = 1
is exactly same code we write in init
callback. Internal state of the server is simply an integer. Usually it is a map, tuple or list with settings and state.
{state, state}
means that we do not want to update the state and want to return state as result. This is the code which goes in handle_call
callback in Bob
.
And code new_state = state + 1
is the code which goes into handle_cast
callback, because we do not need to respond with result, we just change server Bob
internal state.
Working with module will look like:
1 | pid = SimpleGenServerMock.start_link() |
Same Server With GenServer Behaviour
Now if we want to re-write same code using GenServer
it will look like this:
1 | defmodule SimpleGenServerBehaviour do |
While in this example it did not saved a lot of lines for more complicated code having GenServer
deal with all complexity saves a lot of tying. Also you got timeout, named processes and stable, production proven error hanlding for free.
Using GenServer behaviour is very similar to code we wrote before:
1 | {:ok, pid} = GenServer.start_link(SimpleGenServerBehaviour, []) |
Better to implement start in the module
1 | defmodule Stack do |
Receiving Regular Messages
The goal of GenServer
is to abstract the “receive” loop for developers, automatically handling system messages, support code changes, synchronous calls and more. Therefore, you should never call your own “recieve” inside the GenServer callbacks as doing will cause the GenServer misbehave.
Besides the synchronous and asynchronous communication provided by call/3
and cast/2
, regular messages sent by functions such as Kernal/send2
, Process.send_after/4
and similar, can be handled inside the handle_info/2
callback.
handle_info/2
can be used in many sinutations, such as handling monitor DOWN messages sent by Process.monitor/1
. Another use case for handle_info/2
is to perform periodic work, with the help of Process.send_after/4
:
1 | defmodule MyApp.Periodically do |