Genserver - Issue with tick in Process.send_after_self()

defmodule Server.Foo do
  use GenServer
  use Export.Python
  require Logger

  @ms_sleep_interval 500

  def start_link(_) do
    GenServer.start_link(__MODULE__, %{})
  end

  @impl true
  def init(state) do
    Process.flag(:trap_exit, true)

    Logger.info("Launching #{__MODULE__} . . . ")

    priv_path = Path.join(:code.priv_dir(:arbit), "python")
    {:ok, py} = Python.start_link(python_path: priv_path)

    fetch(py)
    |> Bar.main()

    Process.send_after(self(), :tick, @ms_sleep_interval)
    {:ok, Map.put(state, :py, py)}
  end

  def fetch(py) do 
    Logger.info("Running #{__MODULE__} . . . ")

    Python.call(py, "baz", "main", [])
  end

  @impl true
  def handle_info(:tick, %{py: py} = state) do
    Process.send_after(self(), :tick, @ms_sleep_interval)

    fetch(py)
    |> Bar.main()

    {:noreply, state}
  end

  def handle_info({:EXIT, _, :normal}, %{py: py} = state) do 
    Process.send_after(self(), :tick, @ms_sleep_interval)

    fetch(py)
    |> Bar.main()

    {:noreply, state}
  end

  @impl true
  def terminate(_reason, %{py: py}) do
    Python.stop(py)
    :ok
  end
end

Tick isn’t working as intended. A new instance is started every few seconds, rather than every half-second. What am I overlooking?

How long does this take to execute?

The handle_info callback won’t complete until it does. If it takes longer than your @ms_sleep_interval then all that Process.send_after does it put something in the GenServer’s mailbox, but the GenServer won’t handle it until the prior handle_info returns.

4 Likes

Thank you for your insights. That takes about four seconds to complete. How could I go about unblocking the GenServer so that it processes the Process.send_after message before Python.call() completes?

Use :timer.send_interval once on init instead of reissuing Process.send

https://erlang.org/doc/man/timer.html#send_interval-2

You might also want to run the python code in its own task.

2 Likes

You can’t technically do that as you asked. You need to understand that GenServers are sequential. They do one thing at a time – pull one message from the mailbox, execute the logic related to that, then look at the next message in the mailbox. However, your handle_info callback could launch another process, such as a Task to do the work in a separate process. That way you have multiple processes doing things concurrently.

6 Likes