How do you set a hard limit to how many clients can be joined to a given topic at any one time?
Ex: I want to set a limit of 5 so that if there are 5 users joined to a topic and another one tires to join they are denied entrance.
How do you set a hard limit to how many clients can be joined to a given topic at any one time?
Ex: I want to set a limit of 5 so that if there are 5 users joined to a topic and another one tires to join they are denied entrance.
I’d probably have an ets table for counting how many users there are in a particular chat room, and consult it in every channel join. Note that this approach wouldn’t work (or rather would require some modifications) for multi-node setup.
I’m actually curious if this can be accomplished using the presence module, since that keeps track of the number of users in a given topic.
Since presence is built on CRDTs as far as I remember, it’s eventually consistent, so that allows for more users (> limit) to join the channel at certain points. But yeah, if Presence has an API suitable for it, you can do it.
Thanks, I’ll try to make it happen with presence. If it works well enough I’ll post the code in this thread.
You could have a look on list/2
and perhaps do:
defmodule Example.Presence do
use Phoenix.Presence,
otp_app: :example,
pubsub_server: Example.PubSub
alias Example.Socket
def track(socket) do
track(socket, Socket.id(socket), %{})
end
def count(topic) do
topic
|> list
|> map_size
end
end
Which uses the list/1
callback injected and defined when using Phoenix.Presence
. I’ve also included a track/1
function that could come in handy - that uses Phoenix’ track/3
callback.
I think I’m doing something similar.
This is what my code looks like currently.
def join("webrtc:" <> corner_id, %{"user_id" => user_id}, socket) do
if(Kernel.map_size(Presence.list(socket)) < 5) do
socket = assign(socket, :current_user_id, user_id)
send(self(), :after_join) # this is where the pressence is kept
{:ok, %{channel: "webrtc:#{user_id}"}, assign(socket, :user_id, user_id)}
else
{:error, %{reason: "room is full"}}
end
end
Update 1:
And it doesn’t work
Update 2:
This works actually but for some reason elixir interprets <
as <=
. I’m not sure why, there must be something I’m missing.
I’m curious, are you sure Presence.list(socket)
works as intended? I’d expect you to be doing Presence.list(topic)
instead.
What’s the difference?
Nevermind, the source code shows there’s no difference.
Why do we have two different functions that do the same thing?
Watcha mean? Presence.list(socket)
grabs the topic from the socket struct (you can see this from the source I linked above) so it’s basically the same as if you did Presence.list(topic)
. The docs don’t seem to mention this though, that’s why I was confused.
Aside from this, I’m not sure why you’re having that <
as <=
issue .
^ How are you testing this?
^ Could there be a race condition such that a second client is joining the channel before the first one is tracked?
So a total of 3 users entered the room?
Could it be that those users share the same socket id? What’s your call to Phoenix.track/3
like?
I’m not sure why but calling Presence.track(socket, Socket.id(socket), %{})
give’s me this error:
[error] an exception was raised:
** (UndefinedFunctionError) function ChatWeb.Socket.id/1 is undefined (module ChatWeb.Socket is not available)
ChatWeb.Socket.id(%Phoenix.Socket{assigns: %{}, channel: ChatWeb.WebrtcChannel, channel_pid: #PID<0.457.0>, endpoint: ChatWeb.Endpoint, handler: ChatWeb.UserSocket, id: nil, join_ref: "1", joined: false, private: %{log_handle_in: :debug, log_join: :info}, pubsub_server: Chat.PubSub, ref: nil, serializer: Phoenix.Transports.V2.WebSocketSerializer, topic: "webrtc:", transport: Phoenix.Transports.WebSocket, transport_name: :websocket, transport_pid: #PID<0.455.0>, vsn: "2.0.0"})
(chat) lib/chat_web/channels/webrtc_channel.ex:15: ChatWeb.WebrtcChannel.join/3
(phoenix) lib/phoenix/channel/server.ex:188: Phoenix.Channel.Server.init/1
(stdlib) gen_server.erl:374: :gen_server.init_it/2
(stdlib) gen_server.erl:342: :gen_server.init_it/6
(stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
If you are doing Presence.track(socket, id, meta)
with a same id
for two different socket connections, since Presence.list/1
returns a map whose keys are those IDs then I presume what’s happening is that one connection is overwriting the other in this map. Thus when you grab the size of the map, it doesn’t match the number of connected users because two share a same key.
You don’t need to call Socket.id/1
for this - although it indeed is a lot handier to call instead of having to match against the assigns on every function definition in order to grab the user id. Also, the error you have there shows that most likely that’s not what the socket module is named.
Anyhow, what’s the Presence.track/3
call you had prior?
I forgot to mention this prior but each of the connected users have a different user id. Since I’m using the user id of the individual users as the socket id.
socket = assign(socket, :current_user_id, user_id)
And each of the different users I’m using to test this have a different user id.
I see . Can you possibly debug/log both
user_id
and the map returned from Presence.list/1
for each of the joining clients and show us?
Here you go:
[info] JOIN "webrtc:" to ChatWeb.WebrtcChannel
Transport: Phoenix.Transports.WebSocket (2.0.0)
Serializer: Phoenix.Transports.V2.WebSocketSerializer
Parameters: %{"user_id" => "2"}
[info] CHECK
[info] 2
%{
"user:1" => %{
metas: [
%{
initiator: false,
phx_ref: "iBPiBd8fTm4=",
user_id: 1,
username: "admin"
}
]
},
"user:3" => %{
metas: [
%{initiator: false, phx_ref: "3zIVfMK1ZVs=", user_id: 3, username: "bob"}
]
}
}
[info] Replied webrtc: :ok