15 Jul 2013, 03:42
Dave_gnome_head_isolated_pragsmall

Dave Thomas (344 posts)

  • The ticker process in this chapter is a central server that sends events to registered clients. Reimplement this as a ring of clients. A client sends a tick to the next client in the ring. After 2 seconds, that next client sends a tick to its next client.

    When thinking about how to add clients to the ring, remember to deal with the case where a client’s receive loop times out just as you’re adding a new process. What does this say about who has to be responsible for updating the links?

10 Mar 2014, 21:32
Webcam_pragsmall

Andrea Bernardo Ciddio (2 posts)

This solution works, but requires 2 different functions - start and add_client - to generate the ring. Perhaps it can be improved to use only one.

defmodule RingTick do
  @interval 2000
  @name :ticker

  def start do
    spawn __MODULE__, :ticker, []
  end

  def add_client do
    pid = spawn __MODULE__, :receiver, []
    register pid
  end

  def ticker(next_client \\ self) do
    :global.register_name @name, self
    receive do
      { :register, new_client } ->
        IO.puts "registering #{inspect new_client}"
        send new_client, { :tick, next_client }
        receiver(new_client)
      after @interval ->
        IO.puts "tick"
        send next_client, { :tick }
        receiver(next_client)
    end
  end

  def receiver(next_client \\ self) do
    receive do
      { :tick, new_client } ->
        IO.puts "first tock in #{inspect self}, sending a tick to #{inspect new_client} in 2s"
        ticker(new_client)
      { :tick } ->
        IO.puts "tock in #{inspect self}, sending a tick to #{inspect next_client} in 2s"
        ticker(next_client)
    end
  end

  def register(client_pid) do
    send :global.whereis_name(@name), { :register, client_pid }
  end
  
end
01 May 2014, 14:41
Ernie2_pragsmall

Ernie Miller (4 posts)

I am almost shocked this actually worked. Tested up to 4 nodes. It’s a bit repetitive owing to my not seeing how to DRY it up yet since I just started with Elixir on Monday.

The general idea is that we always send the join request to the “ring” node – or the master. It forwards the join until such time as it gets to the final node, at which point the new member is added. The third parameter in each case is the “state” of whether or not the current node is the “ticker” at the moment.

defmodule Ring do

  @interval 2000

  @name :ring

  def start do
    pid = spawn(__MODULE__, :loop, [nil, true])
    :global.register_name(@name, pid)
  end

  def join do
    ring_pid = :global.whereis_name(@name)
    pid = spawn(__MODULE__, :loop, [ring_pid, nil, false])
    send ring_pid, { :join, pid }
  end

  def loop(ring_pid \\ self, nil, true) do
    receive do
      { :join, client_pid } ->
        loop(ring_pid, client_pid, true)
    after
      @interval ->
        IO.puts "tick -> ring"
        send ring_pid, { :tick }
        loop(ring_pid, nil, false)
    end
  end

  def loop(ring_pid, nil, false) do
    receive do
      { :join, client_pid } ->
        loop(ring_pid, client_pid, false)
      { :tick } ->
        IO.puts "tock"
        loop(ring_pid, nil, true)
    end
  end

  def loop(ring_pid, next_pid, true) do
    receive do
      { :join, client_pid } ->
        send next_pid, { :join, client_pid }
        loop(ring_pid, next_pid, true)
    after
      @interval ->
        IO.puts "tick -> next_pid"
        send next_pid, { :tick }
        loop(ring_pid, next_pid, false)
    end
  end

  def loop(ring_pid, next_pid, false) do
    receive do
      { :join, client_pid } ->
        send next_pid, { :join, client_pid }
        loop(ring_pid, next_pid, false)
      { :tick } ->
        IO.puts "tock"
        loop(ring_pid, next_pid, true)
    end
  end

end
17 Sep 2014, 13:41
Patrick_pragsmall

Patrick Oscity (13 posts)

Here’s my solution:

defmodule RingTicker do
  @interval 2000
  @master :ring_ticker_master

  def join do
    pid = spawn(__MODULE__, :wait, [])
    case :global.whereis_name(@master) do
      :undefined ->
        send pid, {:become_master, [pid]}
      master_pid ->
        send master_pid, {:join, pid}
    end
  end

  def tick(queue) do
    receive do
      {:join, pid} ->
        IO.puts "join #{inspect pid}"
        tick(queue ++ [pid])
      after @interval ->
        IO.puts "tick #{inspect :erlang.localtime}"
        [next | rest] = queue
        new_queue = rest ++ [next]
        send next, {:become_master, new_queue}
        wait
    end
  end

  def wait do
    receive do
      {:become_master, new_queue} ->
        :global.re_register_name(@master, self)
        tick(new_queue)
    end
  end
end
09 Oct 2014, 20:11
200-g_pragsmall

Aleksey Gureiev (11 posts)

Last node always sends tick to root. New nodes refer to the current root, and replace it.

defmodule C1 do
  @interval 2000
  @name :ticker

  def start do
    pid = spawn __MODULE__, :listen_for_tick, [ root = :global.whereis_name(@name) ]
    :global.re_register_name @name, pid

    # start ticking when first client
    if root == :undefined do
      send_tick_to pid
    end
  end

  def send_tick_to(next) do
    client = case next do
      :undefined -> :global.whereis_name @name
      _ -> next
    end

    send client, { :tick }
  end

  def listen_for_tick(next) do
    receive do
      { :tick } ->
        IO.puts "tick at #{inspect self}"

        :timer.sleep @interval
        send_tick_to(next)

        listen_for_tick(next)
    end
  end

end
10 Jan 2015, 21:10
Generic-user-small

Tim Morton (2 posts)

I went with a slightly different design than the solutions above.

Each process keeps a ticking boolean state. If it’s ticking, then it needs to send the :tick message to the next process. Otherwise, it just keeps listening.

defmodule RingNode do
  @interval 2000 # 2 seconds
  @name :ticker

  def start do
    head_pid = spawn(__MODULE__, :listen, [true])
    :global.register_name(@name, head_pid)
  end

  def add_node do
    send :global.whereis_name(@name), { :register }
  end

  def listen(ticking, next_pid \\ self) do
    receive do
      { :register } ->
        IO.puts "Registering from #{inspect self}"
        new_pid = spawn(__MODULE__, :listen, [false, next_pid])
        IO.puts "Created #{inspect new_pid}"
        listen(ticking, new_pid)
      { :tick } ->
        IO.puts "Tock #{inspect self}"
        listen(true, next_pid)
    after @interval ->
      if ticking do
        IO.puts "Tick #{inspect self}"
        send next_pid, { :tick }
      end
      listen(false, next_pid)
    end
  end
end
14 Jan 2015, 22:22
Generic-user-small

Pierre Sugar (56 posts)

defmodule Ring do
  
  @name     :ticker

  def start do
    pid = spawn(__MODULE__, :generator, [[]])
    :global.register_name(@name, pid)
  end

  def register(client_pid) do
    send :global.whereis_name(@name), { :register, client_pid }
  end

  def generator(clients) when clients == [] do
    receive do
      { :register, pid } ->
        IO.puts "adding #{inspect pid} to the ring"
        send pid, { :client, pid }
        send pid, { :tick, "tick from #{inspect self}" }
        generator([pid|clients])
    end
  end
  def generator(clients) do
    receive do
      { :register, pid } ->
        IO.puts "adding #{inspect pid} to the ring"
        send pid, { :client, List.last(clients) }
        send List.first(clients), { :client, pid }
        generator([pid|clients])
    end
  end

end

defmodule Client do

  @interval 5000

  def start do
    pid = spawn(__MODULE__, :receiver, [nil])
    Ticker.register(pid)
  end

  def receiver(next_pid) do
    receive do
      { :tick, message } ->
        IO.puts message
        send_to_client(next_pid)
        receiver(next_pid)
      { :client, next_pid } ->
        receiver(next_pid)
    end
  end

  defp send_to_client(next_client) when next_client == nil, do: nil
  defp send_to_client(next_client) do 
    receive do
    after @interval ->
        send next_client, { :tick, "tick from #{inspect self}" }
    end
  end

end
  You must be logged in to comment