我有一个非常基本的凤凰应用程序,需要将一些数据加载到内存中。为了管理这些数据,我像往常一样在主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
调用有什么问题?
答案 0 :(得分:2)
传递给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