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 likespawn(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?
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.