2
votes

I have a gen_server that mimics a gen_fsm, (don't ask me why...), a gen_server:call will cause this gen_server from the current state to transfer to the next state, if no gen_server:call invoked in a certain time, the gen_server terminates.

To be more specific, the life of the gen_server looks like this:

state_1 -> state_2 -> ... -> state_N -> terminated

when the server is at state_i, if there is no gen_server call invoked on this server, after t_i seconds, the server will go to state terminated, of cause, this is achieved by using {reply, Reply, NewState, t_i} as the return of the handle_call/3.

The problem of the method is that, I cannot retrieve some information from this gen_server, for to do that, I need to invoke gen_server:call on it, which would mess the timeout up.

One possible workaround is to put the last state transfer time stamp into the state, after each retrieval call, reset the new timeout to a appropriate value, a prototype looks like this:

handle_call(get_a, _From, #state{a = 1,
                                 state = state_2,

                                 %% this is the timestamp when the server transfered to state_2
                                 unixtime = T1

                                }=S) ->
    Reply = S#state.a,
    NewTimeout = t_2 - (current_unixtime() - T1),
    {reply, Reply, S, NewTimeout};

In this way, I can get the effect I want, but it's ugly, is there any better way to to this?

1

1 Answers

2
votes

If you want to set timeouts independently of other events like calls it's probably best to use a timed message.

When you set the timeout use erlang:send_after/3:

TimerRef = erlang:send_after(10000, self(), my_timeout).

You can cancel your timeout any time with erlang:cancel_timer/1.

erlang:cancel_timer(TimerRef).

Receive it with handle_info:

 handle_info(my_timeout, State) ->

If you need multiple such timeouts, use a different message, or if you have the possibility of some sort of race condition and need further control, you can create unique references with erlang:make_ref/0 and send a message like {Ref, my_timeout}.

Watch out for the edge cases - remember you could cancel a timer and then still receive it unexpectedly (because it's in your message queue when you cancel), and you don't make them unique (as suggested above using a reference) you could be expecting a timeout, and get it early, because it's the previous one that entered your message queue, etc (where as with a reference you can check it is the latest set). These things are easy to deal with, but watch out for them.