DEV Community

Cover image for Adding periodic task in Elixir with OTP
rafaquelhodev
rafaquelhodev

Posted on

Adding periodic task in Elixir with OTP

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The entire code can be found HERE .


Usage example:

First open the iex shell by:

iex -S mix
Enter fullscreen mode Exit fullscreen mode

Then,

import Calculator

{:ok, pid} = Calculator.start_link([])

Calculator.add_periodic(pid, 2)
Enter fullscreen mode Exit fullscreen mode

And voila, we'll see something like this:

2
4
6
8
...
Enter fullscreen mode Exit fullscreen mode

Top comments (0)