Agent.update在超时时挂起

时间:2015-12-14 16:38:10

标签: elixir phoenix-framework elixir-framework

我有一个非常基本的凤凰应用程序,需要将一些数据加载到内存中。为了管理这些数据,我像往常一样在主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 个答案:

答案 0 :(得分:2)

传递给Agent.get/2Agent.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