1

I'm currently working through the ubiquitous process ring in elixir. The ring is linked, but in the following fashion:

iex(1)> Ring.Worker.create_ring_of_linked_processes(3)
Ring.Worker.create_ring_of_linked_processes(3)
[%{"links" => [#PID<0.121.0>, #PID<0.120.0>], "pid" => #PID<0.122.0>},
 %{"links" => [#PID<0.120.0>, #PID<0.122.0>], "pid" => #PID<0.121.0>},
 %{"links" => [#PID<0.121.0>], "pid" => #PID<0.120.0>}]

I've noticed an asymmetry in the links here - should #PID<0.120.0> have the mapping "links" => [#PID<0.121.0>,#PID<0.122.0>] rather than just "links" => [#PID<0.121.0>] ?

The code is as follows:

  def loop() do
    receive do
      {:link, pid} when is_pid(pid) ->
        Process.link(pid)
        loop()
    end
  end

  def create_ring_of_linked_processes(num_of_processes) do
    num_of_processes
    |> create_processes
    |> link_processes([])
  end


  def link_processes([pid1, pid2 | rest], linked_processes) do
    send(pid1, {:link, pid2})
    :timer.sleep(1)
    {:links, links} = Process.info(pid1, :links)
    link_processes(
      [pid2 | rest], [%{"pid" => pid1, "links" => links} | linked_processes]
    )
  end

  def link_processes([pid | []], linked_processes) do
    %{"pid" => first_pid, "links" => _} = List.last(linked_processes)
    send(pid, {:link, first_pid})
    :timer.sleep(1)
    {:links, links} = Process.info(pid, :links)
    [%{"pid" => pid, "links" => links} | linked_processes]
  end

  @spec create_processes(integer) :: [pid]
  def create_processes(num_of_processes) do
    for _ <- 1..num_of_processes, do: spawn(__MODULE__, :loop, [])
  end
Cœur
  • 37,241
  • 25
  • 195
  • 267
category
  • 2,113
  • 2
  • 22
  • 46
  • 1
    Can you post an [MCVE](https://stackoverflow.com/help/mcve)? If you're getting the links from `Process.info(pid, :links)`, you might be calling it too soon. The links should be symmetric if the process were linked using `Process.link/1`. – Dogbert Apr 10 '17 at 14:28
  • @Dogbert sure, updated. I'm using a delay of one millisecond. – category Apr 10 '17 at 15:40
  • 2
    I think you need to modify this to collect `Process.info(_, :links)` after all the `Process.link/1` calls have been made. – Dogbert Apr 10 '17 at 16:41
  • @Dogbert That produced symmetry, thanks! Please may you submit that as an answer with the reason why that worked and the above didn't? – category Apr 11 '17 at 03:54

1 Answers1

2

This is because you're linking the processes at the same time as collecting its :links, but some links for that process are being created after you collect its links.

For example, if you spawn a process a, and then collect its links, it'll be an empty list.

iex(1)> a = spawn(fn -> :timer.sleep(:infinity) end)
#PID<0.82.0>
iex(2)> Process.info(a, :links)
{:links, []}

If you spawn b now and link it to a, b will have [a] in its links and a will have [b].

iex(3)> b = spawn(fn -> Process.link(a); :timer.sleep(:infinity) end)
#PID<0.85.0>
iex(4)> Process.info(b, :links)
{:links, [#PID<0.82.0>]}
iex(5)> Process.info(a, :links)
{:links, [#PID<0.85.0>]}

So, you need to collect the links for each process after all the linking is complete if you want the final links for each process.

Dogbert
  • 212,659
  • 41
  • 396
  • 397
  • I got symmetrical links to be reported by checking the links using `Process.info(_, :links)` after the `link_processes/2` function was executed in the `create_ring_of_linked_processes/1` function - but I don't think that this _guarantees_ that the 'linking is complete' - is there some way to know this in elixir? Even though `send(pid, {:link, _})` is executed, the `loop/0` process still needs to receive and handle the message in an unknown amount of time. – category Apr 14 '17 at 16:48
  • 1
    You're right, it's not guaranteed. The usual way this is done is making the receiver send another message back to the caller after the linking is complete. The caller will need to wait for the message to arrive before proceeding. `GenServer.call` is a robust implementation of something very similar to what I described just now; you might want to turn your process into a `GenServer` if you plan to use this in a real application. – Dogbert Apr 16 '17 at 16:27
  • Great, thanks, will do - marking as answer as it was just the example that didn't have link reporting symmetry guaranteed. – category Apr 17 '17 at 05:34