Обработчик тайм-аута GenServer не вызывается - PullRequest
1 голос
/ 26 марта 2019

У меня довольно простая настройка: один GenServer, своего рода кеш, который порождает дочерние GenServers с таймаутом, который они обрабатывают, send отправляя родителю сообщение об их бездействии.

Ребенок проходит тесты, которые подтверждают, что он отправляет {:inactive, my_id} после указанного времени ожидания. Проблема в том, что это происходит только до тех пор, пока ребенок никогда не получит вызов, чтобы получить данные в их состоянии, и в этом случае они никогда не останавливаются.

Почему обработка одного вызова должна предотвращать тайм-аут? Есть ли способ обрабатывать звонки, не ограничивая время ожидания?

Полный тестовый пример здесь: https://github.com/thure/so-genserver-timeout

Ребенок:

defmodule GenServerTimeoutBattery.Child do
  use GenServer

  def start_link(child_id, timeout_duration, parent_pid) do
    GenServer.start_link(__MODULE__, [child_id, timeout_duration, parent_pid], [name: String.to_atom(child_id)])
  end

  def get_data(child_id) do
    GenServer.call(String.to_atom(child_id), :get_data)
  end

  @impl true
  def init([child_id, timeout_duration, parent_pid]) do
    IO.puts('Timeout of #{timeout_duration} set for')
    IO.inspect(child_id)
    {
      :ok,
      %{
        data: "potato",
        child_id: child_id,
        parent_process: parent_pid
      },
      timeout_duration
    }
  end

  @impl true
  def handle_call(:get_data, _from, state) do
    IO.puts('Get data for #{state.child_id}')
    {
      :reply,
      state.data,
      state
    }
  end

  @impl true
  def handle_info(:timeout, state) do
    # Hibernates and lets the parent decide what to do.
    IO.puts('Sending timeout for #{state.child_id}')
    if is_pid(state.parent_process), do: send(state.parent_process, {:inactive, state.child_id})

    {
      :noreply,
      state,
      :hibernate
    }
  end
end

Тест:

defmodule GenServerTimeoutBattery.Tests do
  use ExUnit.Case

  alias GenServerTimeoutBattery.Child

  test "child sends inactivity signal on timeout" do
    id = UUID.uuid4(:hex)

    assert {:ok, cpid} = Child.start_link(id, 2000, self())

    # If this call to `get_data` is removed, test passes.
    assert "potato" == Child.get_data(id)

    assert_receive {:inactive, child_id}, 3000

    assert child_id == id

    assert :ok = GenServer.stop(cpid)
  end
end

1 Ответ

1 голос
/ 26 марта 2019

Оказывается, установка timeout на init применяет тайм-аут, который имеет значение только до получения вызова или трансляции.

Каждый вызов или трансляция может затем установить свой собственный timeout. Если не указано timeout, по умолчанию используется значение :infinity. Документы на этот счет не указаны, хотя теперь это имеет смысл для меня.

...