ChatRoom in Elixir using Mutex and Threads

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

Thanks in Advice :slight_smile:

Hello and welcome,

Do You really need mutex in Elixir?

You might be interested by this YT video

by @geo

3 Likes

What was your education source that claimed you need mutexes in Elixir? :thinking:

You don’t need them here.

4 Likes

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

2 Likes

Thanks. However, I do not see how can I do that. So far I’m trying to store the messages in a structure but I´m having problems with that

Well, It was a recommendation, however I’m open to hear more, I have tried several things and it does not work

Sounds like you need a GenServer to manage your state. And since it’s based on a single BEAM process, there’s no need for synchronization.

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?

Do you know of GenServer?

1 Like

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

What do you mean? How do you know whether it is or isn’t working? How are you running this code?

@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.

1 Like

That is so helpful Thanks

Why on earth do you need a mutex?

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

:wave: 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.

Anyway, if you’re interested, check out https://BuildItWithPhoenix.com.

1 Like