Search code examples
elixirerlang-otpphoenix-framework

Which OTP behavior should I use for an "endless" repetition of tasks?


I want to repeatedly run the same sequence of operations over and over again next to a Phoenix application (without crashing the whole web-app if something brakes in the worker of course) and don't really know wether I should use a GenServer, Elixir's Tasks, an Agent or something completely different I haven't thought about so far.

When I start my Phoenix app a worker should start as well, that periodically pulls some values of a serial-connection, broadcasts them through a Phoenix channel, collects them until @save_interval is reached and then calculates the median, broadcasts that median via a different channel and writes it to an InfluxDB. Right now I have something (kind of working) like this:

def do_your_thing(serial_pid) do
  Stream.interval(@interval_live)
    |> get_new_values_from_serial(serial_pid)
    |> broadcast!("live-channel:#{@name}")
    |> Enum.take(div(@interval_save, @interval_live))
    |> calculate_medians()
    |> broadcast!("update-channel:#{@name}")
    |> write_to_database()

  do_your_thing(serial_pid) # repeat
end

I'm only starting to figure all that OTP stuff out and hope someone of you could help me stumble into the right direction here.


Solution

  • You should use a GenServer that sends itself messages after x seconds (60 seconds in the example below):

    defmodule MyApp.Worker do
      use GenServer
    
      def start_link() do
        GenServer.start_link(__MODULE__, [])
      end
    
      def init([]) do
        schedule_work()
        {:ok, []}
      end
    
      def handle_info(:work, state) do
        state = do_work(state)
        schedule_work()
        {:noreply, state}
      end
    
      defp do_work(state) do
        # Do your work here and return state
      end
    
      defp schedule_work do
        Process.send_after(self(), :work, 60_000)
      end
    end