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/2
、Agent.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
我有一个非常基本的 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/2
、Agent.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