Agent.update 超时挂断

Agent.update hangs up on timeout

我有一个非常基本的 phoenix 应用程序,它需要将一些数据加载到内存中。为了管理这些数据,我像往常一样在 main lib/my_app.ex start/2 中初始化了一个 Agent

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调用有什么问题

您传递给 Agent.get/2Agent.update/2 等的函数在代理进程中执行,而不是在调用方进程中执行。

这里发生的是一种死锁:您在传递给 Agent.get/2 的函数中调用 load_data_portion/1,这意味着 在代理 中。

这意味着load_data_portion/1是在代理进程内部执行的。在 load_data_portion/1 中,您调用了 Agent.update/2,但是在当前函数(传递给 Agent.get/2 的函数)returns.[=22= 之前代理无法处理该调用]

您传递给 Agent.get|update|get_and_update 函数的函数在代理中执行 "atomically",这意味着代理在执行这些函数时无法执行任何操作 - 包括处理其他调用。所以 Agent.update/2 正在等待代理可以自由处理传递的函数,但是等待步骤发生在代理本身正在执行的函数内部 - 因此出现死锁。

您可能想要使用 Agent.get_and_update/2 之类的东西,这样您就可以始终 return 需要的数据,并仅在必要时加载您没有的数据。

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