Am I correct to assume that an Agent’s state will not be garbage collected when the Agent’s process falls out of scope? I guess what I want to know is. What happens to a Agent when its process falls out of scope and do we have to manually stop all Agents when they are no longer needed?
Note I understand that a call to an Agent’s stop function will stop and release
the Agent’s process and state(stack).
All erlang process will continue to stop unless stopped. Whether or not a variable with that processes pid is in scope or not somewhere does not matter.
So if I have a function which returns a function that forms a closure around a Agent, then I should also return something which can stop that agent right? Something like…
{function with Agent, function with call to Agent’s stop function}
A way to make sure it is always killed is to link it to a process (Agent.start_link) and thus when that process dies, so will it. It could even be a janitorial process that just kills itself after a set amount of time or so.
It will die if the other process crashes, but not if it stops/ends normally - you’d have to use GenServer’s terminate callback or equivalent to kill it in that case.
One way that makes sense to me when thinking Erlang/Elixir processes, and hence Agents, is that they very much behave like OS processes. You start a process and it keeps going, whether you have a reference to it or not, until it decides to terminate or you kill it. If it is a GenServer then you can kill it nicely so it will clean up before it dies, or kill it nastily so it just dies immediately.
When a process dies there is no way of making it automatically save any of its state. That is something you must explicitly take care of yourself.
I started this discussion because I wanted to know what would happen to the resources created by an Agent in a function which closed around that agent… Basically I was creating a simple memoization function which carried the previously calculated values in a map contained in an Agent.
def start_mem(f \\ &(&1 + 1)) do
{_, agt} = Agent.start_link(fn -> %{} end)
#return a tuple {function add/retrieve from agent, function stop agent}
{
fn v ->
case Agent.get(agt, fn m -> Map.has_key?(m, v) end) do
false ->
ans = f.(v)
Agent.update(agt, fn m -> Map.put(m, v, ans) end)
ans
true ->
Agent.get(agt, fn m -> Map.get(m, v) end)
end
end,
fn -> Agent.stop(agt) end
}
end