i was following a tutorial on using registry to store state located here: https://medium.com/@adammokan/registry-in-elixir-1-4-0-d6750fb5aeb but am having some problems with the docs maybe i just dont understand so here goes.
defmodule City.Server do
use GenServer
def start_link(city) do
name = via_tuple(city)
GenServer.start_link(__MODULE__, [city], name: name)
end
defp via_tuple(city) do
{:via, Registry, {:city_registry, city}}
end
def top_agent(city) do
GenServer.call(via_tuple(city), :top_agent)
end
def agent(city) do
GenServer.call(via_tuple(city), :agent)
end
def loadAgents(city) do
agents = Site.AgentController.getAgents(city)
:queue.from_list(agents)
end
## Server callbacks
def init(:ok) do
state = loadAgents()
{:ok, state }
end
def process_next_in_queue({q, nil}) do
Logger.debug("ProcessNext - Current agent is nil, Processing Queue: #{inspect q}")
case :queue.out(q) do
{:empty, q} -> {q, nil}
{{:value, agent}, q} -> {q, Task.Supervisor.async(:city_registry, agent)}
end
end
and the in the site sup
supervisor(Registry, [:unique, :city_registry])
however i am missing something, the registry does start and the city loads with the agents, however i am having trouble with the fact i have multiple citys, id like to start each as a genserver can anyone help
One of the problems I see is how you init
your genserver. See how it’s done in the article you link https://github.com/amokan/registry_sample/blob/master/lib/registry_sample/account.ex#L77
so that if you pass [city]
in
def start_link(city) do
name = via_tuple(city)
GenServer.start_link(__MODULE__,
[city], # <-- here
name: name)
end
then you need to expect it in init/1
. Try this
def init([city]) do
state = load_agents(city)
{:ok, state}
end
instead of this
def init(:ok) do
state = loadAgents()
{:ok, state }
end
i fixed that the resulting code is here and it does work i do have the agents listed in the state of a city genserver, i know how to load in iex but how do i load a genserver per city from list of [citys] in phoenix? i will have a later operation that will fetch the agent at the top of that citys queue in order to send a message via channel. simple ordered queue per city. im thinking i should use registry to hold the processes started with the genservers (provided i can start them) and somehow route to them via another genserver. is this something more suited for genstage? im having trouble with should i have a supervisor start the genserver have it fetch an initial city list and then have that server make the same sup start more genservers as it enums through the list. is this the correct steps i should take or am i way off?
defmodule City.Server do
require Logger
use GenServer
def start_link(city) do
name = via_tuple(city)
GenServer.start_link(__MODULE__, city, name: name)
end
defp via_tuple(city) do
{:via, Registry, {:city_registry, city}}
end
def top_agent(city) do
GenServer.call(via_tuple(city), :top_agent)
end
def agent(city) do
GenServer.call(via_tuple(city), :agent)
end
def loadAgents(city) do
agents = Site.ScorecardController.getAgentsbyCity(city)
:queue.from_list(agents)
end
def read(pid) do
GenServer.call(pid, {:read})
end
def add(pid, item) do
GenServer.cast(pid, {:add, item})
end
## Server callbacks
def init(city) do
state = loadAgents(city)
{:ok, state }
end
def handle_call({:read}, from, state) do
{:reply, state, state}
end
Yes, you can start them in a supervisor. I don’t think there is a need for genstage.
btw, messages don’t have to be tuples, so you can just use :read
and not {:read}
.
can you explain how i would start them in a sup, im still kinda new to elixir, im confused where i would pull my list of cities to start the process and how/where i start the sup i cant find any examples of how to do this your help is appreciated, i think i see tho i can run the db wuery in the init method of the sup right? how do i impliment a router tho? do i have to use registry /
Suppose your city
s come and go (that is, they are :transient
), then your city supervisor might look like this
defmodule City.Supervisor do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, nil, name: __MODULE__)
end
def start_city(city_id) do
Supervisor.start_child(__MODULE__, [city_id])
end
def stop_city(city_id) do
case Registry.lookup(:city_registry, {:city_registry, city_id}) do
[] -> :ok
[{pid, _}] ->
Process.exit(pid, :shutdown)
:ok
end
end
def init(_) do
children = [worker(City, [], restart: :transient)]
supervise(children, [strategy: :simple_one_for_one])
end
end
and you can start them in your application
defmodule City.Application do
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
supervisor(Registry, [:unique, :city_registry]),
supervisor(City.Supervisor, []),
worker(Task, [&start_cities/0], restart: :transient)
]
opts = [strategy: :one_for_one, name: __MODULE__.Supervisor]
Supervisor.start_link(children, opts)
end
defp start_cities do
citi_ids = # get the list of (initial) cities to start somehow
for city_id <- city_ids, do: City.Supervisor.start_city(city_id)
end
end
1 Like
thanks so much i understand now!! didnt know about transient
I’m glad we could help. As a minor point, punctuation really does matter. A large block of text with no paragraph breakups, punctuation, and word contractions in my view says that a person does not care enough about their question to format it congenially.
I am disabled with 15% usage of my hands and program 40h a week i miss punctuation n stuff cuz of distal interphalingeal disorder etc etc