В Gossip GenServer процессы умирают до выхода из состояния

Я создаю множество GenServers сплетен, отправляя сообщения друг другу. Я установил условие выхода, чтобы каждый процесс умирал после получения 10 сообщений. Каждый GenServer создается в начале слухов в функции launch.

defmodule Gossip do
    use GenServer

    # starting gossip
    def start_link(watcher \\ nil), do: GenServer.start_link(__MODULE__, watcher)
    def init(watcher), do: {:ok, {[],0,watcher}}
    def launch(n, watcher \\ nil) do
        crew = (for _ <- 0..n, do: elem(Gossip.start_link(watcher),1))
        Enum.map(crew, &(add_crew(&1,crew--[&1])))
        crew
            |> hd()
            |> Gossip.send_msg()
    end 


    # client side
    def add_crew(pid, crew), do: GenServer.cast(pid, {:add_crew, crew})
    def rcv_msg(pid, msg \\ ""), do: GenServer.cast(pid, {:rcv_msg, msg})
    def send_msg(pid, msg \\ ""), do: GenServer.cast(pid, {:send_msg, msg})


    # server side  
    def handle_cast({:add_crew, crew}, {_, msg_counter, watcher}), do:
        {:noreply, {crew, msg_counter, watcher}}

    def handle_cast({:rcv_msg, _msg}, {crew, msg_counter, watcher}) do
        if msg_counter < 10 do
            send_msg(self())
        else
            GossipWatcher.increase(watcher)
            IO.inspect(self(), label: "exit of:") |> Process.exit(:normal)
        end
        {:noreply, {crew, msg_counter+1, watcher}}
    end

    def handle_cast({:send_msg,_},{[],_,_}), do: Process.exit(self(),"crew empty")
    def handle_cast({:send_msg, _msg}, {crew, msg_counter, watcher}=state) do
        rcpt = Enum.random(crew) ## recipient of the msg
        if Process.alive?(rcpt) do
            IO.inspect({self(),rcpt}, label: "send message from/to")
            rcv_msg(rcpt, "ChitChat")
            send_msg(self())
            {:noreply, state}
        else
        IO.inspect(rcpt, label: "recipient is dead:")
        {:noreply, {crew -- [rcpt], msg_counter, watcher}}
        end
    end
end


defmodule GossipWatcher do
    use GenServer

    def start_link(opt \\ []), do: GenServer.start_link(__MODULE__, opt)
    def init(opt), do: {:ok, {0}}
    def increase(pid), do: GenServer.cast(pid, {:increase})  
    def handle_cast({:increase}, {counter}), do:
        IO.inspect({:noreply, {counter+1}}, label: "toll of dead")

end

Я использую модуль GossipWatcher для отслеживания количества GenServer, кто умирает после получения 10 сообщений. Проблема в том, что отображается подсказка iex, хотя есть еще GenServers живые. Например, более 1000 GenServer, только ~ 964 GenServers умирают в конце сплетни.

iex(15)> {:ok, watcher} = GossipWatcher.start_link
{:ok, #PID<0.11163.0>}
iex(16)> Gossip.launch 100, watcher            
send message from/to: {#PID<0.11165.0>, #PID<0.11246.0>}
:ok     
send message from/to: {#PID<0.11165.0>, #PID<0.11167.0>}
send message from/to: {#PID<0.11246.0>, #PID<0.11182.0>}
send message from/to: {#PID<0.11165.0>, #PID<0.11217.0>}
...
toll of dead: {:noreply, {960}}
toll of dead: {:noreply, {961}}
toll of dead: {:noreply, {962}}
toll of dead: {:noreply, {963}}
toll of dead: {:noreply, {964}}
iex(17)>

Я что-то упустил? Время ожидания процесса истекло? Любая помощь будет оценена по достоинству
TIA.


person The_Lost_Avatar    schedule 01.10.2018    source источник
comment
@NathanRipert Вот и все, paste.openstack.org/show/731176. Спасибо за внимание Это.   -  person The_Lost_Avatar    schedule 01.10.2018
comment
Он не выходит, но я возвращаю свою командную строку. Я хочу, чтобы процесс продолжал отправлять сообщения, пока не будет достигнуто значение счетчика. Я мог бы ошибаться, полагая, что возврат командной строки завершается.   -  person The_Lost_Avatar    schedule 01.10.2018
comment
Пожалуйста, не стесняйтесь делать это. Отличная работа @NathanRipert по устранению этой проблемы.   -  person The_Lost_Avatar    schedule 03.10.2018
comment
Я считаю редактирование хорошим. Я стараюсь задавать как можно более общие вопросы, но я думаю, что редактирование было необходимо здесь :)   -  person The_Lost_Avatar    schedule 03.10.2018


Ответы (1)


Часть вашего кода, которая может сыграть некоторые трюки, находится здесь:

def handle_cast({:send_periodic_message}, zero_counter_gossip_true) do

    ...

    if (Process.alive?(rcpt)) == true do

    ...

    else
        IO.inspect(rcpt, label: "recipient is dead:")
        {:noreply, {crew -- [rcpt], msg_counter, watcher}}
    end
end

В этой части else вы позволяете GenServer перестать работать: поскольку он не отправляет сообщение соседу или себе, никакие «действия» не запускаются, и он просто перестает что-то делать.
В худшем и маловероятном возможном случае: если вы запустите 2000 GenServer и начнете сплетничать с одного GenServer, и что этот первый разговаривает только со вторым, который также разговаривает только с первым .... тогда только один GenServer будет умрет, и вы вернетесь в командную строку, где еще 1999 GenServer жив, но ничего не делает (поскольку они получают 0 сообщений).

Даже если этот случай надуманный, он показывает, что исполнение сплетен может закончиться преждевременно до того, как каждый GenServer получит 10 сообщений. Следовательно поведение, которое вы описываете.


Я провел несколько тестов, переписал ваш код и использовал второй тип GenServer, чтобы отслеживать, как много GenServers убито, а сколько выживает. Оказывается, что из 1000 GenServers я получаю в среднем 40 GenServer, которые еще живы после того, как я получил приглашение iex.

person Nathan Ripert    schedule 02.10.2018
comment
С вашей стороны было здорово подумать обо всем этом. На самом деле я столкнулся с той же проблемой, 960/1000 погибших, а может быть, когда-то меньше, а может быть, иногда и больше. - person The_Lost_Avatar; 03.10.2018