How to implement a resettable countdown timer using GenServer in Elixir or Erlang

How do we implement a reset countdown timer with GenServer?

1) complete the task after a certain amount of time, say, every 60 seconds

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

I looked at How to periodically perform actions with gen_server Erlang? but it doesn’t quite reflect the rest aspect of the timer before the countdown has expired.

Thanks.

+4
source share
2 answers

Phoenix? , .

, , :

defmodule MyApp.Periodically do
  use GenServer

  def start_link() 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

, . , :work, GenServer.

+8

, , ...

erlang: cancel_timer/1, ( ). " ", . , , , , , ( ), , , , .

:

-define(INTERVAL, 60000). % One minute

init(Args) ->
    ...
    % Start first timer
    MyRef = erlang:make_ref(),
    {ok, TRef} = erlang:send_after(?INTERVAL, self(), {trigger, MyRef}),
    ...
    {ok, State#your_record{
        timer  = TRef,
        latest = MyRef
        }}.

% Trigger only when the reference in the trigger message is the same as in State
handle_info({trigger, MyRef}, State = #your_record{latest=MyRef}) ->
    % Do the action
    ...
    % Start next timer
    MyRef = erlang:make_ref(),
    {ok, TRef} = erlang:send_after(?INTERVAL, self(), trigger),
    ...
    {ok, State#your_record{
        timer  = TRef,
        latest = MyRef
        }}.

% Ignore this trigger, it has been superceeded!
handle_info({trigger, _OldRef}, State) ->
    {ok, State}.

- reset :

handle_info(reset, State = #your_record{timer=TRef}) ->
    % Cancel old timer
    erlang:cancel_timer(TRef),
    % Start next timer
    MyNewRef = erlang:make_ref(),
    {ok, NewTRef} = erlang:send_after(?INTERVAL, self(), trigger),
    {ok, State#your_record{
        timer  = NewTRef,
        latest = MyNewRef
        }}.

, , .

, , , , , , , .

+4

Source: https://habr.com/ru/post/1625516/


All Articles