Agent.update зависает по таймауту

У меня есть очень простое приложение Phoenix, которое требует загрузки некоторых данных в память. Для управления этими данными я инициализировал Agent в основном lib/my_app.exstart/2 по-прежнему:

children = [
  supervisor(MyApp.Endpoint, []),
  ...
  worker(MyApp.Api.V1.MyController, []),
]

В MyApp.Api.V1.MyController У меня есть ленивый загрузчик для этих данных:

def show(conn, %{"id" => id}) do
  data_portion = get_data_portion(id)
end

def get_data_portion(id) do
  Agent.get(__MODULE__, fn map ->
    case Map.fetch(map, id) do
      {:ok, value} -> value
      :error -> load_data_portion(id)
    end
  end)
end

def load_data_portion(id) do
  data_portion = File.cwd!
                 |> Path.join("data/portions/#{id}.yml")
                 |> YamlElixir.read_from_file
  IO.puts "BEFORE"
  # ⇓⇓⇓⇓ on this call it hangs and terminates by default timeout (5s)
  Agent.update(__MODULE__, &Map.put(&1, id, data_portion))
  IO.puts "AFTER"
  data_portion
end

В случае, если это связано, мой start_link похоже:

def start_link do
  Agent.start_link(fn -> %{} end, name: __MODULE__)
end

Я почти уверен, что мне не хватает чего-то простого, но я не могу понять, что именно. Итак, мой вопрос: что не так с Agent.update позвони выше?

1 ответ

Решение

Функции, которые вы передаете Agent.get/2, Agent.update/2и т. д. выполняются в процессе агента, а не в процессе вызывающего.

То, что происходит здесь, является своего рода тупиком: вы звоните load_data_portion/1 внутри функции, которую вы передаете Agent.get/2, что означает внутри агента.

Это означает, что load_data_portion/1 выполняется внутри процесса агента. В load_data_portion/1, ты звонишь Agent.update/2, но этот вызов не может быть обработан агентом, пока текущая функция (переданная в Agent.get/2) возвращается.

Функции, которые вы передаете Agent.get|update|get_and_update функции выполняются в агенте "атомарно", то есть агент не может ничего делать, пока выполняет эти функции, включая обработку других вызовов. Так Agent.update/2 ожидает, что агент сможет обработать переданную функцию, но этот шаг ожидания происходит внутри функции, которая выполняется самим агентом - отсюда и тупик.

Вы можете использовать что-то вроде Agent.get_and_update/2 так что вы всегда можете вернуть нужные данные и загрузить данные, которых у вас нет, только при необходимости.

def get_data_portion(id) do
  Agent.get_and_update __MODULE__, fn(map) ->
    case Map.fetch(map, id) do
      {:ok, value} ->
        {value, map}
      :error ->
        data = parse_yaml_data(id)
        {data, Map.put(map, id, data)}
    end
  end
end
Другие вопросы по тегам