15 Jul 2013, 03:42
Dave_gnome_head_isolated_pragsmall

Dave Thomas (338 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
  You must be logged in to comment