Идиоматический c способ справиться или предотвратить «состояние гонки» в каналах эликсира - PullRequest
0 голосов
/ 09 апреля 2020

Извините за тупой вопрос. Но я новичок ie в эликсире, пришедший из golang.

Я действительно люблю стиль эликсира и FP, я пытаюсь реализовать некоторые базовые функции c и у меня возникли проблемы с условиями гонки при подключении через веб-сокет , Например, в golang я разрешил это с помощью Mutex, но это не стиль FP.

Первоначальная проблема: несколько соединений WebSocket, ожидающих группу из 5. И затем случайным образом перетасовывают людей. В этом случае у меня были некоторые проблемы с «состоянием гонки». Одинаковые люди в разных группах и т. Д. c.

Какой правильный идиоматический c способ решить эту проблему в эликсире?

Спасибо за вашу помощь. И еще раз прошу прощения за глупый вопрос.

Я поделюсь своим уродливым фрагментом кода)

Например, два одновременных пользователя присоединяются к лобби-каналу, который я отслеживаю, они присоединились через присутствие. После этого, когда некоторые пользователи отправляют событие «матч», я нахожу других онлайн-пользователей в лобби, пока их не станет больше.

defp match(user_id, online_list) when length(online_list) < 1

Затем я создаю комнату и отправляю им room_id. Но когда два пользователя отправляют совпадение, у меня две комнаты из-за состояния гонки.

Я хочу иметь одну общую комнату для группы пользователей. В lang такие go, я мог бы использовать mutex и делиться. Но я не знаю, как реализовать эту логику c в эликсире.

Прямо сейчас, если Алиса вызывает событие, и у меня достаточно пользователя для Алисы, но Боб вызывает это событие одновременно, и он также было достаточно пользователей. Но они должны быть в общей группе, а не в двух отдельных. Они пересекаются

def match(user_id) do
    user_id = Integer.to_string(user_id)
    match(user_id, [])
  end

  defp match(user_id, online_list) when length(online_list) < 1 do
    new_online_list =
      RchatWeb.Presence.list("room:lobby")
      |> Map.delete(user_id)
      |> Map.keys()
    match(user_id, new_online_list)
  end

  defp match(first_user_id, online_list) do
    second_user_id = Enum.random(online_list) |> String.to_integer()
    second_room_id = Accounts.get_user_room_id(second_user_id)
    {:ok, room} = cond do
      is_nil(second_room_id) -> create_room()
      true -> {:ok, get_room!(second_room_id)}
    end
    # Try to fix race condition with presence or channels
    {:ok, first_user} =
      Accounts.get_user!(first_user_id)
      |> Accounts.update_user(%{room_id: room.id})
    {:ok, second_user} =
      Accounts.get_user!(second_user_id)
      |> Accounts.update_user(%{room_id: room.id})
    ids = [first_user.id, second_user.id]
    {:ok, room.hash, ids}
  end

1 Ответ

2 голосов
/ 09 апреля 2020

Эликсир не имеет изменяемого состояния, поэтому концепция локального мьютекса там бессмысленна. Распределенные блокировки могут иметь несколько вариантов использования, но это очень отличается от локальных мьютексов.

Помните, что каждый процесс всегда последовательный, поэтому, если я правильно понимаю ваш сценарий использования, вы захотите что-то вроде:

defmodule Queue do
  use GenServer

  def start_link(_), do: GenServer.start_link(__MODULE__, [])

  def register_and_wait(pid),
    do: GenServer.call(pid, {:register, self()}, :infinity)

  def init(_), do: {:ok, []}

  def handle_call({:register, pid}, from, participants) do
    new_state = [{pid, from} | participants]
    case length(new_state) do
      5 ->
        shuffled = Enum.shuffle(new_state)
        {pids, refs} = Enum.unzip(shuffled)

        for ref <- refs, do: GenServer.reply(ref, {:ready, pid})

        {:noreply, shuffled}

      n when n < 5 ->
        {:reply, {:wait, 5 - n}, new_state}

      _ ->
        {:reply, :overcrowded, participants}
    end
  end
end

Теперь register_and_wait/1 будет регистрировать текущий процесс и блокировать столько времени, сколько необходимо (остерегайтесь бесконечной блокировки) для столько процессов, сколько необходимо (в данном случае 5, но это можно сделать настраиваемым). Невозможно получить взаимоблокировку, если достаточно процессов для регистрации, и тогда будет возвращено {:ready, shuffled_list_of_pids}.

...