small medium large xlarge

Dave_gnome_head_isolated_pragsmall
15 Jul 2013, 03:42
Dave Thomas (370 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 (13 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
Generic-user-small
09 Oct 2015, 18:20
Ju Liu (1 post)

Here is my solution, using a RingMaster to handle the management of the linked list and a RingClient which just propagates the ticks:

defmodule RingMaster do
  @name :ring_master

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

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

  def loop do
    IO.puts "# Master ready..."

    receive do
      {:register, pid} ->
        IO.puts "# Registered #{inspect pid}"

        send pid, {:set_next, pid}
        IO.puts "# First is #{inspect pid}"
        IO.puts "# Last is #{inspect pid}"

        send pid, {:tick, 1}
        IO.puts "-> Tick 1 sent"

        loop(pid, pid)
    end
  end

  def loop(first, last) do
    receive do
      {:register, pid} ->
        IO.puts "# Registered #{inspect pid}"

        send last, {:set_next, pid}
        send pid, {:set_next, first}
        IO.puts "# First is #{inspect first}"
        IO.puts "# Last is #{inspect pid}"

        loop(first, pid)
    end
  end
end

defmodule RingClient do
  @interval 2000

  def run do
    pid = spawn(__MODULE__, :loop, [])
    RingMaster.register(pid)
  end

  def loop do
    receive do
      {:set_next, pid} ->
        IO.puts "# Next is #{inspect pid}"
        loop(pid)
    end
  end

  def loop(next) do
    receive do
      {:tick, num} ->
        IO.puts "<- Tick #{num} received"
        loop(next, num, true)
    end
  end

  def loop(next, num, send_message) do
    receive do
      {:tick, num} ->
        IO.puts "<- Tick #{num} received"
        loop(next, num, true)
      {:set_next, pid} ->
        IO.puts "# Next is #{inspect pid}"
        loop(pid, num, send_message)
    after @interval ->
      case send_message do
        false ->
          loop(next, num, send_message)
        true ->
          next_num = num + 1
          send next, {:tick, next_num}
          IO.puts "-> Tick #{next_num} sent"
          loop(next, next_num, false)
      end
    end
  end
end
Generic-user-small
14 Dec 2015, 11:47
Charles Okwuagwu (4 posts)
defmodule Ring do
  @interval 2000
  @root :root_node

  def add_client do
    case root = :global.whereis_name(@root) do
      :undefined -># root
        pid = spawn(__MODULE__, :proc, [nil])
        Process.put(:current,pid)
        :global.register_name(@root, pid)
        send pid, {:tick, pid}
        IO.puts "use Ring.add_client to add new clients"
      _ ->
        pid = spawn(__MODULE__, :proc, [root])
        cur = Process.get(:current)
        Process.put(:current,pid)
        send cur, {:next, pid}
    end
  end

  def proc(next) do
    receive do
      {:next, pid} -> proc(pid)
      {:tick, src} when next == nil ->
        :timer.sleep(@interval)
        send src, {:tick, self}
        proc(next)
      {:tick, src} ->
        IO.puts "got tick on #{inspect src} @ #{inspect time_stamp}"
        :timer.sleep(@interval)
        send next, {:tick, self}
        proc(next)
    end
  end

  defp time_stamp do
    {{year, month, day}, {hour, minute, second}} = :calendar.local_time
    :io_lib.format("~4..0B-~2..0B-~2..0B ~2..0B:~2..0B:~2..0B",[year, month, day, hour, minute, second])
     |> List.flatten
     |> to_string
  end
end
Generic-user-small
21 Feb 2016, 22:04
Donald Kelly (2 posts)

Here’s an approach where each client, even the first, is identical, but only the first client is registered by name (to receive all the :register messages from other clients). The reply to the :register message gives the next pid in the ring.

defmodule Ticker do

  @interval 2000   # 2 seconds
  @name     :ticker

  def start do
    spawn(__MODULE__, :register, [])
  end

  def register do
    case :global.register_name(@name, self) do
      :yes ->
        generator(self)
      :no  ->
        send :global.whereis_name(@name), { :register, self }
        receive do
          { :next, pid } ->
            IO.puts "registration complete"
            receiver(pid)
        end
    end
  end

  defp generator(next) do
    receive do
      { :register, pid } ->
        IO.puts "registering #{inspect pid}"
        send pid, { :next, next }
        generator(pid)
    after
      @interval ->
        IO.puts "tick #{inspect next}"
        send next, { :tick }
        receiver(next)
    end
  end

  defp receiver(next) do
    receive do
      { :tick } ->
        IO.puts "tock in client"
        generator(next)
    end
  end
end
Generic-user-small
11 Mar 2016, 10:06
Stefan Houtzager (8 posts)

This one uses an agent to be able to store/get the list of nodes in order of starting. Node.list appeared to be not in this order when I started the fourth node.

defmodule Client do
  @interval 2000 # 2 seconds

  def start do
     pid = spawn(__MODULE__, :receiver, [false, nil])
    :global.register_name(Node.self(), pid)

    if Node.list != [] do
      agent = :global.whereis_name(NodeList)

      # notify the previous node of the pid hereabove as its next node
      send Agent.get(agent, fn list -> list end) |> List.last |> :global.whereis_name(), {:set_next, pid}

      # notify the just spawned process of the first as its next node
      send pid, {:set_next, Agent.get(agent, fn list -> list end) |> List.first |> :global.whereis_name()}

      node = [Node.self]
      Agent.update(agent,&(&1 ++ node))
    else
      {:ok, agent} = Agent.start(fn -> [Node.self] end, name: NodeList)
      :global.register_name(NodeList, agent)
    end
  end

  def receiver(listening, next_client) do
    receive do
      { :tick, name} ->
          IO.puts "tick in client from #{name}"
          receiver(true, next_client)
      { :set_next, next_node} ->
         receiver(true, next_node)
    after
      @interval ->
        if listening and next_client != nil do
          send next_client, {:tick, Node.self}
          receiver(false, next_client)
        else
          receiver(true, next_client )
        end
    end
  end
end
Generic-user-small
16 May 2016, 20:41
Simon (2 posts)
defmodule RingTicker do
  import :timer, only: [ sleep: 1 ]
  
  @interval 2000
  @name     :ringticker
  
  # Create client
  def start do
    pid = spawn(RingTicker, :register, [ :global.whereis_name(@name) ]) 
  end
  
  # There is no registered RingTicker, so register this one
  def register( :undefined ) do
    IO.puts "Registered as first RingTicker."
    :global.register_name(@name, self)
    send self, { :tick, [self] }
    receiver []
  end
  
  # Contact current registered Ticker
  def register(current) do
    IO.puts "Registering to already existing RingTicker #{inspect current}."
    send current, { :greet, self } 
    receiver []
  end
  
  def receiver(partners) do
    receive do
      { :greet, pid } ->
        IO.puts "Greeting from #{inspect pid}. Adding to ring..." 
        receiver(partners ++ [ pid ])
      { :tick, [head | tail] } ->
        IO.puts "Tick by #{inspect self}, next tick by: #{inspect head}."
        :global.register_name(@name, head)
        sleep @interval
        send head, { :tick, partners ++ tail ++ [head] }  
        receiver []  
    end
  end
end
Img_2196_pragsmall
10 Jun 2016, 22:01
Diogo Neves (12 posts)

This is a really cool exercise! thanks :)

defmodule RingTicker do

  @name    :ticker
  @timeout 2000

  def start do
    start_client_for_current_ticker

    pid = spawn(__MODULE__, :generator, [[]])
    :global.re_register_name @name, pid
  end

  def start_client_for_current_ticker do
    @name
    |> :global.whereis_name
    |> _start_client
  end

  defp _start_client(:undefined), do: :ok
  defp _start_client(ticker) do
    IO.puts "Starting client and registering at #{inspect ticker}"
    client = spawn(__MODULE__, :receiver, [])
    send ticker, {:register, client}
  end

  def receiver do
    receive do
      {:tick, pid} ->
        IO.puts "tock, from #{inspect pid}"
        receiver()
    end
  end

  def generator(clients) do
    receive do
      {:register, pid} ->
        IO.puts "registering #{inspect pid} as client"
        generator([pid | clients])

    after
      @timeout ->
        IO.puts "tick"
        Enum.each clients, fn client ->
          send client, {:tick, self()}
        end
        generator(clients)
    end
  end

end

On window 1:

iex> RingTicker.start()

On window 2 to N:

iex> RingTicker.start()

Back to window 1:

iex> RingTicker.start_client_for_current_ticker()

I just realised this doesn’t actually implement the spec and I’ll post another one soon

Img_2196_pragsmall
10 Jun 2016, 22:37
Diogo Neves (12 posts)

Here it is:

defmodule RingTicker do

  @name    :ticker
  @timeout 2000

  def start do
    pid = spawn(__MODULE__, :receiver, [:undefined])
    attach_to_current_ticker(pid)
    :global.re_register_name(@name, pid)
    pid
  end

  def attach_to_current_ticker(pid) do
    _current |> _register(pid)
  end

  defp _register(:undefined, _), do: :no
  defp _register(ticker, pid) do
    IO.puts "Attaching this client to current ticker #{inspect ticker}"
    send ticker, {:register, pid}
  end

  def tick_current do
    _current |> tick
  end

  def tick(:undefined) do
    IO.puts "No one to tick"
  end
  def tick(pid) do
    me = self()
    spawn fn ->
      :timer.sleep @timeout
      IO.puts "tick"
      send pid, {:tick, me}
    end
  end

  defp _current do
    :global.whereis_name @name
  end

  def receiver(client) do
    receive do
      {:register, pid} ->
        IO.puts "registering client #{inspect pid}"
        receiver(pid)

      {:tick, pid} ->
        IO.puts "tock from #{inspect pid}"
        tick(client)
        receiver(client)
    end
  end

end

Similar usage, but now you can start the tick at any node (if you keep the ~~~pid~~~ returned by ~~~RingTicker.start~~~

Generic-user-small
17 Jul 2016, 14:46
Nathan Hessler (10 posts)

took me a bit to wrap my head around a working implementation, but really happy with my final solution. This relies on the ability for messages in the mailbox that don’t match anything in receive will be kept around. However this is not fault tolerant. If any of the processes fail, the ring will be broken.

defmodule RingTicker do

  @interval 2000 # 2 seconds
  @entrance :ring_entrance

  def start do
    spawn(__MODULE__, :enter, [:global.whereis_name(@entrance)])
  end

  def enter(:undefined) do
    :global.register_name(@entrance, self)
    tick(self)
  end

  def enter(entrance) do
    send entrance, {:insert, self}
    standby
  end

  def standby do
    receive do
      {:join, next_ticker} -> tock(next_ticker)
    end
  end

  def tock(next_ticker) do
    receive do
      {:tick} ->
        IO.puts "tock on #{inspect self}"
        tick(next_ticker)
    end
  end

  def tick(next_ticker) do
    receive do
      {:insert, new_ticker} ->
        send new_ticker, {:join, next_ticker}
        tick new_ticker
    after
      @interval ->
        IO.puts "tick on #{inspect self}"
        send next_ticker, {:tick}
        tock next_ticker
    end
  end
end
You must be logged in to comment