上文解释了问题成因,即进程允许异步投递, 但进程内部有调用port(receive_match)的模式出现 ,现在来看一个典型的场景。
霸爷(yufeng)曾经不建议我们使用error_logger模块记录日志,因为该模块就使用了这个模式。
首先看看error_logger的启动方式:
start() ->
case gen_event:start({local, error_logger}) of
{ok, Pid} ->
simple_logger(?buffer_size),
{ok, Pid};
Error -> Error
end.
可以看出这是一个单进程的gen_event。
接着来看看error_logger发送任意一条error日志的方式:
error_logger.erl
error_msg(Format, Args) ->
notify({error, group_leader(), {self(), Format, Args}}).
notify(Msg) ->
gen_event:notify(error_logger, Msg).
gen_event.erl
notify(M, Event) -> send(M, {notify, Event}).
send({global, Name}, Cmd) ->
catch global:send(Name, Cmd),
ok;
send(M, Cmd) ->
M ! Cmd,
ok.
可以看到error_logger允许异步投递消息。
最后来看看error_logger处理一条日志的过程:
由于error_logger是一个gen_event,其上加载了若干event_handler,此处分析涉及日志文件写的error_logger_file_h:
error_logger_file_h.erl
handle_event(Event, {Fd, File, PrevHandler}) ->
write_event(Fd, tag_event(Event)),
{ok, {Fd, File, PrevHandler}};
write_event(Fd, {Time, {error, _GL, {Pid, Format, Args}}}) ->
T = write_time(maybe_utc(Time)),
case catch io_lib:format(add_node(Format,Pid), Args) of
S when is_list(S) ->
io:format(Fd, T ++ S, []);
_ ->
F = add_node("ERROR: ~p - ~p~n", Pid),
io:format(Fd, T ++ F, [Format,Args])
end;
io.erl
format(Io, Format, Args) ->
o_request(Io, {format,Format,Args}, format).
o_request(Io, Request, Func) ->
case request(Io, Request) of
{error, Reason} ->
[_Name | Args] = tuple_to_list(to_tuple(Request)),
{'EXIT',{get_stacktrace,[_Current|Mfas]}} = (catch erlang:error(get_stacktrace)),
erlang:raise(error, conv_reason(Func, Reason), [{io, Func, [Io | Args]}|Mfas]);
Other ->
Other
end.
request(Pid, Request) when is_pid(Pid) ->
execute_request(Pid, io_request(Pid, Request));
error_logger_file_h打开日志文件的方式不是raw,此处的文件描述符Io是一个pid()。
execute_request(Pid, {Convert,Converted}) ->
Mref = erlang:monitor(process, Pid),
Pid ! {io_request,self(),Pid,Converted},
if
Convert ->
convert_binaries(wait_io_mon_reply(Pid, Mref));
true ->
wait_io_mon_reply(Pid, Mref)
end.
wait_io_mon_reply(From, Mref) ->
receive
{io_reply, From, Reply} ->
erlang:demonitor(Mref),
receive
{'DOWN', Mref, _, _, _} -> true
after 0 -> true
end,
Reply;
{'EXIT', From, _What} ->
receive
{'DOWN', Mref, _, _, _} -> true
after 0 -> true
end,
{error,terminated};
{'DOWN', Mref, _, _, _} ->
receive
{'EXIT', From, _What} -> true
after 0 -> true
end,
{error,terminated}
end.
此处又碰到了熟悉的receive_match模式,调用此模式的位置在error_logger进程内部,将继续引发恶性循环。
除了error_logger,这种模式还在很多地方影响着我们,该肿么办?
未完待续...