Активно удалять пользователя из канала чата в эликсире

Я новичок в Elixir и работаю над простым чат-сервером с несколькими каналами.

Мой канал - это GenServer, и он хранит список пользователей (pid), а также карту (user ref -> pid) для обработки сбоев пользовательского процесса (из Руководства по эликсирам). Но поскольку это карта с ссылками в качестве ключей, когда я реализовал функцию remove_member_from_channel, я хотел удалить pid (значение карты), как я могу это сделать? Или я неправильно выбрал способ реализовать?

defmodule Chat.Channel do
  use GenServer

  # ......

  def add_member(channel, user) do
    GenServer.call(channel, {:add_member, user})
  end

  def remove_member(channel, user) do
    GenServer.call(channel, {:remove_member, user})
  end

  # ......

  def handle_call({:add_member, user}, _from, {members, refs}) do
    if Enum.member?(members, user) do
      {:reply, :already_added, {members, refs}}
    else
      new_members = [user | members]
      ref = Process.monitor(user)
      new_refs = Map.put(refs, ref, user)
      {:reply, :ok, {new_members, new_refs}}
    end
  end

  def handle_call({:remove_member, member}, _from, {members, refs}) do
    if Enum.member?(members, member) do
      new_members = List.delete(members, member)

      # What to do with refs??

      {:reply, :ok, {new_members, refs}}
    else
      {:reply, :member_not_added, {members, refs}}
    end
  end

  def handle_info({:DOWN, ref, :process, _pid, _reason}, {members, refs}) do
    {member, new_refs} = Map.pop(refs, ref)
    new_members = List.delete(members, member)
    {:noreply, {new_members, new_refs}}
  end
end
0
0
124
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Используйте Enum.filter/2 (или Enum.reject/2:

def handle_call({:remove_member, member}, _from, {members, refs}) do
  if Enum.member?(members, member) do
    new_members = List.delete(members, member)
    new_refs =
      Enum.filter(refs, fn
        {_ref, ^member} -> false
        _ -> true
      end)
    {:reply, :ok, {new_members, refs}}
  else
    {:reply, :member_not_added, {members, refs}}
  end
end

В общем, лучше мне сохранить карту used_id ⇒ reference, примерно так:

new_refs = Map.put(refs, user, ref) # or even better used.id

таким образом, позже можно было бы просто использовать Map.delete/2:

...
  new_members = List.delete(members, member)
  new_refs = Map.delete(refs, member) # or member.id

Означает ли это, что, если мы сохраняем его как user_pid = reference, когда пользовательский процесс дает сбой, мы должны использовать Enum.filter/2 или Enum.reject/2 для его удаления? Поскольку мы можем получить ссылку только при сбое.

Peter Ren 11.04.2018 14:20

Ухох, действительно, правда. Что ж, тогда можно было бы поддерживать карты обаuser ⇒ ref и ref ⇒ user.

Aleksei Matiushkin 11.04.2018 14:22

Другие вопросы по теме