Is there a way to self terminate a genserver after no activity?

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.

2 Likes