I’m implementing a game server that tracks questions and answers. Once the game is started, a timer starts running, and after it’s done, the game process will exit. A user can also stop the game before the timer is done. Since I already had the game server implemented for tracking the game state and the module was getting pretty complex, I’ve added the timer as a separate GenServer.
defmodule Qotd.Games.GameSession do
use GenServer
# skipped most of the module and GenServer and Supervisor glue code for brevity
def handle_call(:start, _from, game) do
game = Game.start(game)
{:ok, _} = start_timer()
{:reply, {:ok, game}, game}
end
def handle_info({:EXIT, _from, :normal}, game) do
game = Game.close(game)
{:stop, :normal, game}
end
def handle_info({:EXIT, _from, reason}, game) do
{:stop, reason, game}
end
defp start_timer(game) do
Process.flag(:trap_exit, true)
Timer.start_link(id: {__MODULE__, game.id}, ends_at: timer_ends_at())
end
end
defmodule Qotd.Timer do
use GenServer
alias Qotd.Timer.Timer
defp via(id), do: {:via, Registry, {Qotd.Registry.Timer, id}}
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: via(opts[:id]))
end
@impl true
def init(opts) do
timer = Timer.new(opts[:ends_at])
ref = :timer.send_interval(opts[:interval], :tick)
{:ok, {ref, timer}}
end
def handle_info(:tick, {ref, timer}) do
tick({ref, timer})
end
@impl true
def handle_call(:check, _from, {ref, timer}) do
{:reply, timer, {ref, timer}}
end
def handle_call(:check, _from, {ref, timer}) do
{:reply, timer, {ref, timer}}
end
def handle_call(:stop, _from, state) do
{:stop, :normal, state}
end
defp tick({ref, timer}) do
case Timer.tick(timer) do
%{remaining: 0} = timer ->
{:stop, :normal, timer}
timer ->
{:noreply, {ref, timer}}
end
end
end
My question is: is this a reasonable approach for running the timer? And if it is, how would I handle the timer process crashing without also killing the game session? Ideally, I would like to restart the timer several times before giving up.