异步gen_server进行port访问时性能严重下降的原因和应对方法(三)典型场景

上文解释了问题成因,即进程允许异步投递, 但进程内部有调用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,这种模式还在很多地方影响着我们,该肿么办?

未完待续...

 

你可能感兴趣的:(server)