This is an Elixir question, not a Phoenix question. I am not interested in any PubSub or Channels solution, but rather how to do this with basic Elixir.
If you have a user who has established a WebSock connection as shown here in the EchoServer example or as shown in the MyAppWeb.ConnectionTimer example, how can you utilize this websocket to send them a message from a distinct Elixir process/service?
Both the conn
and the WebSock
behavior are not processes. They have no process ID. Thus they have no capacity to receive messages. They are just looping on state
in reaction to user triggered handle_in
stimulus.
1) ELIXIR SERVICE TO CLIENT MESSAGING (EASY)
One can envision a way the conn
/Websock
could be made to send out messages into Elixir in response to incoming triggers easily enough:
- Create a Genserver on each new connection made and set the PID for this into the state of
conn
/WebSock
on its initialization. - When a specific
handle_in
request is received, you can useProcess.send
to send that GenServer a message which it will then receive it underhandle_info
Thus we can easily allow client → separate Elixir process system communication through the WebSock.
2) ELIXIR SERVICE TO CLIENT MESSAGING (UNCLEAR?)
But what about separate Elixir process → client WebSock communication? Where such communication is not triggered by the user’s initiation, but rather our system’s?
Let’s say for whatever reason that GenServer wants to send the client a message through their websock. How can it do so? How can any process do so?
My current guess based on the fact that in this example he is running:
defp schedule_alert, do: Process.send_after(self(), :alert, :timer.seconds(15))
This suggests to me each running conn
/WebSock
while not a process itself is being run by and is associated with a parent process (direct Supervisor?) that is uniquely associated with it (one-to-one). So somewhere here there is a possible solution.
EXPERIMENT
I just tested debugging out the PID of the main supervisor that starts my router in Application.ex and also debugging out self()
in the router itself under get /
for simple localhost:port/
accesses. On every reload, a new PID is shown there, which is not the PID of the main application supervisor that started the router.
This suggests a new process is initiated on every new web request to the router. But what is this process, then, and how do we control it? Presumably it has the conn
/Websock
within its own state (or the conn
/WebSock
functions are augmenting or overriding its base functions) when the connection is upgraded so if we can control that or override its functions we can perhaps accomplish the goal?
Simpler: If we assume all conn/WebSock’s have their own unique one-to-one Supervisor PID running them given by self()
within the WebSock code (which seems to be true), then we can message that PID just like in this timer websock to trigger the handle_info
of the WebSock. Then based on the handle_info
manually run inside that handle_in
which has the capacity to message the user directly through the websock.
I am thinking that is the solution now as I work it through. I will try that next.
GOAL
I am looking for the basic Elixir solution to this. To pre-empt, yes I have tried to solve this already but can’t. I have four books on Elixir sitting on the couch next to me but none explain this. ChatGPT can’t seem to answer without telling me about Phoenix (which I’m not interested in and is not magic in any case and must follow the same laws as base Elixir).
There is obviously some way to do this that systems like Phoenix are utilizing. That’s what I want to understand.
Thanks for any help.