Issue with Horde / HordeRegistry with elixir 1.18

Hi folks, I’m trying the simple SayHello example from the Horde docs, but I can’t get it to work.

I can start a child with:

Horde.DynamicSupervisor.start_child(
  HelloClusterApp.HelloSupervisor, 
  {HelloClusterApp.SayHello, name: "say_hello_1"}
)

I can also lookup on both nodes, and the process is actually there:

iex(foo@jotumhein)> Horde.Registry.lookup(HelloClusterApp.HelloRegistry, "say_hello_1")
[{#PID<24805.479.0>, nil}]

and:

iex(bar@jotumhein)> Horde.Registry.lookup(HelloClusterApp.HelloRegistry, "say_hello_1")
[{#PID<0.479.0>, nil}]

But I can’t figure it out how to send a message.

I’m trying:

GenServer.call({:via, Horde.Registry, {HelloClusterApp.HelloRegistry, "say_hello_1"}}, :action)

.. but I always get this error (running on the same node or not):

** (exit) exited in: GenServer.call({:via, Horde.Registry, {HelloClusterApp.HelloRegistry, "say_hello_1"}}, :action, 5000)
    ** (EXIT) an exception was raised:
        ** (Protocol.UndefinedError) protocol String.Chars not implemented for type Tuple. This protocol is implemented for the following type(s): Atom, BitString, Date, DateTime, Float, Integer, List, NaiveDateTime, Time, URI, Version, Version.Requirement

Got value:

    {#PID<0.219.0>, [:alias | #Reference<0.0.28035.3569112820.221577217.43273>]}

            (elixir 1.18.4) lib/string/chars.ex:3: String.Chars.impl_for!/1
            (elixir 1.18.4) lib/string/chars.ex:22: String.Chars.to_string/1
            (hello_cluster_app 0.1.0) lib/hello_cluster_app/say_hello.ex:37: HelloClusterApp.SayHello.handle_call/3
            (stdlib 6.2.2.1) gen_server.erl:2381: :gen_server.try_handle_call/4
            (stdlib 6.2.2.1) gen_server.erl:2410: :gen_server.handle_msg/6
            (stdlib 6.2.2.1) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
    (elixir 1.18.4) lib/gen_server.ex:1128: GenServer.call/3
    iex:13: (file)

11:58:14.039 [error] GenServer {HelloClusterApp.HelloRegistry, "say_hello_1"} terminating
** (Protocol.UndefinedError) protocol String.Chars not implemented for type Tuple. This protocol is implemented for the following type(s): Atom, BitString, Date, DateTime, Float, Integer, List, NaiveDateTime, Time, URI, Version, Version.Requirement

Got value:

    {#PID<0.219.0>, [:alias | #Reference<0.0.28035.3569112820.221577217.43273>]}

    (elixir 1.18.4) lib/string/chars.ex:3: String.Chars.impl_for!/1
    (elixir 1.18.4) lib/string/chars.ex:22: String.Chars.to_string/1
    (hello_cluster_app 0.1.0) lib/hello_cluster_app/say_hello.ex:37: HelloClusterApp.SayHello.handle_call/3
    (stdlib 6.2.2.1) gen_server.erl:2381: :gen_server.try_handle_call/4
    (stdlib 6.2.2.1) gen_server.erl:2410: :gen_server.handle_msg/6
    (stdlib 6.2.2.1) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
Last message (from #PID<0.219.0>): :action
State: %{count: 0, init_args: ["say_hello_1"]}
Client #PID<0.219.0> is alive

    (stdlib 6.2.2.1) gen.erl:260: :gen.do_call/4
    (elixir 1.18.4) lib/gen_server.ex:1125: GenServer.call/3
    (elixir 1.18.4) src/elixir.erl:386: :elixir.eval_external_handler/3
    (stdlib 6.2.2.1) erl_eval.erl:919: :erl_eval.do_apply/7
    (elixir 1.18.4) src/elixir.erl:364: :elixir.eval_forms/4
    (elixir 1.18.4) lib/module/parallel_checker.ex:120: Module.ParallelChecker.verify/1
    (iex 1.18.4) lib/iex/evaluator.ex:336: IEx.Evaluator.eval_and_inspect/3
    (iex 1.18.4) lib/iex/evaluator.ex:310: IEx.Evaluator.eval_and_inspect_parsed/3

Same thing if running with GenServer.call(HelloClusterApp.SayHello.via_tuple("say_hello_1"), :action)


Extra info

I have created a new project scaffold with mix new hello_cluster_app --sup, and my build is:

$ elixir -v
Erlang/OTP 27 [erts-15.2.7] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [dtrace]

Elixir 1.18.4 (compiled with Erlang/OTP 26)

application.ex starts as:

def start(_type, _args) do
    topologies = [example: [strategy: Cluster.Strategy.Gossip]]

    children = [
      {Cluster.Supervisor, [topologies, [name: HelloClusterApp.ClusterSupervisor]]},
      {Horde.Registry, [name: HelloClusterApp.HelloRegistry, keys: :unique, members: :auto]},
      {Horde.DynamicSupervisor,
       [name: HelloClusterApp.HelloSupervisor, strategy: :one_for_one, members: :auto]}
    ]

    opts = [strategy: :one_for_one, name: HelloClusterApp.Supervisor]
    Supervisor.start_link(children, opts)
  end

say_hello.ex:

defmodule HelloClusterApp.SayHello do
  use GenServer
  require Logger

  def child_spec(opts) do
    name = Keyword.get(opts, :name, __MODULE__)

    %{
      id: "#{__MODULE__}_#{name}",
      start: {__MODULE__, :start_link, [name]},
      shutdown: 10_000,
      restart: :transient
    }
  end

  def start_link(name) do
    case GenServer.start_link(__MODULE__, [name], name: via_tuple(name)) do
      {:ok, pid} ->
        {:ok, pid}

      {:error, {:already_started, pid}} ->
        Logger.info("already started at #{inspect(pid)}, returning :ignore")
        :ignore
    end
  end

  @impl true
  def init(args) do
    state = %{count: 0, init_args: args}
    {:ok, state}
  end

  def via_tuple(name), do: {:via, Horde.Registry, {HelloClusterApp.HelloRegistry, name}}

  @impl true
  def handle_call(msg, from, state) do
    IO.puts("Handling call: #{from} => #{inspect(state)} => #{inspect(msg)}")
    {:reply, :foo, state}
  end

  @impl true
  def handle_info(msg, state) do
    IO.puts("Handling info: #{inspect(state)} => #{inspect(msg)}")
    {:noreply, state}
  end
end

Pretty sure you’ll just need to add an inspect(from) to your handle_call function:

IO.puts(“Handling call: #{from} => #{inspect(state)} => #{inspect(msg)}”)

OMFG!

I wrote this last night while not fully awake and I missed that! shame on me!

Thank you so much!

ps: having a little of time to inspect the stack trace, the error message was clear and I didn’t pay enough attention to it:

                (elixir 1.18.4) lib/string/chars.ex:22: String.Chars.to_string/1
--->            (hello_cluster_app 0.1.0) lib/hello_cluster_app/say_hello.ex:37: HelloClusterApp.SayHello.handle_call/3
                (stdlib 6.2.2.1) gen_server.erl:2381: :gen_server.try_handle_call/4

happens to me all day every day :rofl:

glad I could help, have fun with your project!