Hi! I’ve been studying concurrency models with elixir. Now, I want to develop a ChatRoom, where users can Join, post messages, and disconnect themselves using threads and mutex in elixir. So far I have developed the model for entering new users and disconnect them. However, I have put some “prints” to see how its working, but they are not showing when I run the program. This is the code,
defmodule MyMutex do
@mutex :free
def lock(:free) do
{:ok, :locked}
end
def lock(:locked) do
{:error, :already_locked}
end
def unlock(:locked) do
:ok
end
def unlock(:free) do
{:error, :not_locked}
end
end
defmodule Chat do
defstruct users: [], messages: [], mutex: MyMutex
def start_link() do
{:ok, pid} = Task.start_link(fn -> action(%__MODULE__{}) end)
end
defp action(chat) do
receive do
{:join, :user} ->
chat = new_user(chat, :user)
action(chat)
#{:post, user} ->
# chat = post_message(chat, user)
# action(chat)
{:disconnect, user} ->
chat = disconnect_user(chat, user)
action(chat)
{:users_chat} ->
post_all_users(chat)
action(chat)
end
end
defp post_all_users(chat) do
Enum.each(chat.users, fn user ->
"User:"
end)
end
defp post_message_to_all(chat, message) do
Enum.each(chat.users, fn user ->
IO.puts("Message from server: #{message} (Sent to: #{user})")
end)
end
defp new_user(chat ,user) do
{:ok, mutex} = MyMutex.lock(chat.mutex)
updated_chat = %{ chat | users: chat.users ++ [user], mutex: mutex }
IO.puts("Entering the chat: #{user}")
MyMutex.unlock(mutex)
post_message_to_all(updated_chat, "#{user} has joined the chat.")
updated_chat
end
defp disconnect_user(chat, user) do
{:ok, mutex} = MyMutex.lock(chat.mutex)
chat = %{chat | users: List.delete(chat.users, user),
mutex: mutex}
IO.puts("The user #{user} has leaved the chat")
MyMutex.lock(mutex)
chat
end
end
I have to agree with other posters regarding mutex. General usecase for a mutex is to lock a shared resource when multiple threads are writing to it. In erlang we have processes which do not share resources. If you wish to share something (for example the chat messages), you can create a new process which holds this information and read/write to it by sending messages to that process. Since the process will only action one message at a time, no mutex will be required
Ok, let’s just go with the mutex idea for this, but with the understanding that you generally don’t need them in Elixir. It can be a little frustrating to have someone tell you “don’t do x” because sometimes doing it wrong first helps us learn to do it right.
You’ve started with a task, which is a process. So you’re moving in the right direction. Having that receive loop inside the task will allow you to manage state. In general, we wouldn’t use a task for this - either a process with Process.spawn/1 or a GenServer when it gets more complex, but we can accomplish what you want inside the task.
Can you tell us what you’re trying and what you expect to happen? I notice a few issues in your new_user/2 and disconnect_user/2 where you’re throwing away the mutex values. What functions are you calling and what is supposed to print?
I want to print the entering messages and users that enters the chat, and I’m not calling any functions inside those that you mention I’m just changing the values of the struct
@affq19 I made some revisions to your code to get it to work. There are commit messages that you can look at to understand why the changes were made. I’ve also added a simple test module to test it. It can be invoked from iex like this:
c("chat.ex")
Test.run()
As has been discussed, the mutex really isn’t necessary here and implementing this using either Process.spawn/1,3 or as a GenServer is the correct way to do it. You should be able to safely remove the mutex code without it affecting anything and it might be a good first step to make the code more Elixirish.
At the moment of testing I found that whan I’m trying to access the messages of two users at the same time, the program does not return the messages in order, I mean for example first the ones of the first user and then the ones of the second user, it starts to mix the messages
I was brought here by the mention of @kokolegorille. It looks like you’ve received some great direction here already!
However, I wouldn’t be a good marketer if I didn’t mention that I have a course that goes into detail building a chat room system from scratch using LiveView. And initially we do it without a database—building a GenServer to handle messages in-memory as a stepping stone.