[erlang-questions] The Danger of gen:server timeout

151 views
Skip to first unread message

Ben Murphy

unread,
Jun 2, 2017, 5:09:18 AM6/2/17
to Erlang-Questions Questions
So a bunch of gen_server:* methods allow you to return a timeout
value. So if you were thinking about how this would be implemented you
would expect if elapsed time >Timeout it would send a timeout message
to your process. This is true. However, if elapsed time > much greater
than Timeout then a timeout message could be sent to your process :/
How is this possible?

If your process receives a system message before the timeout then the
timeout is reset without considering how long you have waited before
receiving a system message. For example: you could be waiting 1 month
for a timeout then after 20 days someone did sys:get_state on your
process then you are waiting another 1 month for your timeout..

-module(wtf).
-behaviour(gen_server).

-export([init/1, code_change/3, handle_call/3, handle_cast/2,
handle_info/2, terminate/2]).

init(_Args) ->
TS = erlang:timestamp(),
io:format("init: ~p~n", [TS]),
{ok, TS, 30 * 1000}.


handle_info(Message, TS) ->
io:format(user, "handle info :~p ~p ~n", [Message, erlang:timestamp()]),
Now = erlang:timestamp(),
io:format(user, "difference: ~p ~n", [timer:now_diff(Now, TS)]),
{noreply, TS}.


code_change(_1, _2, _3) ->
throw(wtf).
handle_call(_1, _2, _3) ->
throw(wtf).
handle_cast(_1, _2) ->
throw(wtf).
terminate(_1, _2) ->
ok.

Erlang/OTP 19 [erts-8.0] [source] [64-bit] [smp:4:4]
[async-threads:10] [hipe] [kernel-poll:false]

Eshell V8.0 (abort with ^G)
1> {ok, Pid} = gen_server:start_link(wtf, [], []).
init: {1496,394234,956218}
{ok,<0.59.0>}
2> sys:get_state(Pid).
{1496,394234,956218}
3> handle info :timeout {1496,394279,244306}
difference: 44288466
_______________________________________________
erlang-questions mailing list
erlang-q...@erlang.org
http://erlang.org/mailman/listinfo/erlang-questions

Jesper Louis Andersen

unread,
Jun 2, 2017, 7:59:46 AM6/2/17
to Ben Murphy, Erlang-Questions Questions
Here is the crux of what you are toying with: Erlang has two kinds of timeouts: idle timeouts and point-in-time timeouts. The timeouts you can set in a gen_server are idle timeouts, so they are implemented basically as

receive
  ...
after Timeout ->
  ...
end

What this means is that any message which arrives in the mailbox cancels the timeout. In particular, your sys:get_state/1 call invokes such a message, resetting the timeout. If you want a point-in-time timeout, there is another tool for this: erlang:send_after/3. It will deliver a message through another timer system and even if the process is active, you will get the timer in a relatively timely fashion. You are not guaranteed to get the timer precisely at the point if the system is heavily loaded, but usually you get it within a couple of ms of where you want at worst. This is the essence of being soft-realtime, amont other things.

The gen_server timeouts are nice to use for the situation where idling means you can do some janitorial work in your process or do a hibernation. I often have some janitorial work that runs either on a counter (every 100 message) and also on an idle timer (20 seconds has passed with no message).

Jachym Holecek

unread,
Jun 2, 2017, 8:20:58 AM6/2/17
to Jesper Louis Andersen, Erlang-Questions Questions
# Jesper Louis Andersen 2017-06-02:
> Here is the crux of what you are toying with: Erlang has two kinds of
> timeouts: idle timeouts and point-in-time timeouts. The timeouts you can
> set in a gen_server are idle timeouts, so they are implemented basically as
>
> receive
> ...
> after Timeout ->
> ...
> end

They may happen to be implemented that way in gen_server, but it is debatable
whether this is intentional (might have been purely for simplicity) and
whether this is correct as far as expectations go -- system messages are
not seen by the user code running the gen_server process (they are handled
under the hood), so as far as user logic is concerned the server had been
idle despite these under-the-hood messages perhaps flowing through.

Not that this has ever been an issue for me personally, and as you indicate
it's extremely easy to work around explicitely... But I do recall implementing
idle timeouts with erlang:start_timer/X without resetting the timer on
system messages in a custom behaviour module on at least one occasion,
and I recall thinking implemention in gen_server / gen_fsm (which I used
as reference) was a bit suspicious, although in a harmless way.

So there's that.

BR,
-- Jachym
Reply all
Reply to author
Forward
0 new messages