Giter VIP home page Giter VIP logo

blog-erlang's Introduction

blog-erlang

blog-erlang's People

Contributors

lgweix avatar

Watchers

 avatar

blog-erlang's Issues

erlang中nif不被抢占问题的解决办法

nif函数内部不会消耗时间片,也就不会被抢占,所以nif函数消耗的时间太长的话,影响整个系统进程的调度,所以官方建议一个nif执行时间少于1ms
解决方法:
1.手动计算nif函数消耗的时间
R16引入enif_consume_timeslice函数,让 NIF 代码自己在恰当的地方调用这个 api,然后根据 enif_consume_timeslice 返回的结果判断是否需要放弃控制权,因此实际上还是协程的模式。协程式调度和抢占式调度混合在一起本来就是坏味道,如果通过判断发现已经用完时间片,程序员必须自己手工保存断点以及下一次恢复断点;而且这里还要自己估计时间片,把 timeslice 和虚拟机中本来就很模糊的规约(reduction)混在一起,味道也不好闻。

2.异步线程
大致思路就是将耗时的NIF计算放在一个单独的OS线程中执行,这个线程虽然不能接受Erlang时间发来的消息,但是可以发消息给Erlang进程。这样我们可以在Erlang进程中启动一个OS线程,并等待OS将计算结果以消息的方式发送过来。如前所述,等待消息的Erlang进程会被Erlang调度器抢占,也不会有堵塞调度器的问题。
例子
enif_thread_create(...
发送消息要注意的是新建一个env
ErlNifEnv *msgenv = enif_alloc_env();
enif_send(NULL, pid, msgenv, msg);
发送完要清除env
enif_clear_env(env);
另外,官方文档中强调,创建的OS线程要join,否则在NIF动态库unload的时候VM会崩溃。

3.R17引入的"脏调度器"
脏调度器本质上和普通调度器是一样的,也是运行在虚拟机中的调度器线程,但是这种调度器专门运行长时运行的 NIF,R17 允许将长时运行的 NIF 直接丢到脏调度器上去跑。通过调用 enif_schedule_dirty_nif 将需要长时运行的 NIF 函数丢到脏调度器上。长时运行的函数返回的时候要调用 enif_schedule_dirty_nif_finalizer 函数,表示从脏调度器返回到了普通调度器。
参考:http://www.cnblogs.com/zhengsyao/p/dirty_scheduler_otp_17rc1.html

4.native process
该功能暂时还没实现,可能要等到R18

erlang热更新时辨别新旧beam文件

erlang每次编译都会生成一个唯一的版本号,据此可以判断文件beam文件是否已经更新,参数代码如下:

%% 获取热更列表

diff_list() ->
    {ok, Cwd} = file:get_cwd(),
    Filelist = filelib:fold_files( Cwd ++ "/../ebin", ".beam", true, fun(File, Acc) -> [File|Acc] end, []),

    F = fun(File,Acc0) ->
        {ok,{Module,NewVsn}} = beam_lib:version(File),

        List = Module:module_info(attributes),
        case lists:keyfind(vsn, 1, List) of
            {vsn,NewVsn} ->
                Acc0;
            _ ->
                [Module|Acc0]
        end
    end,
    lists:foldl(F, [], Filelist).

guard表达式的限制

参考:http://erlang.org/doc/reference_manual/expressions.html
1.分号与逗号的区别:
一个以分号(;)分隔的guard序列,其中一个guard为true时,结果为true
Guard1;...;GuardK
一个以逗号(,)分隔的guard序列,所有guard表达式为true时,结果为true
Guard1,...,GuardK

2.有效guard表达式是有效erlang表达式的子集,这是为了保证guard表达式没有副作用。有效的guard表达式如下:
原子true
其它常量(terms and bound variables), 结果都为false
Type Test BIFS表中的bif调用
Term的比较
算术表达式
布尔表达式
短路表达式(andalso/orelse)

3.如果一个表达式发生异常,整个guard表达式失败(false)。如果是一个guard表达式序列,则计算下一个guard表达式.

注:保护式(guard)中如果出错,不会报错,只会返回false!
case 1=:1 of
true when not erlang:length(t) =:= 1 orelse true ->
ok;
_ ->
error
end.
Result is: error
保护式中对t (atom) 求length会出错,本应crash掉,但因为在保护式中,默认出错后结束此保护式计算并返回false,这也是为什么保护式不接受复杂的函数,只能用erlang的bif来做的原因之一。

有效bif列表
is_atom/1
is_binary/1
is_bitstring/1
is_boolean/1
is_float/1
is_function/1
is_function/2
is_integer/1
is_list/1
is_map/1
is_number/1
is_pid/1
is_port/1
is_record/2
is_record/3
is_reference/1
is_tuple/1

Other BIFs Allowed in Guard Expressions:
abs(Number)
bit_size(Bitstring)
byte_size(Bitstring)
element(N, Tuple)
float(Term)
hd(List)
length(List)
map_size(Map)
node()
node(Pid|Ref|Port)
round(Number)
self()
size(Tuple|Bitstring)
tl(List)
trunc(Number)
tuple_size(Tuple)

关于erlang中的timer:send_after

erlang中有两个延时发送消息的函数erlang:send_after/3, timer:send_after/3,有什么区别呢?

看下timer:send_after/3的源码

send_after(Time, Pid, Message) -> 
    req(apply_after, {Time, {?MODULE, send, [Pid, Message]}})

req(Req, Arg) ->
    SysTime = system_time(),
    ensure_started(),
    gen_server:call(timer_server, {Req, Arg, SysTime}, infinity)

handle_call({apply_after, {Time, Op}, Started}, _From, _Ts)
  when is_integer(Time), Time >= 0 ->
    BRef = {Started + 1000*Time, make_ref()},   %返回给调用进程的数据,{到期时间,ref}
    Timer = {BRef, timeout, Op},  
    ets:insert(?TIMER_TAB, Timer),  %插入定时ets表
    Timeout = timer_timeout(system_time()),  
    {reply, {ok, BRef}, [], Timeout};   %这里返回值带有Timout参数,如果超时未能收到其它消息,将会触发超时

%%超时处理函数
timer_timeout(SysTime) ->
    case ets:first(?TIMER_TAB) of
 '$end_of_table' ->
     infinity;
 {Time, _Ref} when Time > SysTime ->
     Timeout = (Time - SysTime + 999) div 1000,
     %% Returned timeout must fit in a small int
     erlang:min(Timeout, ?MAX_TIMEOUT);
 Key ->
     case ets:lookup(?TIMER_TAB, Key) of
  [{Key, timeout, MFA}] ->
      ets:delete(?TIMER_TAB,Key),
      do_apply(MFA),
      timer_timeout(SysTime);
  [{{Time, Ref}, Repeat = {repeat, Interv, To}, MFA}] ->
      ets:delete(?TIMER_TAB,Key),
      NewTime = Time + Interv,
      %% Update the interval entry (last in table)
      ets:insert(?INTERVAL_TAB,{{interval,Ref},{NewTime,Ref},To}),
      do_apply(MFA),
      ets:insert(?TIMER_TAB, {{NewTime, Ref}, Repeat, MFA}),
      timer_timeout(SysTime)
     end
    end.


%%超时后执行相应的操作
%% Help functions
do_apply({M,F,A}) ->
    case {M, F, A} of
 {?MODULE, send, A} -> %发送消息
     %% If send op. send directly, (faster than spawn)
     catch send(A);
 {erlang, exit, [Name, Reason]} ->
     catch exit(get_pid(Name), Reason);
 _ ->
     %% else spawn process with the operation
     catch spawn(M,F,A)       %开启新进程,执行M:F(A), 由timer:apply_after/4调用
    end.

%%gen_server主循环
loop(Parent, Name, State, Mod, hibernate, Debug) ->
    proc_lib:hibernate(?MODULE,wake_hib,[Parent, Name, State, Mod, Debug]);
loop(Parent, Name, State, Mod, Time, Debug) ->
    Msg = receive
       Input ->
      Input
   after Time ->  %超时则产生一个timeout消息
    timeout
   end,
    decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, false)

总结:

  1. timer模块把定时器保存在一个叫?TIMER_TAB的有序ETS表中
    ?TIMER_TAB = ets:new(?TIMER_TAB, [named_table,ordered_set,protected]),
  2. 利用了gen_server返回值的超时机制
  3. Timer模块开头有一句话:
    The timeouts are not exact, but should be at least as long as requested.
    意思是此模块的timeout不是非常精确,但至少保证经过了要求的时间
  4. erlang:send_after是BIF函数,它们把计时器附着在进程自己身上。
    so尽量使用erlang:send_after函数吧

erlang进程间的link及monitor

Process Termination

进程结束的时候,总是伴随一个exit reason, reason可以是任何term. 如果exit reason 是原子normal时,就认为是正常结束. 进程代码执行完毕就是正常结束. 进程发生运行时错误时, 会以退出原因为{Reason, Stack}结束. 进程可以调用下面的BIF函数结束自己:

exit(Reason)
erlang:error(Reason)
erlang:error(Reason, Args)

进程将以退出原因Reason退出在调用exit/1时,而其它情况将是{Reason, Stack}.

Links

两个进程可以相互link. Pid1和Pid2可以通过Pid1中调用BIF函数link(Pid2)(或者相反)创建连接. links是双向的,两个进程之间只能有一个link, 重复调用link(Pid)没有效果.link可以通过BIF函数unkink(Pid)来移除.

Error Handing

erlang对进程有一个内建的错误处理机制.结束的进程会发送一个exit signals 给所有linked的进程,这些进程也将会以同样的方式处理exit信号. 通过这种机制,一些进程就可以监控其它进程.

  • 发送exit signals. 当一个进程结束,退出原因为Reason, 它会发送退出原因为Reason的exit signals给所有link的进程. 进程也可以调用函数exit(Pid, Reason), 结果就是发送一个exit reason为Reason的信号给Pid,但对调用进程没有影响.
  • 收到退出信号(exit signal). 当进程收到一个exit signal, 退出reason不是normal时,
    默认结束进程,并将exit signal发送给所有linked的进程,
    reason不变.退出原因为normal的signal会被忽略. 进程可以调用下面的函数trap退出信号:
process_flag(trap_exit, true)

当进程正在trapping exits时, 收到exit信号不会退出,而是被转换成普通的消息

{'EXIT',FromPid, Reason}

放入消息队列. 一个例外情况是,如果exit reason是kill, exit(Pid,kill) 将无条件结束进程, 无论是否trap exit
signals.

Monitors

另一个可选的连接方式是minitors.进程Pid1可以创建一个monitor连到Pid2, 通过调用函数
erlang:monitor(process, Pid2),该函数返回一个reference Ref.如果Pid2以退出原因Reason结束了, 一个'DOWN'消息被发送到Pid1:

{'DOWN', Ref, process, Pid2, Reason}

如果Pid2不存在,'DOWN'消息将会立即发送, Reason被设置为 noproc. Monitors是单向的, 重复调用erlang:monitor/2会创建多个独立的monitors, 进程结束的时候每个monitors都会发送一个'DOWN'消息

erlang中获取系统用户目录

软件配置文件可以在系统用户目录中, 这样软件升级后就直接覆盖原文件就可以了,配置文件依然保留本地配置. erlang中通过init:get_argument(home).获取用户目录
windows中结果:
1> init:get_argument(home).
{ok,[["C:\Users\Administrator"]]}

linux中结果如下:
20> init:get_argument(home).
{ok,[["/home/gavin"]]}

erlang日志框架lager

lager是一个非常优秀的erlang日志框架

启动

lager:start()

添加编译选项:

{parse_transform, lager_transform}
也可以采用一种技巧, 将lager:xxx函数再次封装到一个自定义的文件中, 这样只需要在自定义日志文件中添加上面的编译标志即可.但这时line, module这些就会出问题了,需要自己解决

使用

lager:error("some message").
注:如果直接在控制台调用此函数,则会提示函数不未定义的错误.这是因为lager中实际上不存在此函数,编译的时候, 编译器会自动替换为lager:log(...)系列函数

过载保护

lager会根据mailbox的大小在同步和异步两种方式间切换, 配置如下:
{async_threshold, 20},
{async_threshold_window, 5}
上面的配置表示, mailbox超过20的时候, 切换到同步模式, mxailbox大小降到20-5=15时,又会切换到异步模式
通过下面的配置可以限制,每秒处理的消息上限
{error_logger_hwm, 50}
另外一种方过载保护方式是, 当消息到达一定数量后, 直接杀死日志处理进程, 一段时间重启.

运行期日志等级改变

lager:set_loglevel(lager_console_backend, debug).
lager:set_loglevel(lager_file_backend, "console.log", debug).

异常打印的美化

tryfoo()
catchClass:Reason ->
lager:error(
"~nStacktrace:~s",
[lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason})])
end.

记录打印美化

lager:info("My state is ~p", [lager:pr(State, ?MODULE)])
示例:

([email protected])3> AccInfo = #account_info{account="abc", role_ids = []}.
#account_info{account = "abc",role_ids = []}
([email protected])5> lager:pr(AccInfo,login_mgr). 
{'$lager_record',account_info,
                 [{account,"abc"},{role_ids,[]}]}

Tracing

lager支持基于日志消息属性的重定向. lager自动捕获调用点的pid,module,function和line属性

trace到控制台

lager:trace_console([{module, mymodule}], error)
lager:trace_console([{module, mymodule}]) % default loglevel is debug

将模块role中, info以上级别的日志重定向到控制台, 可以在一个filter中指定多个表达式, loglevel

trace到文件
lager:trace_file("log/error.log", [{module, mymodule}, {function, myfunction}], warning)

删除指定的trace

{ok, Trace} = lager:trace_file("log/error.log", [{module, mymodule}]),
lager:stop_trace(Trace)

删除所有trace
lager:clear_all_traces().

lager:starus()可以获得所有log backends 和 traces信息

([email protected])6> lager:status().
Lager status:
Console (lager_event) at level info
File log/error.log (lager_event) at level error
File log/console.log (lager_event) at level info
Active Traces:
Tracing Reductions:
Tracing Statistics:
  input: 0
 output: 0
 filter: 0
ok

在配置文件中配置Trace

{lager, [
  {handlers, [...]},
  {traces, [
    %% handler,                         filter,                message level (defaults to debug if not given)
    {lager_console_backend,             [{module, foo}],       info },
    {{lager_file_backend, "trace.log"}, [{request, '>', 120}], error},
    {{lager_file_backend, "event.log"}, [{module, bar}]             } %% implied debug level here
  ]}
]}.

erlang中的性能分析工具

erlang提供了fprof,eprof,cover,cprof 4个性能测试工具,对比如下:

Tool Results Size of Result Effects on Program Execution Time Records Number of Calls Records Execution Time Records Called by Records Garbage Collection
fprof Per process to screen/file Large Significant slowdown Yes Total and own Yes Yes
eprof Per process/function to screen/file Medium Small slowdown Yes Only total No No
cover Per module to screen/file Small Moderate slowdown Yes, per line No No No
cprof Per module to caller Small Small slowdown Yes No No No
  1. fprof
   fprof:trace([start, {procs, all}]). % 跟踪所有文件,all可以替换成pid, 或者[pid,...]
   fprof:trace(stop).
   fprof:profile().
   fprof:analyse().  % 指定输出文件到文件,fprof:analyse([{dest, "result.txt"}])
  • 分析结果默认按acc字段排序,{sort,own}选项可以按own字段排序
    fprof:analyse([{sort, own}]).
  • 直接生成分析报告而不产生中间文件:
{ok, Tracer} = fprof:profile(start),
fprof:trace([start, {tracer, Tracer}]),
%% Code to profile
fprof:trace(stop);
  1. eprof
eprof:start().
eprof:start_profiling([Pid]).
eprof:stop_profiling().
eprof:log(FileName). % 后续的分析报告同时保存到文件中,没有这一步只会打印到shell中
eprof:analyze(). % 生成分析报告

erlang中遇到的system limit错误

问题

在本地做服务器测试,当连接数到达大概8177的时候,gen_tcp:connect/3函数会返回{error, system_limit}

查阅erlang官方文档System Limit一节,主要系统限制如下:

名称 限制
Processes 默认最在同时存活进程数为32768, 启动参数+P调整
Atoms 默认最大数量为1,048,576, 可以使用+t参数调整
Open ports 默认最大端口数量为16384, 启动参数+Q调整
Open files and socket 依赖最大Open ports
Ets tables 默认1400, 通过环境变量ERL_MAX_ETS_TABLES修改

疑惑

ports 数量限制为16384大概是8177的两倍还多,通过erlang:system_info/1函数查看相应了限制值如下(win7+r18):

([email protected])3> erlang:system_info(process_limit).
262144
([email protected])4> erlang:system_info(port_limit).   
8192
([email protected])5> erlang:system_info(ets_limit). 
2053

所以系统限制跟操作系统,及erlang版本是相关的,通过erlang:system_info/1函数查看到的值才是准确的.查看当前系统相关进程,端口,ets数量如下:

erlang:system_info(process_count)
erlang:system_info(port_count)
erlang:system_info(ets_count)

解决

启动时加上+Q参数调整ports上限解决问题

Lager问题请教

大佬好,我们项目目前线上使用的是lager打日志,最近有碰到日志丢失的情况;

我单独压测了lager打日志的性能,接近1000的qps(超过线上的并发量了),并没有发现日志丢失的情况

请问大佬有没有啥思路,帮忙定位问题?

感谢!!

获得一个对象占用的字数

通过未文档化的 BIF erts_debug:size() 可以获得一个对象占用的字数:
83> L1 = [1,2,3].
[1,2,3]
84> erts_debug:size(L1).
6
85> L2 = [L1,L1,L1].
[[1,2,3],[1,2,3],[1,2,3]]
86> erts_debug:size(L2).
12
通过另一个未文档化的 BIF 调用 erts_debug:flat_size() 可以得到对象平坦化后的大小:
88> erts_debug:flat_size(L1).
6
89> erts_debug:flat_size(L2).
24

erlang中bif函数与c代码的对应

erlang内建函数(built infunctions)用是C代码实现的,erlang中调用函数名与c中的定义的名字是不一样的,也不知道具体是在哪个c语言文件中定义的。今天终于知道了映射规则比如binary:bin_to_list/2 对应的函数为 binary_bin_to_list_1, 即模块名_函数名_参数个数, 至于模块名没有对应关系,我们全文搜索函数名即可找到定义的地方
1
例外的情况,对于erlang模块中的函数,对应的c函数名不包括模块名
2

erlang进程池poolboy问题

简单使用

poolboy代码比较简单,主要代码都在poolboy.erl模块中, 简单使用如下:

%% 启动进程池
PoolArgs = [{name, {local, my_pool}}, {worker_module, test_proc},{size, 15000}, {max_overflow, 0}],
poolboy:start_link(PoolArgs),

%% 从进程池中获取一个进程
Worker = poolboy:checkout(Pool)
%% 释放一个进程到池中
poolboy:checkin(Pool, Worker)

name 指定进程池进程名称

  • worker_module 进程回调模块,可以是一个gen_server,实现start_link/1函数
  • size 进程池大小,即池中进程数量,启动进程池的时候就会创建
  • max_overflow 最大溢出进程数,如果进程数量size, 充许动态创建的进程数.动态创建的进程,结束后就会被回收

poolboy通过预先创建工作进程的方式,降低创建进程的开销. 进程池本身也是一个gen_server进程, 用户通过pool:checkout/1取出工作进程时,调用的是gen_server:call/2

场景描述

测试环境,结节A上通过rpc:call启动节B上的进程,进程池在B节点上, 进程池中会创建几K的进程

问题

  1. 通过checkout取出一个进程时,进程池会monitor调用进程,如果调用进程结束了,则进程池会将分配出去的进程重新放入池中. 但rpc会在远程节点(B节点)上spawn一个临时进程, 在临时进程中调用用户函数, 进程池监测到临时进程结束后,就回会回收进程.想到的解决方法有几以下几种(上面的几种方案似乎都不怎么样):
  2. 改用rpc:block_call, 这个函数在远程节点不会创建临时进程,但这会阻塞rpc处理进程rex
  3. 在B节点上启动一个固定的进程,通过该进程从进程池中取出工作进程,这无疑会增加系统负载
  4. 修改poolboy代码,去掉monitor相关内容
  5. 性能问题. 测试发现使用了poolboy,系统性能并没有提高,这让我很是疑惑.从poolboy中取出工作进程时, 通过gen_server:call的方式, call是很影响效率的

思考

出现上面的问题, 应该是对poolboy的使用场景有误, 大量的时间花在进程的切换上,导致进程池本身成为瓶颈. 适用的场合应该是,少量进程的情况,比如连接数据库时,会有一个连接池.

参考资料

介绍几个著名的erlang进程池:
http://www.erlang-factory.com/static/upload/media/1427795030373316erlangfactory2015.pdf

erlang 热更新

  1. 自动热更新. 可以参考mochiweb的实现 https://github.com/mochi/mochiweb/blob/master/src/reloader.erl
    mochiweb reloader每隔一秒检查一次已加载的所有模块(code:all_loaded()),遍历模块列表,检查其所在路径的变更状况,若模块在一秒内有变动,则通过code:load_file(Module)加载模块到运行时系统,执行热更。
    2.mochiweb是对比文件的修改时间来确定beam文件是否修改, 其实可以比较beam文件的版本号来实现
    `%% 获取热更列表
    diff_list() ->
    {ok, Cwd} = file:get_cwd(),
    Filelist = filelib:fold_files( Cwd ++ "/../ebin", ".beam", true, fun(File, Acc) -> [File|Acc] end, []),

    F = fun(File,Acc0) ->
    {ok,{Module,NewVsn}} = beam_lib:version(File),
    List = Module:module_info(attributes),
    case lists:keyfind(vsn, 1, List) of
    {vsn,NewVsn} ->
    Acc0;
    _ ->
    [Module|Acc0]
    end
    end,
    lists:foldl(F, [], Filelist).`

erlang反编译

erlang中编译时如果带有debug_info,则可以反编译出源码

code(File)->
    {ok,{_Mod, [{abstract_code, {_X, Ac}}]}} = beam_lib:chunks(File,[abstract_code]),
    Src = erl_prettypr:format(erl_syntax:form_list(Ac)),
    file:write_file(File++".erl",Src).

另:可以对debug_info信息进行加密,这样防止源码泄露

erlang远过程调用rpc:call,rpc:block_call, rpc:cast的实现

rpc模块代码在rpc.erl文件中, 这里主要关注下rpc:call, rpc:block_call, rpc:cast的实现

1. rpc:call的实现

call(N,M,F,A,infinity) when node() =:= N ->  %% Optimize local call
    local_call(M,F,A);
call(N,M,F,A,infinity) ->
    do_call(N, {call,M,F,A,group_leader()}, infinity);
call(N,M,F,A,Timeout) when is_integer(Timeout), Timeout >= 0 ->
    do_call(N, {call,M,F,A,group_leader()}, Timeout).

如果调用的是本地节点上的函数, 则作了优化处理, local_call/3函数中直接调用apply/3来实现.

local_call(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
    case catch apply(M, F, A) of
    {'EXIT',_}=V -> {badrpc, V};
    Other -> Other
    end.

如果调用的是非本地节点, 最终通过call远程节点上的名为?NAME的gen_server进程实现, .进程名NAME的定义为 rex, 该进程的代码也位于rpc.erl文件中. 超时参数为infinity与非infinity处理方式不一样, 这里也是让我疑惑的地方, 为什么设置了Timeout时要开一个进程?

do_call(Node, Request, infinity) ->
    rpc_check(catch gen_server:call({?NAME,Node}, Request, infinity));
do_call(Node, Request, Timeout) ->
    Tag = make_ref(),
    {Receiver,Mref} =
    erlang:spawn_monitor(
      fun() ->
          %% Middleman process. Should be unsensitive to regular
          %% exit signals.
          process_flag(trap_exit, true),
          Result = gen_server:call({?NAME,Node}, Request, Timeout),
          exit({self(),Tag,Result})
      end),
    receive
    {'DOWN',Mref,_,_,{Receiver,Tag,Result}} ->
        rpc_check(Result);
    {'DOWN',Mref,_,_,Reason} ->
        %% The middleman code failed. Or someone did 
        %% exit(_, kill) on the middleman process => Reason==killed
        rpc_check_t({'EXIT',Reason})
    end.

远程节点上, call的处理跟我们平时调用gen_server:call的处理方式不一样, call的处理函数中并不是做实际工作, 而是开启一个工作进程, 这样就减轻了rex进程的负担, 这种方法值得借签.

handle_info({From, {call,Mod,Fun,Args,Gleader}}, S) ->
    %% Special for hidden C node's, uugh ...
    handle_call_call(Mod, Fun, Args, Gleader, {From,?NAME}, S);

handle_call_call(Mod, Fun, Args, Gleader, To, S) ->
    RpcServer = self(),
    %% Spawn not to block the rpc server.
    {Caller,_} =
    erlang:spawn_monitor(
      fun () ->
          set_group_leader(Gleader),
          Reply = 
              %% in case some sucker rex'es 
              %% something that throws
              case catch apply(Mod, Fun, Args) of
              {'EXIT', _} = Exit ->
                  {badrpc, Exit};
              Result ->
                  Result
              end,
          RpcServer ! {self(), {reply, Reply}}
      end),
    {noreply, gb_trees:insert(Caller, To, S)}.

handle_info({Caller, {reply, Reply}}, S) ->
    case gb_trees:lookup(Caller, S) of
    {value, To} ->
        receive
        {'DOWN', _, process, Caller, _} -> 
            gen_server:reply(To, Reply),
            {noreply, gb_trees:delete(Caller, S)}
        end;
    none ->
        {noreply, S}
    end;

2. rpc:block_call的实现

block_call本地节点的处理代码与call相同, 远程节点上的处理则不同. block_call在远程节点上并不开启子进程去处理实际工作, 则是直接在rex进程中处理.

handle_call({block_call, Mod, Fun, Args, Gleader}, _To, S) ->
    MyGL = group_leader(),
    set_group_leader(Gleader),
    Reply = 
    case catch apply(Mod,Fun,Args) of
        {'EXIT', _} = Exit ->
        {badrpc, Exit};
        Other ->
        Other
    end,
    group_leader(MyGL, self()), % restore
    {reply, Reply, S};

3. rpc:cast的实现

rpc:cast对本地节点函数, 直接spawn一个进程来处理, 对于远程节点则使用gen_server:cast实现:

cast(Node, Mod, Fun, Args) when Node =:= node() ->
    catch spawn(Mod, Fun, Args),
    true;
cast(Node, Mod, Fun, Args) ->
    gen_server:cast({?NAME,Node}, {cast,Mod,Fun,Args,group_leader()}),
    true.

远程节点上的处理, 也是spawn了一个进程来处理

handle_cast({cast, Mod, Fun, Args, Gleader}, S) ->
    spawn(fun() ->
          set_group_leader(Gleader),
          apply(Mod, Fun, Args)
      end),
    {noreply, S};

总结:

  1. 对于本地节点,rpc模块都会单独处理, 额外消耗很少
  2. rpc:block_call会阻塞远程节点上的rpc处理进程, 所以只适合那种耗时非常少的操作; 而rpc:call在远程节点上开启单独的进程来处理工作, 适合处理耗时长的工作.
  3. rpc:cast没有block方式,都是spawn一个进程来处理

erlang自带的http server

erlang自带了一个http server, 如果需求比较简单,就没必要用cowboy这样重量级的web server了.
Inets HTTP server 通过两种方式,提供创建动态web页面的能力,一是通用的CGI脚本;另一个是叫做ESI-functions的机制, 下面的示例演示了ESI方式创建web页面
测试代码:

%% test_server.erl
-module(test_server).
-export([start/0]).
start() ->
    ok = inets:start(),
    {ok, Pid} = inets:start(httpd, [{proplist_file, "test_server.config"}]),
    io:format("server info:~p", [httpd:info(Pid)]),
    ok.
%% test.erl
-module(test).
-export([server_list/3]).
server_list(SesstionId, Env, Input) ->
    io:format("Env:~p", [Env]),
    io:format("Input:~p", [Input]),
    mod_esi:deliver(SesstionId, "hello world"),
    ok.

配置

[{port, 8090},
 {server_name, "httpd_test"},
 {server_root,"e:/test"},
 {document_root, "e:/test"},
 {erl_script_alias, {"", [test]}},
 {modules, [mod_esi]}
].

配置说明
erl_script_alias: 是mod_esi模块必需的选项, 用于区别哪些模块适应esi
modules: 默认值为[mod_alias, mod_auth, mod_esi, mod_actions, mod_cgi, mod_dir, mod_get, mod_head, mod_log, mod_disk_log]

测试:

1> test_server:start().
server info:[{mime_types,[{"htm","text/html"},{"html","text/html"}]},
             {ipfamily,inet},
             {server_name,"httpd_test"},
             {bind_address,any},
             {modules,[mod_esi]},
             {server_root,"e:/test"},
             {erl_script_alias,{[],[test]}},
             {port,8090},
             {document_root,"e:/test"}]ok
2> Env:[{server_software,"inets/6.2"},
     {server_name,"admin-PC"},
     {gateway_interface,"CGI/1.1"},
     {server_protocol,"HTTP/1.1"},
     {server_port,8090},
     {request_method,"GET"},
     {remote_addr,"127.0.0.1"},
     {script_name,"/test/server_list?adb=3"},
     {http_host,"127.0.0.1:8090"},
     {http_connection,"keep-alive"},
     {http_cache_control,"max-age=0"},
     {http_upgrade_insecure_requests,"1"},
     {http_user_agent,"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"},
     {http_accept,"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"},
     {http_accept_encoding,"gzip, deflate, sdch"},
     {http_accept_language,"zh-CN,zh;q=0.8"},
     {query_string,"adb=3"}]2>
     Input:"adb=3"2>

erlang:load_nif/2加载dll失败的问题

项目中有部分功能是用c实现的, 编译成了dll, 通过erlang:load_nif/2函数加载, 但函数执行失败了, 于是在shell中手动调用测试:

8> erlang:load_nif("debug/erl_nif",0).
{error,{load_failed,"Failed to load NIF library debug/erl_nif: '找不到指定的模块。'"}}

dll是其它同事用vs2013编译的, 我本地安装的是vs2015, 所以首先想到的是, 是否与vs版本有关.debug版本的dll依赖的dll需要对应的vs版本才行. 为了验证这个想法, 使用release版本的dll, 结果还是失败了, 不过原因有些不同:

9> erlang:load_nif("release/erl_nif",0).
{error,{bad_lib,"Library module name 'erl_nif' does not match calling module 'erl_eval'"}}

错误提示,至少说明dll文件找到了, 上一步的推测应该是正确的. 但问题还是没有解决, 猜想有可能是编译生成release版本dll本身有问题, 但其它同事项目启动却正常, 说明dll正常加载了.但更奇怪的是, 在其它同事电脑上shell中直接调用上面的函数,都失败了(错误原因有所不同) 没办法,只能去翻看erlang文档中erlang:load_nif/2的描述, 竟然真找到了答案:
The call to load_nif/2 must be made directly from the Erlang code of the module that the NIF library belongs to.
load_nif/2竟然只能从代码中调用, 也就是说在shell中调用无效. 直接将项目中用到的debug版本替换成release版本, 项目程序正常启动了.

crash dump文件查看

1.使用下面的命令打开查看器 
crashdump_viewer:start().
官网地址:http://www.erlang.org/doc/apps/erts/crash_dump.html

2.网上有资料说webtool里可以看到,我在本地(R17)测试发现没有找到crashdumpviewer
1> webtool:start().
WebTool is available at http://localhost:8888/
Or http://127.0.0.1:8888/

3.先打开observer:start(),然后File->Examine Clashdump

4.生成crash dump文件的方法
1)直接调用erlang:halt("abort")
2)还有一种不弄崩溃erl的方法,参考http://www.bubuko.com/infodetail-511433.html

关于rebar eunit不能自动执行单元测试的问题

在win7在使用rebar执行单元测试时提示:
E:\my_project\server\trunk>rebar.cmd eunit
==> trunk (eunit)
There were no tests to run.

单元测试时代码类似这样:
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
simple_test() ->
%do something
ok.
-endif.

手动调用Mod:test()显示单元测试正常, 尝试将单元测试代码单独存放在文件中也是如此

$ ./rebar -V
rebar 2.5.1 R14B04 20140730_173812 git 2.5.1-1-ge9f62c4

更新至最新版本, 还是如此
$ ./rebar -V
rebar 2.6.2 18 20160704_085227 No VCS info available.

奇怪的是同样的代码在linux上执行./rebar eunit是可以自动执行单元测试例程的, 只能说明本地环境有问题
本环境为: window 7 + R18

关于rebar 编译c文件

rebar编译c/cpp相关的代码在rebar_port_compiler.erl文件中.

1. 如何生成_.dll/_.so, 或者exe/无扩展名(linux)

rebar支持c/cpp文件的编译, 不过需要在rebar.config文件中加上一些配置, 如下:

{port_specs, [
    %{"win", "priv/test.dll",["c_src/test.c"]},
    {"priv/test.so",["c_src/test.c"]}
]}.

上面注释的那一行是可有可无的, 这也是我翻看到rebar的源码文件才知道. 我们无需指定平台win/linux, 而且rebar检测到平台是windows的话,会自动将test.so,改成test.dll.如果扩展名为空,在windows上则会自动加上.exe扩展名. 上面的配置中,源文件不支持通配符,必需一个一个的写

2. 使用的编译器

rebar在windows上默认使用的编译器是vc++, 所以直接安装vs最方便.安装好后,我们还需要在环境变量中加上vc++的相关执行目录.另外默认情况下vc++没的把相关标准头文件目录等加到环境变量中,但其提供一个批处理文件
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat 来设置所需要的环境变量
所以我们在系统环境变量中加上:

C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin;
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC;

然后,在调用rebar compile之前, 调用一下vcvarsall.bat

当然也可以不使用默认编译器,在rebar.config配置使用gcc或者其它编译器,不过配置项比较多,稍显麻烦,示例:

{port_env, [{"win32", "CC", "gcc.exe"},
            {"win32", "LINKER", "ld.exe"},
            {"win32", "CFLAGS", "-O3 -march=native $ERL_CFLAGS"},
            {"win32", "DRV_CFLAGS", "-g -Wall $ERL_CFLAGS"},
            {"win32", "DRV_LDFLAGS", "-shared $ERL_LDFLAGS"},
            {"win32", "DRV_CC_TEMPLATE", "$CC -c $CFLAGS $DRV_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE"},
            {"win32", "DRV_LINK_TEMPLATE", "$LINKER $PORT_IN_FILES $LDFLAGS $DRV_LDFLAGS -o $PORT_OUT_FILE"},
            {"win32", "ERL_LDFLAGS", " -L$ERL_EI_LIBDIR -lerl_interface -lei"}
           ]}.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.