Search code examples
timerelixircountdowngen-server

How to implement a resetable countdown timer with a GenServer in Elixir or Erlang


How do we implement a reset-able countdown timer with a GenServer?

1) perform a task after fixed amount of time, say every 60 seconds

2) have a way to reset the countdown back to 60 seconds before the timer elapses

I have looked at How to perform actions periodically with Erlang's gen_server? but it doesn't quite cover the aspect of resting the timer before the countdown elapses.

Thanks.


Solution

  • How can I schedule code to run every few hours in Elixir or Phoenix framework? defines how to do a periodic job.

    To implement a cancel using that as a base you could do the following:

    defmodule MyApp.Periodically do
      use GenServer
    
      def start_link(_name \\ nil) do
        GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
      end
    
      def reset_timer() do
        GenServer.call(__MODULE__, :reset_timer)
      end
    
      def init(state) do
        timer = Process.send_after(self(), :work, 60_000)
        {:ok, %{timer: timer}}
      end
    
      def handle_call(:reset_timer, _from, %{timer: timer}) do
        :timer.cancel(timer)
        timer = Process.send_after(self(), :work, 60_000)
        {:reply, :ok, %{timer: timer}}
      end
    
      def handle_info(:work, state) do
        # Do the work you desire here
    
        # Start the timer again
        timer = Process.send_after(self(), :work,60_000)
    
        {:noreply, %{timer: timer}}
      end
    
      # So that unhanded messages don't error
      def handle_info(_, state) do
        {:ok, state}
      end
    end
    

    This maintains a reference to the timer, allowing it to be cancelled. Every time the :work message is received, a new timer is created and stored in the state of the GenServer.