Registry.update_value/3 error when called from Task but not from iex

I am trying to grow my skills a bit and exploring the Registry module. I have a custom registry set up as follows:

defmodule MyReg do 
  def start_link() do 
    Registry.start_link(keys: :unique, name: MyReg)
    Registry.register(MyReg, :default_key, MapSet.new())
  end

I want to iterate over a collection and store each item in the MapSet accessed by the :default_key but also use each item as another key with it’s own value.

# in the same module
def register(item, value) do 
  case Registry.register(MyReg, item, [value]) do 
    {:ok, _} ->
       # the item was not previously registered so add it to the collection of keys
       Registry.update_value(MyReg, :default_key, fn keys -> MapSet.put(keys, item) end)
    _ ->
      # if the item has already been registered, update it's values
       Registry.update_value(MyReg, item, fn vals -> [value | vals] end)
  end
end

When I run this manually from within iex -S mix it seems to work great. But when I try to run it as follows it fails at the update_value step.

MyReg.start_link()

collection
|> Task.async_stream(fn {item, value} -> 
        MyReg.register(item, value)
end)
|> Enum.to_list()
# [ok: :error, ok: :error, ...]
> Registry.lookup(MyReg, :default_key)
# MapSet.new()

I believe that the problem is that somehow the processes spawned by Task.async_stream do not have access to the registry created with MyReg.start_link, but I don’t know why.

After enumerating over the collection I can look at the contents of the registry and see that the initial start_link call was successful but that it was never modified after that.

As per documentation, this function updates the value for key for the current process in the unique registry.

Your example might be simplified to:

iex|💧|1 ▸ Registry.start_link(keys: :unique, name: MyReg)
iex|💧|2 ▸ Registry.register(MyReg, :default_key, :value)
iex|💧|3 ▸ spawn fn -> Registry.update_value(MyReg, :default_key, fn _ ->
...|💧|3 ▸   :new_value
...|💧|3 ▸ end) |> IO.inspect() end
:error

Registry.lookup/2 also returns a list of tuples {pid(), value}. You might turn your MyReg into GenServer and use its handle_cast/2/handle_call/2 to amend/query the backed registry.

hmm. So Registry is like ETS with :private and no way to make it :public? It’s weird because the source for Registry all ETS tables are initiated with :public options. I might be better off dropping down to ETS in that case rather than adding a middleman.

I guess I also expected access to “bubble up” to parent processes which is clearly wrong. So what is the correct way to have sibling processes access a Registry? Do you need a Supervisor or GenServer as the parent process, that receives messages from children and updates the registry accordingly?

            Parent.Supervisor
                   _|___________
                  |   |        |
              MyReg   Child1  Child2

Cross-linking a useful analogy from another thread:

2 Likes

Thanks, dropping back to ETS for this use case is probably the way to go.

1 Like