Understanding the Control Flow for Processes

Hi Experts,
I am trying to understand the Process Flow when spawning new Processes, for this I wrote a simple program to spawn a process and wait

defmodule Link do
  import :timer, only: [ sleep: 1 ]
  def sad_function do
    IO.puts "I am another process..Now I sleep zzzzz..."
    sleep 1000
    exit(:boom)
  end
  def run do
    Process.flag(:trap_exit, true)
    # Doesnt know whats heppening with the other process
    # spawn(Link, :sad_function, [])
    #The Below knows when the lined process Exists
    spawn_link(Link, :sad_function, [])
    IO.puts "I have spawned and I wait.."
    receive do
      msg ->
        IO.inspect "Wait over..Message received"
        IO.inspect msg
        IO.puts "Is Tuple? #{inspect is_tuple(msg)}"
      after 2000 ->
        IO.puts "Nothing is happening! Are we dead?"
    end
    IO.puts "After Receive Block!"
  end
end
Link.run

The Output is as follows:

λ elixir -r link.exs
I have spawned and I wait..
I am another process..Now I sleep zzzzz...
"Wait over..Message received"
{:EXIT, #PID<0.77.0>, :boom}
Is Tuple? true
After Receive Block!

Question: Why is the After Receive Block logged after the receive Block has been processes, my idea was that the receive block in the run() definition is kind of a callback and the rest of the code will get processed sequentially and the receive block whenever a message is received from the sad_function?

Thanks!

1 Like

Hey @chattes

receive do blocks the calling process. Each process is isolated, so this doesn’t block any other process, but it is blocking for the process that calls receive. This makes it very easy to receive a message and then use it in normal code:

# receives the first 10 messages

messages = for _ <- 1..10 do
  receive do
    msg -> msg
  end
end

messages |> IO.inspect
2 Likes

It is also more a loop than a callback…

Usually, You would do

defmodule Link do
  def run do
    receive do
      :stop ->
        IO.puts "arghhhh"
      msg ->
        IO.inspect msg
        run()
    end
    IO.puts "Hey, I'm out of the loop"
  end
end
iex> pid = spawn_link Link, :run, []
#PID<0.479.0>
iex> send pid, :yo                  
:yo
:yo
iex> send pid, :stop                
arghhhh
:stop
Hey, I'm out of the loop
Hey, I'm out of the loop

So it is normal to continue the code after your receive block get the first message… Because You don’t loop, and the rest is executed :slight_smile:

1 Like

Yeah okay that’s a little bit different than how i was expecting . but thanks for the explanation.
I have a bit of node background so I was thinking more callback style . Good I will have to think a little bit differently then I am used to.

1 Like

Interesting here is that the more high-level abstraction called GenServer that is commonly (almost always) used for processes over plain sends and receives, also adheres to a callback-style API, because the receive loop is one of the things that it has abstracted away from you :slight_smile:

3 Likes

Very nice observation. Maybe a GenServer can be a little bit more easier to understand for node people. But anyways, don’t know if you are used to it, but in ES2016 there is the await function, that actually does pretty something similar to what receive do. :slight_smile:

3 Likes

Have not used async/await yet. Still stuck in Promise land :).
But would checkout the Genserver to see its behaviour.