Process monitoring inside a Nostrum Consumer

Hi everyone,

I’m working on a bot feature (blind test) on Discord using Nostrum.

A command is used to start the game and spin up a GenStateMachine.

I want to monitor a crash of this GenStateMachine in order to send a simple message to the users saying “kaboom!” to advertise about the crash.

For now, I’m starting the processus here, it works great but I can’t handle a process crash.

I tried this option but it does not work :

defmodule Game.Monitor do
  def run(game_data, dl_data) do
    {:ok, _} = Game.start_link(game_data, dl_data)

    ref = Process.monitor(Game)

    receive do
      {:DOWN, ^ref, :process, _from_pid, reason} -> IO.puts("Exit reason: #{reason}")
    end
  end
end

I don’t get why it does not pass in the receive branch.

I’m open to other solution if monitoring is not the good fit here.

Thanks for reading and thank your answers/feedback.

You should monitor the launched pid, not the atom Game.

Thanks for your answer, I tried this before too, but the behavior is the same. The process is restarted and it does not pass into the receive block

You have linked the processes together with start_link. If the game crashes, it will also call whatever process is calling Game.Monitor.run. You should start the game under a supervisor.

1 Like

Thanks for your reply.

Just to be sure I correctly understand your answer. I should :

  • Start the game in an “emtpy/waiting” state, under a Supervisor module
  • When a user request a new game, pass inits data to it, start the game
  • In case of crash, listen for handle_info inside the Supervisor module
  • If game goes well, return to “empty/waiting” state

Hey @Papey I think I would do things differently. I think there is no point in starting a game in an “empty” state, I would simply spawn a game under a supervisor when they are requested. I would probably use a DynamicSupervisor btw because you’ll need to be able to dynamically start new games.

Secondly, Supervisor modules are not really user editable, you don’t really get to run custom code inside of them, doing so would decrease their reliability. If you want to do something on crash, then you should, in some other process, do {:ok, pid} = DynamicSupervisor.start_child(SomeSuperName, {Game, opts}) and then Process.monitor(pid). Then the game is linked to its supervisor, but is monitored by you.

Thanks for answer. I created a GenServer responsible of the Game monitoring and it works !

Here is the code if anyone reading this later needs it

defmodule Game.Monitor do
  @moduledoc """
  A simple GenServer used to monitor a game process
  """

  use GenServer
  require Logger

  def init(channel_id) do
    # On init try to attach to an existing game process
    # In case of Monitor failure, this will reattach the monitor to the game process
    if Process.whereis(Game) do
      Process.monitor(Game)
    end

    {:ok, channel_id}
  end

  def start_link(channel_id) do
    GenServer.start_link(__MODULE__, channel_id, name: __MODULE__)
  end

  def monitor() do
    GenServer.cast(__MODULE__, :monitor)
  end

  def handle_cast(:monitor, channel_id) do
    Process.monitor(Game)
    {:noreply, channel_id}
  end

  def handle_info({:DOWN, _ref, :process, object, reason}, channel_id) when reason != :killed do
    Logger.info("Game process monitor detected a game crash", reason: reason, data: object)

    Nostrum.Api.create_message!(
      channel_id,
      embed: %Nostrum.Struct.Embed{
        :title => "KABOOM ALERT, I just explode, sorry.",
        :description => "💥",
        :color => Colors.get_color(:danger)
      }
    )

    {:noreply, channel_id}
  end
end

I now need to make it less static but I get the idea. Thaks a lot.

1 Like