Phoenix.Tracker handle_diff unable to call Phoenix.Tracker.List

For some reason i’m getting this error:

[error] GenServer KioskTracker_shard10 terminating
** (stop) exited in: GenServer.call(KioskTracker_shard10, {:list, “room:kiosk”}, 5000)
** (EXIT) process attempted to call itself

It’s erroring out on the Phoenix.Tracker.list call.
What is the proper way to access the list of tracked connections within the handle_diff callback function of a Phoenix Tracker?

Thanks!

  def handle_diff(diff, state) do
    kiosks = Phoenix.Tracker.list(__MODULE__, "room:kiosk")
    IO.puts("KIOSKS CONNECTED")
        IO.inspect(kiosks)
    for {topic, {joins, leaves}} <- diff do
      for {key, meta} <- joins do
        IO.puts("presence join: key \"#{key}\" with meta #{inspect(meta)}")
        # This stuff might be for distributed system with more than one node, look into it later
        # msg = {:join, key, meta}
        # Phoenix.PubSub.direct_broadcast!(state.node_name, state.pubsub_server, topic, msg)
      end

      for {key, meta} <- leaves do
        
        
        IO.puts("presence leave: key \"#{key}\" with meta #{inspect(meta)}")
        # This stuff might be for distributed system with more than one node, look into it later
        # msg = {:leave, key, meta}
        # Phoenix.PubSub.direct_broadcast!(state.node_name, state.pubsub_server, topic, msg)
      end
    end

    {:ok, state}
  end

Blockquote

This might be an XY problem. What are you looking to achieve?

Without knowing much about your use case, you could spawn the with that needs the list of kiosks. However, you can’t do that if you need it for the return of the handle diff (I have a hard time envisioning a use case for that)

I think you are right, this may be an XY problem and I might just be banging my head with very limited knowledge in Elixir =D

The problem i’m trying to solve is to shut off the ability to place an order on a mobile app if the separate web kiosk is not open.

The way i’m trying to implement it is in the handle_diff callback, whoever ‘leaves’ the channel will have their mobile app ordering shut off via a feature flag platform (LaunchDarkly).

However, there may be two or three different kiosks up at the same time for the same tenant, and wanted it to shut off if only ALL of them were off. So i was going to retrieve the existing list or presence of connections and check if there are no other kiosks of the same tenant on the list.

Is handle_diff the proper place to write all this logic or should I put it in a different module?

Thanks!

Hmm… I’m trying to implement spawning another process and calling the Track function inside of it but I get this error:

[error] GenServer KioskTracker_shard10 terminating
** (Protocol.UndefinedError) protocol String.Chars not implemented for {:"$gen_call", {#PID<0.630.0>, #Reference<0.3898897701.2923167750.135453>}, {:list, “room:kiosk”}} of type Tuple. This protocol is implemented for the following type(s): Postgrex.Copy, Postgrex.Query, Decimal, Float, DateTime, Time, List, Version.Requirement, Atom, Integer, Version, Date, BitString, NaiveDateTime, URI
(elixir 1.10.2) lib/string/chars.ex:3: String.Chars.impl_for!/1
(elixir 1.10.2) lib/string/chars.ex:22: String.Chars.to_string/1
(savor_kiosk 0.1.0) lib/savor_kiosk_web/channels/kiosk_tracker.ex:60: KioskTracker.handle_diff/2
(phoenix_pubsub 1.1.2) lib/phoenix/tracker/shard.ex:485: Phoenix.Tracker.Shard.report_diff_join/5
(phoenix_pubsub 1.1.2) lib/phoenix/tracker/shard.ex:294: Phoenix.Tracker.Shard.put_presence/6
(phoenix_pubsub 1.1.2) lib/phoenix/tracker/shard.ex:228: Phoenix.Tracker.Shard.handle_call/3
(stdlib 3.9) gen_server.erl:661: :gen_server.try_handle_call/4
(stdlib 3.9) gen_server.erl:690: :gen_server.handle_msg/6
(stdlib 3.9) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Is it saying the second item in the tuple can’t be a string?
If I do Phoenix.Tracker.list(KioskTracker, “room:kiosk”) In iex it works, but that exact function doesn’t work in the spawning function.

Thanks again for the help while i’m diving straight into this world!

def handle_diff(diff, state) do
    
    IO.inspect(diff)

    parent = self()
    
    spawn(fn() ->
      IO.puts("SPAWNING****")
      kiosks = Phoenix.Tracker.list(KioskTracker, "room:kiosk")
      IO.puts("KIOSKS CONNECTED")
      IO.inspect(kiosks)
      send(parent, 'ohai')
    end)

    receive do
      random -> IO.puts("Received #{random}")
    end


    for {topic, {joins, leaves}} <- diff do
      for {key, meta} <- joins do
        IO.puts("presence join: key \"#{key}\" with meta #{inspect(meta)}")
        # This stuff might be for distributed system with more than one node, look into it later
        # msg = {:join, key, meta}
        # Phoenix.PubSub.direct_broadcast!(state.node_name, state.pubsub_server, topic, msg)
      end

      for {key, meta} <- leaves do
        IO.puts("presence leave: key \"#{key}\" with meta #{inspect(meta)}")
        # This stuff might be for distributed system with more than one node, look into it later
        # msg = {:leave, key, meta}
        # Phoenix.PubSub.direct_broadcast!(state.node_name, state.pubsub_server, topic, msg)
      end
    end

    {:ok, state}
  end
end

I’m pretty sure that’s from the receive. The line number will tell you exactly

Regardless, you can’t block the receive like that. When I recommended a spawn, I was talking purely about for side effect code. If you want the tracker result as part of processing the diff, then I think you’re blocked.

What are you trying to do? I’m almost certain that you shouldn’t go down this path.

I’ll take your advice and rethink the solution. I may need help with it as my knowledge is very limited.

Within the receive, or once I somehow have the list of connected kiosks, i wanted to make sure there aren’t any identical kiosks in the list to the one that just disconnected. This is solving for the case that there are two or more kiosks up that belong to the same tenant.

Only when all kiosks of the a specific tenant has disconnected, only then do I want to fire off instructions that turn off mobile ordering functionality.

So in the handle_diff i need two pieces of information:

  1. The entity that disconnected
  2. The list of connected kiosks
    So that I can check the list to see if there are any other kiosks out there belonging to the same tenant. If there aren’t, then i’ll fire off the instructions to shut off mobile ordering. If there is then do nothing.

Would the alternate solution be to send a message from the handle_diff to another process to handle all the logic?

Again, thanks!

Yup you’re right, it errors out in the receive.

Ah! I’ve uncommented out PhoenixPubSub.direct_broadcast! functions and an now trying to do the logic in the Channels. Going to play around with that and think this may be a better direction!

The one thing to consider with handle_diff is that it will execute on every node in the cluster for a given change. This means that if there’s some logic you want to do once, it will actually run N times.

The Channel approach may work here (especially if intercepting handle_diff). You can execute code to check the kiosks and shut down in that case. I normally recommend Tracker, but Presence would actually fit pretty well if you’re intercepting handle_diff. It’s just tracker + broadcasting the diff to that topic.

Awesome, thanks for guidance!

How did you end up solving this issue? Im getting the same error when trying to call Phoenix.Tracker.list(tracker_name, topic) from either inside my handle_diff() callback or another module.

I thought of intercepting presence_diff in my channel, and each time there was a leave I could make a call to Phoenix.Tracker.list(). However Im finding that while intercepting prescence_diff it doesn’t seem to fire when the last person subscribed to a topic leaves (I assume this is because the last person leaving kills the presence for that topic)