small medium large xlarge

Dave_gnome_head_isolated_pragsmall
15 Jul 2013, 03:42
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?

Webcam_pragsmall
10 Mar 2014, 21:32
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
Ernie2_pragsmall
01 May 2014, 14:41
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
Patrick_pragsmall
17 Sep 2014, 13:41
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
200-g_pragsmall
09 Oct 2014, 20:11
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
Generic-user-small
10 Jan 2015, 21:10
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
Generic-user-small
14 Jan 2015, 22:22
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
Generic-user-small
13 Aug 2015, 16:27
Jim Kane (6 posts)

After taking a few wrong turns, I came back to something similar to Tim Morton’s solution. The notion of a permanent master PID just didn’t feel right, so my solution came at it from the other direction.

I think this problem may have been a bit vague, because the solutions seem to be very diverse. Here’s mine:

defmodule RingTicker do
  @moduledoc """
  To implement a ring of clients, a client needs to perform the following actions:

  1. On startup, link to self and run tick loop
  1. register: link self to new client, send my old next_client to new client, register new_client as global receiver
  2. tick: on receipt of tick, put it out and start 2 second timer.

  """

  @interval 5000 # 2 seconds
  @name     :ring_ticker

  def start do
    pid = spawn(__MODULE__, :receiver, [start_ticking])
    IO.puts "spawned starter #{inspect pid}"
    if start_ticking do
      register_ring_end(pid)
    else
      register(pid)
    end
  end

  def start_ticking do
    case ring_end do
      :undefined ->
        true
      _ ->
        false
    end
  end

  def register(client_pid) do
    send ring_end, { :register, client_pid }
  end

  def ring_end do
    :global.whereis_name(@name)
  end

  def receiver(send_tick, next_client_pid \\ self) do
    IO.puts "Starting receiver with #{inspect send_tick} and #{inspect next_client_pid}"
    receive do
      { :register, pid } ->
        IO.puts "registering #{inspect pid} with next #{inspect next_client_pid}"
        add_client_to_ring(pid, next_client_pid)
        receiver(send_tick, pid)
      { :set_next, pid } ->
        receiver(send_tick, pid)
      { :tick } ->
        IO.puts "tock in client #{inspect :calendar.local_time()}"
        receiver(true, next_client_pid)
    after @interval ->
      if send_tick do
        send next_client_pid, { :tick }
      end
      receiver(false, next_client_pid)
    end
  end

  def register_ring_end(pid) do
    :global.register_name(@name, pid)
  end

  def add_client_to_ring(new_client_pid, next_client_pid) do
    send new_client_pid, { :set_next, next_client_pid }
    register_ring_end(new_client_pid)
  end

end
Generic-user-small
16 Aug 2015, 21:44
Stefan Chrobot (10 posts)

Here’s my take using :timer.send_after. After the clients are added to the ring, they form a linked list. There’s one extra process (TickerRing.ticker) that is closing the ring.

defmodule TickerRing do
  @name :ticker_ring

  def start do
    ticker_ring_pid = spawn(__MODULE__, :ticker, [empty])
    :global.register_name(@name, ticker_ring_pid)
  end

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

  defp empty do
    {nil}
  end

  def ticker(ticker_ring) do
    receive do
      {:tick} ->
        case ticker_ring do
          {nil} ->
            IO.puts "#{inspect self} [TR] - empty ticker ring"
          {first_client_pid} ->
            IO.puts "#{inspect self} [TR] - forwarding tick to first client #{inspect first_client_pid}"
            send first_client_pid, {:tick}
        end
        ticker(ticker_ring)
      {:add, new_first_client_pid} ->
        IO.puts "#{inspect self} [TR] - adding #{inspect new_first_client_pid} to the ring"
        case ticker_ring do
          {nil} ->
            send new_first_client_pid, {:connect, self}
            IO.puts "#{inspect self} [TR] - sending bootstrap tick to #{inspect new_first_client_pid}"
            send new_first_client_pid, {:tick}
          {first_client_pid} ->
            send new_first_client_pid, {:connect, first_client_pid}
        end
        ticker({new_first_client_pid})
    end
  end
end

defmodule Client do
  @interval 2000

  def start do
    spawn(Client, :client, [nil])
  end

  def client(next_client_pid) do
    receive do
      {:connect, next_client_pid} ->
        IO.puts "#{inspect self} [CL] - connecting to #{inspect next_client_pid}"
        client(next_client_pid)
      {:tick} ->
        IO.puts "#{inspect self} [CL] - tock, sending tick to #{inspect next_client_pid}"
        :timer.send_after(@interval, next_client_pid, {:tick})
        client(next_client_pid)
    end
  end
end
4c5c2c297ed9f4664cfbe7733a011fb2_pragsmall
22 Aug 2015, 12:31
Artem Medeusheyev (8 posts)

New clients added to the end of the chain without distracting current tick sequence.

defmodule RingClients do
  @name :ring_clients
  @interval 2000

  def start(name) do
    pid = spawn(__MODULE__, :loop, [name])
    :global.register_name(@name, pid)
    send(pid, {:tick, name})
  end

  def loop(name, next_ticker \\ self) do
    root = get_root
    receive do
      {:register, pid} when next_ticker == root ->
        loop(name, pid)
      reg = {:register, _pid} ->
        send(next_ticker, reg)
      {:tick, pid_name} ->
        IO.puts "#{name}: tick received from #{String.strip(pid_name)}"
        :timer.sleep(@interval)
        send(next_ticker, {:tick, name})
    end
    loop(name, next_ticker)
  end

  defp get_root do
    :global.whereis_name(@name)
  end

  def join(name) do
    pid = spawn(__MODULE__, :loop, [name, get_root])
    send(get_root, {:register, pid})
  end
end
iex(2)> RingClients.start "root"
root: tick received from root
...
iex(3)> RingClients.join "  2"
...
iex(4)> RingClients.join "    3"
...
iex(5)> RingClients.join "      4"
iex(6)> root: tick received from 3
iex(6)>   2: tick received from root
iex(6)>     3: tick received from 2
iex(6)>       4: tick received from 3
iex(6)> root: tick received from 4
iex(6)>   2: tick received from root
iex(6)>     3: tick received from 2
iex(6)>       4: tick received from 3
  You must be logged in to comment