1
votes

I'm working through Joe Armstrong's Programming Erlang 2nd E. The book has exercises at the end of each chapter. Chapter 13, exercise 1 says:

Write a function my_spawn(Mod, Func, Args) that behaves like spawn(Mod, Func, Args) but with one difference. If the spawned process dies, a message should be printed saying why the process died and how long the process lived for before it died.

Here's a solution that has a race condition:

my_spawn(Mod, Func, Args) ->
    Pid = spawn(Mod, Func, Args),
    spawn(fun() ->
                  Ref = monitor(process, Pid),
                  T1 = erlang:monotonic_time(millisecond),
                  receive
                      {'DOWN', Ref, process, Pid, Why} ->
                          io:format("~p died because of ~p~n", [Pid, Why]),
                          io:format("~p lived for ~p ms~n", [Pid, erlang:monotonic_time(millisecond) - T1])
                  end
          end),
    Pid.

Spawning the process and creating the monitor is not an atomic step, so if the process dies after spawning but before the monitor is created, we won't get the error message.

Here's an attempt without the race condition:

my_spawn_atomic(Mod, Func, Args) ->
    spawn(fun() ->
                  {Pid, Ref} = spawn_monitor(Mod, Func, Args),
                  T1 = erlang:monotonic_time(millisecond),
                  receive {'DOWN', Ref, process, Pid, Why} ->
                          io:format("~p died because of ~p~n", [Pid, Why]),
                          io:format("~p lived for ~p ms~n", [Pid, erlang:monotonic_time(millisecond) - T1])
                  end
          end).

But the PID this returns is that of the monitoring process, not the Func process. And given that spawn always returns the PID of the process it creates, there doesn't seem to be a way to return Pid without resorting to a side effect.

What's the idiomatic way to do implement the atomic spawning?

2
You should have a look at spawn_link. See also stackoverflow.com/q/28531101/409228 .Steve Vinoski
How does spawn_link help? I'm already using spawn_monitor.Tianxiang Xiong
As already was mentioned before, atomic spawning already implement as spawn_link/1. Btw, maybe you also found interesting chapter Errors and Processes from LYSE.user4651282
why do you wrap everything in spawn? just get rid of the spawn and it will work fine.Roman Rabinovich

2 Answers

1
votes

You can send Pid from monitoring process as a message:

my_spawn_atomic(Mod, Func, Args) ->
    Parent = self(),
    MPid = spawn(fun() ->
                  {Pid, Ref} = spawn_monitor(Mod, Func, Args),
                  Parent ! {spawned, self(), Pid},
                  T1 = erlang:monotonic_time(millisecond),
                  receive {'DOWN', Ref, process, Pid, Why} ->
                          io:format("~p died because of ~p~n", [Pid, Why]),
                          io:format("~p lived for ~p ms~n", [Pid, erlang:monotonic_time(millisecond) - T1])
                  end
          end),
    receive
        {spawned, MPid, Pid} -> Pid
    after 1000 -> error % 1s should be way enough for spawning monitoring process
    end.

Another option is to wrap the function in a fun with an init phase:

my_spawn(Mod, Func, Args) ->
    Pid = spawn(fun() ->
            receive run -> apply(Mod, Func, Args)
            after 1000 -> exit(init_timeout)
            end
        end),
    spawn(fun() ->
                  Ref = monitor(process, Pid),
                  T1 = erlang:monotonic_time(millisecond),
                  Pid ! run,
                  receive
                      {'DOWN', Ref, process, Pid, Why} ->
                          io:format("~p died because of ~p~n", [Pid, Why]),
                          io:format("~p lived for ~p ms~n", [Pid, erlang:monotonic_time(millisecond) - T1])
                  end
          end),
    Pid.
0
votes

http://marcelog.github.io/articles/erlang_link_vs_monitor_difference.html

The difference between spawn_link and spaw_monitor is well explained here.

-module(mon_test).
-export([my_spawn/3, die_in/1]).

my_spawn(Mod, Func, Args) ->
    spawn(my_spawn(mon_test, my_spawn, [self(), Mod, Func, Args]),
    receive
        Pid -> Pid
        after 1000 -> timeout
    end.

my_spawn(Parent, Mod, Func, Args) ->
    {Pid, Ref} = spawn_monitor(Mod, Func, Args),
    T1 = erlang:system_time(),
    Parent ! Pid,
    receive
       {'DOWN', Ref, _Any, Pid, Why} -> 
        io:format("~p died because of ~p, lived for ~p milliseconds~n", [Pid, Why, (erlang:system_time()-T1)/1000/1000])
    end.

die_in(Secs) ->
  receive 
    Reason -> exit(Reason)
    after Secs*1000 -> exit(timeout_reason)
end.


> mon_test:my_spawn(mon_test, die_in, [5]).
<0.155.0>
<0.155.0> died because of timeout_reason, lived for 5001.152 milliseconds