Print message in terminal of connected nodes inside handle_call

Hello everyone,
I’m trying to make a simple app to learn elixir. Users can join clubs and for now post messages in the club.
When I do Strovo.Club.send_message ClubA, "some message" on node1 messages gets updated correctly and I can see the message if I do Strovo.Club.get_state ClubA on node2.
But I want to also show the message in the terminal of all nodes that have joined the club when I call the send_message function.

Relevant code so far:

@enfore_keys[:club]
defstruct members: [], messages: [], club: nil

def send_msg(club_name, msg), do: GenServer.call(via_tuple(club_name), {:send_msg, self(), Node.self(), msg})

@impl true
def handle_call({:send_msg, pid, node, msg}, _, %@me{members: ms} = state) do
    case Enum.find(ms, fn {ms_pid, node_pid, _} -> ms_pid == pid and node_pid == node end) do
        {_, _, sender} ->
            new_state = %{state | messages: [create_msg_entry(sender, msg) | state.messages]}
            #Enum.each(ms, fn {pid, _, _} -> print msg for every member of the club
            {:reply, :ok, new_state}

        nil ->
            {:reply, {:error, :not_a_participating_member}, state}
    end
end

# HELPER FUNCTIONS

defp via_tuple(club_name), do: {:via, :global, {:club, club_name}}

defp create_msg_entry(sender, msg) do
    timestamp = DateTime.utc_now()
    {timestamp, sender, msg}
end

I’m stuck on how to actually print the message in the terminals of all connected nodes. Anyone has any pointers on how I can do this?

I’ve tried Process.send(pid, {:print_msg, msg}) and GenServer.call(pid, {:print_msg, msg}) but I kept getting errors.

Try Logger. Just add Logger to your module like:

require Logger

def handle_call({:send_msg, pid, node, msg}, _, %@me{members: ms} = state) do
     ...
    Logger.info("Some  message: #{inspect(state)}")
    ...
end

What errors? As readers, we can’t try this code out ourselves so you need to tell us what you saw.

These are 2 of my attempts and their errors

    @impl true
    def handle_call({:send_msg, pid, node, msg}, _, %@me{members: ms} = state) do
        case Enum.find(ms, fn {ms_pid, node_pid, _} -> ms_pid == pid and node_pid == node end) do
            {_, _, sender} ->
                new_state = %{state | messages: [create_msg_entry(sender, msg) | state.messages]}
                #Enum.each(ms, fn {pid, _, _} -> print msg for every member of the club
                {:reply, :ok, new_state, {:continue, {:get_messages, msg, ms}}}

            nil ->
                {:reply, {:error, :not_a_participating_member}, state}
        end
    end

    @impl true
    def handle_continue({:get_messages, msg, ms}, state) do
        Enum.each(ms, fn {pid, node, _} -> GenServer.call(pid, {:print_msg, msg}) end)
        {:noreply, state}
    end

    def handle_call({:print_msg, msg}, state) do
        {:reply, {:ok, msg}, state}
    end


17:13:53.485 [error] GenServer {:club, A} terminating
** (stop) exited in: GenServer.call(#PID<0.175.0>, {:print_msg, "een"}, 5000)
    ** (EXIT) time out
    (elixir 1.13.4) lib/gen_server.ex:1030: GenServer.call/3
    (elixir 1.13.4) lib/enum.ex:937: Enum."-each/2-lists^foreach/1-0-"/2
    (strovo 0.1.0) lib/strovo/club.ex:67: Strovo.Club.handle_continue/2
    (stdlib 4.0.1) gen_server.erl:1120: :gen_server.try_dispatch/4
    (stdlib 4.0.1) gen_server.erl:862: :gen_server.loop/7
    (stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: {:continue, {:get_messages, "een", [{#PID<0.175.0>, :"a@LAPTOP", 
"a"}]}}
State: %Strovo.Club{club: A, members: [{#PID<0.175.0>, :"a@LAPTOP", "a"}], messages: [{~U[2022-12-19 16:13:48.425400Z], "a", "een"}]}
** (EXIT from #PID<0.175.0>) shell process exited with reason: exited in: GenServer.call(#PID<0.175.0>, {:print_msg, "een"}, 5000)
    ** (EXIT) time out
@impl true
def handle_call({:send_msg, pid, node, msg}, _, %@me{members: ms} = state) do
    case Enum.find(ms, fn {ms_pid, node_pid, _} -> ms_pid == pid and node_pid == node end) do
        {_, _, sender} ->
            new_state = %{state | messages: [create_msg_entry(sender, msg) | state.messages]}
            Enum.each(ms, fn {pid, _, _} -> GenServer.call(pid, {:print_msg, msg}) end)
            {:reply, :ok, new_state}

        nil ->
            {:reply, {:error, :not_a_participating_member}, state}
    end
end

def handle_call({:print_msg, msg}, _, state) do
    {:reply, {:ok, msg}, state}
end


** (exit) exited in: GenServer.call({:via, :global, {:club, A}}, {:send_msg, #PID<0.175.0>, :"a@LAPTOP", "een"}, 5000)
    ** (EXIT) time out
    (elixir 1.13.4) lib/gen_server.ex:1030: GenServer.call/3
iex(a@LAPTOP)3> 
17:19:47.156 [error] GenServer {:club, A} terminating
** (stop) exited in: GenServer.call(#PID<0.175.0>, {:print_msg, "een"}, 5000)
    ** (EXIT) time out
    (elixir 1.13.4) lib/gen_server.ex:1030: GenServer.call/3
    (elixir 1.13.4) lib/enum.ex:937: Enum."-each/2-lists^foreach/1-0-"/2
    (strovo 0.1.0) lib/strovo/club.ex:56: Strovo.Club.handle_call/3
    (stdlib 4.0.1) gen_server.erl:1146: :gen_server.try_handle_call/4
    (stdlib 4.0.1) gen_server.erl:1175: :gen_server.handle_msg/6
    (stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message (from #PID<0.175.0>): {:send_msg, #PID<0.175.0>, :"a@LAPTOP", "een"}
State: %Strovo.Club{club: A, members: [{#PID<0.175.0>, :"a@LAPTOP", "a"}], messages: []}
Client #PID<0.175.0> is alive

    (iex 1.13.4) lib/iex/evaluator.ex:166: IEx.Evaluator.loop/1
    (iex 1.13.4) lib/iex/evaluator.ex:32: IEx.Evaluator.init/4
    (stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
** (EXIT from #PID<0.175.0>) shell process exited with reason: exited in: GenServer.call(#PID<0.175.0>, {:print_msg, "een"}, 5000)
    ** (EXIT) time out

I’ve tried Logger, and if I place it in the handle_call I get the message, but only in the terminal in which I call the function.

A frequent cause of timeouts in GenServer.call (especially when it’s called inside a handle_* callback) is deadlock: process A is waiting for process B to reply while process B is waiting for process A to reply.

For instance, in your second trace:

The first line tells us that send_msg was called from 0.175.0.

The second line tells us that the recipient of that message is attempting to call to 0.175.0 while still handling that message.

Aha, I guess that’s why I don’t get those errors with GenServer.cast then.
I’ve also found that when I do iex(b@laptop)>flush() after I’ve called the function send_msg on a@laptop (when using GenServer.cast) that I get {:"$gen_cast", {:print_msg, "message"}}.
Is there a way to do that automatically?