+1 that terminate
callback is not the appropriate place to cleanup counters of processes which are terminated. You need another GenServer to monitor these processes and perform the cleanup.
Instead of rolling your own, you could use gproc aggregated counters.
To make that work, you need to register the aggregated counter, e.g. in your application start callback, or in some top-level process:
:gproc.add_local_aggr_counter(:my_counter)
Now, in every process, you initialize the local counter when the process starts:
# invoke in each actor process
:gproc.add_local_counter(:my_counter, 0)
Where 0
is the initial count for that process.
When you want to change the counter value, you can use update_counter
:
# invoke in each actor process
:gproc.update_counter({:c, :l, :my_counter}, increment)
The :c
and :l
indicate that you’re updating a local counter which is tied to the current process. If the process terminates, its count will be removed from the aggregated count.
To get the aggregate value (sum of all counters), you need to invoke:
:gproc.lookup_local_aggr_counter(:my_counter)
Demo:
:gproc.add_local_aggr_counter(:my_counter)
:gproc.lookup_local_aggr_counter(:my_counter)
# 0
# start one agent and bump its count by 1
{:ok, agent1} = Agent.start_link(fn -> :gproc.add_local_counter(:my_counter, 0) end)
Agent.update(agent1, fn _ -> :gproc.update_counter({:c, :l, :my_counter}, 1) end)
# The aggregated count is now 1
:gproc.lookup_local_aggr_counter(:my_counter)
# 1
# start another agent and bump its count by 2
{:ok, agent2} = Agent.start_link(fn -> :gproc.add_local_counter(:my_counter, 0) end)
Agent.update(agent2, fn _ -> :gproc.update_counter({:c, :l, :my_counter}, 2) end)
# the aggregated count is now 3 (1 from agent1 and 2 from agent2)
:gproc.lookup_local_aggr_counter(:my_counter)
# 3
# stop agent2
Agent.stop(agent2)
# The aggregated count is now 1 (1 from agent1)
:gproc.lookup_local_aggr_counter(:my_counter)