32

I have a process that needs to do some work every fifteen seconds. I'm currently doing it like this:

-behavior(gen_server).

interval_milliseconds ()-> 15000.
init()->
    {ok, 
     _State = FascinatingStateData,
     _TimeoutInterval = interval_milliseconds ()
    }.

%% This gets called automatically as a result of our handlers
%% including the optional _TimeoutInterval value in the returned
%% Result
handle_info(timeout, StateData)->
    {noreply, 
     _State = do_some_work(StateData),
      _TimeoutInterval = interval_milliseconds ()
    }.

This works, but it's extremely brittle: if I want to teach my server a new message, when I write any new handler function, I have to remember to include the optional timeout interval in its return value. That is, say if I'm handling a synchronous call, I need to do this:

%% Someone wants to know our state; tell them
handle_call(query_state_data, _From, StateData)->
    {reply, StateData, _NewStateData = whatever (), interval_milliseconds ()};

instead of

%% Someone wants to know our state; tell them
handle_call(query_state_data, _From, StateData)->
    {reply, StateData, _NewStateData = whatever ()};

As you might guess, I've made that very mistake a number of times. It's nasty, because once the code handles that query_state_data message, the timeouts no longer get generated, and the whole server grinds to a halt. (I can "defibrillate" it manually by getting a shell on the machine and sending a "timeout" message by hand, but ... eww.)

Now, I could try to remember to always specify that optional Timeout parameter in my Result value. But that doesn't scale: I'll forget someday, and will be staring at this bug once again. So: what's a better way?

I don't think I want to write an actual loop that runs forever, and spends most of its time sleeping; that seems counter to the spirit of OTP.

2240
  • 1,368
  • 1
  • 6
  • 18
offby1
  • 5,675
  • 25
  • 43

3 Answers3

41

Use timer:send_interval/2. E.g.:

-behavior(gen_server).

interval_milliseconds()-> 15000.
init()->
    timer:send_interval(interval_milliseconds(), interval),
    {ok, FascinatingStateData}.

%% this clause will be called every 15 seconds
handle_info(interval, StateData)->
    State2 = do_some_work(StateData)
    {noreply, State2}.
gleber
  • 4,382
  • 1
  • 24
  • 28
  • 1
    /me smacks forehead Thanks :) – offby1 Apr 20 '09 at 21:17
  • 1
    unless you need accurate timeouts sub-millisecond and then you need to roll your own solution – Rob Elsner Apr 25 '09 at 22:11
  • 21
    I've chosen to use erlang:send_after, rather than timer:send_interval, and I thought it'd be illuminating to explain why. It's because my handle_info might take so long to complete that it'd still be running when the next interval comes by, and I don't want the timeout messages to pile up in the queue. By using erlang:send_after (once in the init function, and once again at the end of the handle_info(timeout, ...) function), I can ensure that each timeout comes _at least_ interval_milliseconds after the previous one. This might not be right for everyone, but it seems right for me. – offby1 Apr 27 '09 at 21:14
24

The best way is:

init([]) ->
  Timer = erlang:send_after(1, self(), check),
  {ok, Timer}.

handle_info(check, OldTimer) ->
  erlang:cancel_timer(OldTimer),
  do_task(),
  Timer = erlang:send_after(1000, self(), check),
  {noreply, Timer}.
Elzor
  • 744
  • 7
  • 8
  • 1
    It's been a couple of years since I've touched Erlang, but this looks like just what I wound up doing. Thanks. – offby1 Jul 25 '13 at 15:57
7

Use the timer module :)

Gordon Guthrie
  • 6,122
  • 2
  • 25
  • 50
  • 1
    It's not the best solution: http://erlang.org/doc/efficiency_guide/commoncaveats.html#id60206 – mspanc Sep 17 '17 at 11:59