In this post, I'll show how to create a simple periodic task using Elixir OTP.
In this example, we'll create a simple server calculator. For somehow, we want to keep adding numbers periodically over time. The initial structure for this process is:
defmodule Calculator do
use GenServer
@doc """
Starts the calculator.
"""
def start_link(opts) do
GenServer.start_link(__MODULE__, :ok, opts)
end
@impl true
def init(:ok) do
IO.puts("starting calculator")
{:ok, 0}
end
@impl true
def handle_call({:add, num}, _from, state) do
result = state + num
IO.puts(result)
{:reply, {:ok, result}, result}
end
def add(server, num) do
{:ok, result} = GenServer.call(server, {:add, num})
result
end
end
Here, we can add a number by sending a message {:add, num}
to the server. The client function for this purpose is add/2
.
Then, we need to create a new process that will handle the periodic tasks.
defmodule Periodic do
use GenServer
defstruct freq: nil, server: nil, callback: nil
def start_link(opts) do
GenServer.start_link(__MODULE__, :ok, opts)
end
@impl true
def init(:ok) do
{:ok, %{}}
end
@impl true
def handle_call({:periodic, data}, _from, state) do
Process.send_after(data.server, {:tick, data}, data.freq)
{:reply, data, state}
end
@impl true
def handle_info({:tick, data}, state) do
data.callback.()
Process.send_after(data.server, {:tick, data}, data.freq)
{:noreply, state}
end
def retry(server, data) do
data = Map.put(data, :server, server)
GenServer.call(data.server, {:periodic, data})
end
end
The periodic task is created using the Process.send_after/3
function. By doing this, a message {:tick, data}
is sent to the server after a time of data.freq
ms.
The idea behind this server is that the client must provide a callback
function that is run periodically in a frequency (in ms) also defined by the client. Therefore, the client must provide two inputs: callback
and freq
.
In order to run a periodic task, a client must call the retry/2
function, passing the callback
and freq
information.
Finally, we need to define the periodic task that must be run the in Calculator
server. Remember, we want to keep adding numbers periodically. For this, we define a new function add_periodic
and a constant freq
(set to be 1000 ms) at the Calculator
module:
defmodule Calculator do
...
alias Periodic
@freq 1000
def add_periodic(server, num, freq \\ @freq) do
{:ok, pid} = Periodic.start_link([])
callback = fn -> add(server, num) end
Periodic.retry(pid, %Periodic{freq: freq, callback: callback})
end
The entire code can be found HERE .
Usage example:
First open the iex shell by:
iex -S mix
Then,
import Calculator
{:ok, pid} = Calculator.start_link([])
Calculator.add_periodic(pid, 2)
And voila, we'll see something like this:
2
4
6
8
...
Top comments (0)