Continuous server to client messaging over channels. Ideas?

I am building a market scanner as a practice app and require a message to be sent to the client every 5 seconds with results from the scanner. I have chosen to do this over channels but have hit a snag. I am unsure with which is the best way to continuously send these messages. The recursive loop I created continues inevitably (until client disconnects) but therefore never returns the socket to Phoenix limiting my ability to manipulate it for other changes (ie: setting changes, etc…). See below for code:

defmodule MyApp.ScannerChannel do

def join...

def handle_in("alert", _args, socket) do
    IO.puts("Starting Alert Loop...")

    alert_loop(socket)

    {:noreply, socket}
end

def alert_loop(%{assigns: %{user_pid: user_pid}} = socket) do
    response_map =
      MyApp.delta_alert?(socket.assigns[:user_pid])
      |> build_trades_map(%{})
      |> alert_checker(socket)

    updated_socket = assign(socket, :trades_map, response_map)

    push(updated_socket, "alert", response_map)
    
    Process.sleep(5_000)
    alert_loop(updated_socket)
  end
def alert_loop(_socket_no_user_pid), do: nil

This approach limits me significantly and won’t work once I require more interaction with my socket.

Some other options I have thought of are as follows:

  1. Send a message from the client requesting the data every 5 seconds. Issue: For multiple reasons, I would prefer to do it on the backend totally if possible. This is my last resort.

  2. Process.send_after - Request the data through a delayed message. Issue: Same as my current solution. The socket would be continuously passed back and forth and therefore not be updated.

  3. Broadcast/3 - Could just loop a broadcast. Issue: Each user has individual scanner settings and so this wouldn’t work as it would just send out results calculated using default settings.

Does anyone have any other ideas for how I could send a message repeatedly on a set interval?

Thanks.


I have no idea if this lib can be used for such short intervals, or can be extended.

Process.send_after + implementing the handle_info callback in your channel should work fine, I’m not sure what you mean by “socket would be continuously passed back and forth”?

1 Like

I would require the client socket to be passed to the GenServer and then used to make the handle_in call again ensuring the alert_work is sent to the correct client. Therefore the socket would just be passed back and forth. This is an issue because if I need to update the assigns for another task it would never get updated here. See below:

## In Channel ##

def handle_in("alert", _args, socket) do
    IO.puts("Starting Alert Loop...")
    
    alert work...
    push(socket, "alert", alert_work)
    Process.send_after(GenServer, {:alert_loop, socket}, 5000)
    {:noreply, socket}
end

## In GenServer ##

def handle_info({:alert_loop, socket}, state) do
    MyApp.ScannerChannel.handle_in("alert", _args, socket)
    {:noreply, settings}
end

Let me know if I am missing something because I do like this solution.

Thanks, I’ll check it out.

You don’t need a GenServer, just send the message to self() and implement handle_info directly in the channel module. There’s an example of handle_info in the doc: https://hexdocs.pm/phoenix/Phoenix.Channel.html#reply/2

The message would not include the socket, handle_info’s second argument already provides you the socket.

3 Likes

Cool, I wasn’t aware of this. I appreciate your help :beers: