A channel == a process

Hi all
When I create a channel like:

channel "videos:*", Rumbl.VideoChannel

It will start a process for this channel? If yes, then only one process will handle all incoming events?
Thanks

3 Likes

I think there’s a minimum of 2 processes for any given channel. There will be one PubSub process, and one process for each client/user.

2 Likes

When you create a channel like that it creates no processes.

However let me go through the whole process as I understand it (from debugging and reading a good bit of the code).

The client websocket/longpolling connects to the ‘Socket’ that you made, that spawns off a new process, thus (1) process so far, for the open socket. That socket process then checks if it is allowed via your own code and such, do whatever you want, set assigns that will be given to all other topic processes too. That socket then checks the topic being connected to and spawns another new process for just that topic (and each new topic connected comes in the same socket process but then makes a new topic etc…). That topic process gets a copy of the socket assigns (this way you can do something like get the user from the database and store in an assigns for all topics to see). So at this point you have two processes, and one additional process for each extra topic.

8 Likes

Thanks

If I understood it correctly, then a topic like “user:*” will be spawned once for all users, each user/client has be connected over a persistent socket (which itself is a process, sure), but there will be no additional processes for each user:#{id} topic, right?

Nope, a process is spawned for each topic for each user. You can join more topics from inside a topic though, and ‘those’ will not create more processes, but anytime the javascript sides connects to a topic for every single user then a new process is created for that specific topic/connection by default. :slight_smile:

3 Likes

Hmm, thank you, so., this can potentially become expensive… I am wondering, how we could send a single message to an opened socket connection, on a single users topic but to an explicit single user. I mean simply like responding is happening…

I mean, you mentioned, that its possible to join an user to a topic without creating those processes.

So basically from JS channel = s.join('users') Inside users one would join to user:id but broadcasting messages wouldn’t reach the client side… because there is no channel with the topic user:id defined… :confused:

If you broadcast to a topic unique to a given user, say "user:123", only that user will receive the message. If you’re asking how to message to a single user 123’s devices, then you can use Phoenix Presence, but we need to know more info to know what your exact usecase is. Generally it’s just fine to broadcast to a private user topic since you usually want all tabs/devices the user has joined to receive the message.

4 Likes

Not too bad at all. Process’s are overall cheap.

You need to make at least one topic, as it is ‘that’ that can then join others without extra processes. You can broadcast just fine once the topic you made registers to them, like a socket joins "users" but that topic also internally joins "user:#{id}", then you could broadcast to "user:#{id}" and that "users" topic would get it just fine. :slight_smile:

2 Likes

Hi @chrismccord thank you for your attention. I’ll try to explain the use case:

10 users, each on a single device. All of them connect are connecting to the backend via WebSockets.
(For each of those is a socket process created, so far so good)

Now all of them join the room:lobby Another process is created, which holds the state of the channel and all its meta data.

Now each of them joins to the user:uuid channel… As I understood, this will spawn another 10 processes (one for each wildcard uuid)

So, basically, the main question is, how send a single message to a particular user, (I could store a map like uuid -> socket on some generic topic like users ?

1 Like

To a particular user? Just MyEndpoint.broadcast("user:uuid", ...) as normal. :slight_smile:
What about that does not work for you?

1 Like

Ok, this sounds very good, maybe you have some more info on this topic? :smile: I would like to check out some example, to get my head around it. I mean how to join inside the a join, and what do I have to handle inside and so, or is it simple like that?

def join("users", %{"id" => id}, socket) do
  join("users:" <> id, _, socket)
end

It’s at: https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#module-endpoint-api

To join you’d actually do:

def join("users", %{"id" => id}, socket) do
  MyEndpoint.subscribe("users:" <> id)
end

At which point this same "users" process will get messages for "users:"<>id, but it will not automatically forward them to the client, to do that you just listen for them (just like any other PubSub message, Phoenix Channels are just Phoenix.PubSub underneath) and then push them to the client however you want. :slight_smile:

3 Likes

I think you gave me a good starting point to dive deeper now. Thank you!

2 Likes

If you need anything else, just ask. :slight_smile:

Hmm, I think I understood how to subscribe to a virtual topic. And to listen on the incoming events / firing events, seams to be clear. But the last problem I have is to understand how to

… push them to the client however you want.

I would like to push the message to the client over the “users” channel, where the client joined initially to.

…I wish there would be a simple API for like Endpoint.send(socket, payload, topic) function, where the topic process could hold many sockets, but allow direct messaging to each separately. I mean it must work somehow, like handle_in -> reply to a single client, right?

Think of it this way:

  • A socket for a topic only exists in the process for that topic
  • To communicate between processes you broadcast messages between them via topics they are subscribed to
  • A socket can only be talked to by its own process.

Thus you just listen for the messages via normal handle_info or whatever it was for sockets, then just push the message back out to itself just like you do any other message. :slight_smile:

An Endpoint.send(...) makes no sense as you can already push(..., socket, ...) anyway, which is the same thing.

3 Likes

Oh Yeah, now I got it… push is what I was looking for all the time… many thanks. Really strange that this feature is only mentioned in in the interceptions and outgoing events section. This makes it much much clearer…

So, I could basically manage the sockets by myself somewhere in a custom GenServer or so, and broadcast some messages to all sockets, and some for specific users, just with push… you made my day (night actually) Thanks!

2 Likes

Easiest way for specific users is just to have them join a topic all on their own with their user ID or so, like you user:uuid above did, then just broadcast to it. :slight_smile:

Don’t use any custom genserver’s, not at all useful since you can do it all (and safer and faster) via Phoenix.PubSub. :slight_smile:

3 Likes

I’ll do it like you said for my MVP, but its good to know that those options exists and I’ll definitely play around with it when the time comes :wink:

1 Like