Agent.update зависает по таймауту
У меня есть очень простое приложение Phoenix, которое требует загрузки некоторых данных в память. Для управления этими данными я инициализировал Agent
в основном lib/my_app.ex
start/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