I want to start a gen_server that additionally, will perform one action every minute.
What is the best way to schedule that?
There is actually a built-in mechanism within gen_server to accomplish the same thing.
If the third element of response tuple of the init, handle_call, handle_cast or handle_info methods in the gen_server
is an integer, a timeout
message wil be sent to the process after that period of time in millisecs... which should be handled using handle_info. For eg :
init(Args) -> ... % Start first timer {ok, SomeState, 20000}. %% 20000 is the timeout interval handle_call(Input, From, State) -> ... % Do something ... % Do something else {reply, SomeState, 20000}. %% 20000 is the timeout interval handle_cast(Input, State) -> ... % Do something ... % Do something else {noreply, SomeState, 20000}. %% 20000 is the timeout interval %% A timeout message is sent to the gen_server to be handled in handle_info %% handle_info(timeout, State) -> ... % Do the action ... % Start new timer {noreply, SomeState, 20000}. %% "timeout" can be sent again after 20000 ms
There is also the timer
module, which could be used.
http://erldocs.com/R14B02/stdlib/timer.html?i=8&search=timer#cancel_timer/1
To precisely control the timer, you may want to use erlang:start_timer
, and save each timer reference you have created.
erlang:start_timer
has a tiny difference with erlang:send_after
, see http://www.erlang.org/doc/man/erlang.html#start_timer-3 and http://www.erlang.org/doc/man/erlang.html#send_after-3
Example use case:
init(Args) ->
...
TRef = erlang:start_timer(?INTERVAL, self(), trigger),
State = #state{tref = TRef},
...
handle_info({timeout, _Ref, trigger}, State) ->
%% With this cancel call we are able to manually send the 'trigger' message
%% to re-align the timer, and prevent accidentally setting duplicate timers
erlang:cancel(State#state.tref),
...
TRef = erlang:start_timer(?INTERVAL, self(), trigger),
NewState = State#state{tref = TRef},
...
handle_cast(stop_timer, State) ->
TRef = State#state.tref,
erlang:cancel(TRef),
%% Remove the timeout message that may have been put in our queue just before
%% the call to erlang:cancel, so that no timeout message would ever get
%% handled after the 'stop_timer' message
receive
{timeout, TRef, _} -> void
after 0 -> void
end,
...
You have two easy alternatives, use timer:send_interval/2
or erlang:send_after/3
. send_interval
is easier to setup, while send_after
(when used in the Erlang module) is more reliable since it is a built-in function, see the Efficiency Guide.
Using send_after
also ensures that the gen_server
process is not overloaded. If you were using the send_interval
function you would get a message regardless if the process can keep up or not. With send_after
being called just before the return in handle_info
you only schedule a new message once you handled the previous one. If you want more accurate time tracking you can still schedule a send_after
with the time set dynamically to something lower than ?INTERVAL
(or even 0) to catch up.
I would recommend something along the following lines in your gen_server
:
-define(INTERVAL, 60000). % One minute
init(Args) ->
... % Start first timer
erlang:send_after(?INTERVAL, self(), trigger),
...
handle_info(trigger, State) ->
... % Do the action
... % Start new timer
erlang:send_after(?INTERVAL, self(), trigger),
...
Instead of trigger
you could send something with a state if it is needed, like {trigger, Count}
or something.