elixir spwan and spawn_link(:trap_exit)

Keywords: Attribute Erlang Linux hot update

Process is a very basic unit of task execution for elixir. Previously, we did not understand spawn and spawn_link deeply. We only know that the latter is the link between the child process and the parent process, and the accidental collapse of the child process can also lead to the collapse of the parent process. Today I just saw a film about it. The exit_trap process flag of erlang The introduction gives us a deeper understanding of these two concepts.
For generating a new process to perform time-consuming operations:

1. I don't care if the child process hangs up. I choose spawn.

2. I want the child process to hang up and the parent process to stop, so I choose spawn_link.

3. I hope that when the child process hangs up, it will notify the parent process (sending messages, communication between processes using message mechanism, which can avoid deadlock problems caused by multi-threaded shared memory). Process.flag(:trap_exit, true), spawn_link

 #some logic here 
 #need to do some other things that is time-consuming 
 spawn fn ->
     #do some stuff here 
     nil
 end 

 spawn_link fn ->
     #do some stuff here 
     nil
 end 

 Process.flag(:trap_exit, true)
 spawn_link fn ->
     #do some stuff here 
     nil
 end

Write a column below to see how the three approaches differ.

defmodule Process.Test do
  def test1 do
    spawn fn ->
      IO.puts "I'm  a new process and i will crash"
      Process.sleep 2000
      exit(:abnormal)
    end
  end

  def test2 do
    spawn_link fn ->
      IO.puts "I'm a new process and i will crash"
      Process.sleep 2000
      exit(:abnormal)
    end
  end

  def test3 do
    Process.flag(:trap_exit, true)
    spawn_link fn ->
      IO.puts "I'm a new process and i will crash"
      Process.sleep 2000
      exit(:abnormal)
    end
  end
end

The above code defines a module that contains three functions to represent three methods of generating new processes, respectively.

Open an IEX (my environment is linux)

Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> defmodule Process.Test do
...(1)>   def test1 do
...(1)>     spawn fn ->
...(1)>       IO.puts "I'm  a new process and i will crash"
...(1)>       Process.sleep 2000
...(1)>       exit(:abnormal)
...(1)>     end
...(1)>   end
...(1)> 
...(1)>   def test2 do
...(1)>     spawn_link fn ->
...(1)>       IO.puts "I'm a new process and i will crash"
...(1)>       Process.sleep 2000
...(1)>       exit(:abnormal)
...(1)>     end
...(1)>   end
...(1)> 
...(1)>   def test3 do
...(1)>     Process.flag(:trap_exit, true)
...(1)>     spawn_link fn ->
...(1)>       IO.puts "I'm a new process and i will crash"
...(1)>       Process.sleep 2000
...(1)>       exit(:abnormal)
...(1)>     end
...(1)>   end
...(1)> end
{:module, Process.Test,
 <<70, 79, 82, 49, 0, 0, 8, 124, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 186,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:test3, 0}}
iex(2)> 

Let's execute the functions in the module one by one, starting with function test1

iex(3)> Process.Test.test1
I'm  a new process and i will crash
#PID<0.113.0>

We can see that nothing happens except the printed information. In fact, the child process has been abnormally exited by the exit function, but we do not see any error message. The main reason is that spawn is used. The current process does not care about the running state of the process. Okay, let's move on to the next one.

iex(4)> Process.Test.test2
I'm a new process and i will crash
#PID<0.115.0>
** (EXIT from #PID<0.80.0>) :abnormal

Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 

This time we can see ** (EXIT from # PID < 0.80.0>): the information of abnormal, and we can also see that iex reprinted the startup information iex serial number from 4 to 1. Exceptional exit on behalf of the child process does end the normal execution of the parent process.

iex(1)> Process.Test.test3
I'm a new process and i will crash
#PID<0.119.0>

While continuing to execute the test3 function, the same result as test1 appears. This is the process flag: trap_exit is working. spawn_link does not cause the parent process to exit as well. But there seems to be another problem: what about the message that the child process abnormally exits?
In fact, the parent process has received the message, but there is no handler processing mechanism (no printing) we can slightly change the program, add receive function in test3.

defmodule Process.Test do
  def test3 do
    Process.flag(:trap_exit, true)
    spawn_link fn ->
      IO.puts "I'm a new process and i will crash"
      Process.sleep 2000
      exit(:abnormal)
    end

    receive do 
      msg ->
        IO.inspect msg
      _ ->
        nil
    end
  end
end

Copy the code into the iex just now (although the module is the same name here, but do not close the previous iex but copy and paste directly, elixir hot update support repl directly replace the code module)

{:module, Process.Test,
 <<70, 79, 82, 49, 0, 0, 8, 228, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 186,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:test3, 0}}
iex(2)>

Run test3 function again

iex(3)> Process.Test.test3
I'm a new process and i will crash
{:EXIT, #PID<0.120.0>, :abnormal}
{:EXIT, #PID<0.120.0>, :abnormal}

At this point, we can clearly see that the abnormal exit of the child process (: abnormal)
Here's a brief introduction to the application scenario
Assume that the server needs to receive requests from the client in parallel and provide services for each request. At this point, if we need to care about whether the service can be successfully completed, we need to use spawn_link or Process.monitor to monitor the application service process. For the abnormal exit of the sub-application service, when we receive the message, we can choose the appropriate measures (restart). Here, the receive used in the routine is blocking, just for demonstration. GenServer's handle_info can be used to receive and process the message in the actual project. As for the message structure of process exit, if Process.monitor pid is used, every PID process {DOWN, ref,: process,_pid, reason} will be sent to the parent process calling monitor to listen on, handle_info can be used to process the message.

defmodule Test do
  use GenServer
  def monitor_me pid do
    Process.monitor pid 
    #Here, the pid can be stored in the ets table, and when handle_info receives the message, it can match the pid to do the corresponding processing.
  end

  def handle_info({:DOWN, ref, :process, _pid, reason}, state) do
   #do something here when receive this message
   {:noreply, state}
  end
end 

Posted by itsmani1 on Tue, 11 Dec 2018 16:24:05 -0800