I have aded a small snippet and the explanation below:
defmodule Carts.Service.Restaurant do
use GenServer
@restaurant_timeout 15_000
@refresh_timeout 10_000
def handle_continue(:init, id) do
state = load_restaurant_state(id)
# send messages to self from time to time to refresh state in restaurant has been changed
trigger_state_update_call()
# send messages to self to timeout the process
state = trigger_timeout_call(state)
{:noreply, state}
end
def handle_call({:any_message}, _from, state) do
#handle any_message logic
state = trigger_timeout_call(state)
{:reply, resp, state, restaurant_timeout()}
end
def handle_info({:refresh_state}, state) do
# load restaurant state
state = if state_changed?(state), do: reload_state(), else: state
trigger_state_update_call()
{:noreply, state}
end
def handle_info(:timeout, state) do
if state.idle_timer, do: Process.cancel_timer(state.idle_timer)
{:stop, :normal, state}
end
defp trigger_state_update_call do
Process.send_after(self(), {:refresh_state}, @refresh_timeout)
end
defp trigger_timeout_call(state) do
if state.idle_timer, do: Process.cancel_timer(state.idle_timer)
idle_timer = Process.send_after(self(), :timeout, @restaurant_timeout)
%{state | idle_timer: idle_timer}
end
end
When the process starts I init two timeouts to self, one to refresh the state and one to timeout the genserver.
Refresh state timeout periodically checks for changes, updates state and triggers another refresh. This is independent of anything else from the genserver.
Timeout timer is the same with the difference that if any other message comes to the process I cancel the old timer and start it again. So as long as the genserver will receive calls it will start the timeout again. If there is no call during the timeout period it will execute the :timeout message and stop.
I hope is clear.