Erlang/OTP 两三事
gen_server
提纲
-
gen_server 概述
-
gen_server 实例
-
gen_server 源码
-
gen_server 陷阱
-
gen_server 技巧
gen_server 概述
gen_server 是Erlang 体系中最常用的behavior
& 使用module:
rpc net_kernel file_server etc.
& 使用场景:
1, gen_server behavior 通常用在负责资源分配的进程, client process 请求server process将会获得相应的资源;
2, 作为long-lived process 角色用于维护特定的资源, 如DB connections, ets table etc;
3, 其他: 并发控制, 锁.
gen_server 概述
server 和client 两种角色:
1, server 负责维护资源和提供接口
2, client 通过server 提供的接口访问资源
notice:
1, server 和 client 都属于Erlang process
2, 接口调用通过Erlang message 实现
gen_server 概述

提纲
-
gen_server 概述 (X)
-
gen_server 实例
-
gen_server 源码
-
gen_server 陷阱
-
gen_server 技巧
gen_server 实例
NB! 文档
gen_server:start_link 接口定义
init/1 callback 定义
%% interface
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%% callback
init([]) ->
{ok, #state{}, ?HIBERNATE_TIMEOUT}.
gen_server 实例
NB! 文档
gen_server:call/2,3 接口定义
handle_call 几种返回形式
%% interface
%% gen_server client need a new ets table
new_ets() ->
gen_server:call(?SERVER, create_ets_table).
%% callback
%% gen_server server process will execute new op and keep the ets table alive
handle_call(create_ets_table, _From, State) ->
EtsTable = ets:new(ets_table, [public]),
{reply, EtsTable, State, ?HIBERNATE_TIMEOUT};
gen_server 实例
%% interface
%% gen_server client is very lazy, would not execute logic by itself
check_method_valid(MethodName) ->
gen_server:call(?SERVER, {if_method_valid, MethodName}).
%% callback
%% then gen_server server help client
handle_call({if_method_valid, MethodName}, _From, State) ->
case MethodName of
'call' -> {reply, true, State, ?HIBERNATE_TIMEOUT};
'cast' -> {reply, true, State, ?HIBERNATE_TIMEOUT};
_ -> {reply, false, State, ?HIBERNATE_TIMEOUT}
end;
gen_server 实例
%% interface
%% gen_server client has no resource which server had, so has to call server
push_msg(Target, Msg) when erlang:is_pid(Target) ->
gen_server:cast(?SERVER, {push_msg, Target, Msg}).
%% callback
%% gen_server server do
handle_cast({push_msg, Target, Msg}, State) ->
erlang:send(Target, Msg),
{noreply, State, ?HIBERNATE_TIMEOUT};
NB! 文档
gen_server:cast/2 接口定义
handle_cast 几种返回形式
提纲
-
gen_server 概述 (X)
-
gen_server 实例 (X)
-
gen_server 源码
-
gen_server 陷阱
-
gen_server 技巧
gen_server 源码
仅对主要 code 做概要性阐述
若有问题, 可详细交流
主要蓝本:
http://www.cnblogs.com/--00/tag/gen_server/

gen_server 源码
gen module 是gen_server, gen_fsm module 的基础
主要实现了以下函数:
start/5 用于spawn 匿名gen_server 进程
start/6 用于spawn 命名gen_server 进程
call/3, call/4 用来处理gen_server:call/N 请求
reply/2 函数
gen_server 源码
spec of `start` function from gen module:
start(GenMod, LinkP, Name, Mod, Args, Options)
GenMod :: gen_server | gen_fsm
LinkP :: nolink | link
Name :: {local, atom()} | {global, term()} | {via, atom(), term()} %% 指定的name
Mod :: atom() %% 就是使用了gen_server behaviour 的模块名
Args :: term() %% Mod 模块 init func 的初始参数
Options :: [{timeout, Timeout} | {debug, [Flag]} | {spawn_opt, OptionList}]
%% 这个参数是用来指定创建进程的参数
Flag :: trace | log | {logfile, File} | statistics | debug
gen_server 源码
gen_server 的主体module 主要包括:
- start
- main loop
其中start 是由gen module 下的init_it 函数调用 gen_server:init_it/6 触发start 的
init_it(Starter, self, Name, Mod, Args, Options) ->
%% 注意, 如果使用nolink start 时, Parent 就是自己
init_it(Starter, self(), Name, Mod, Args, Options);
init_it(Starter, Parent, Name0, Mod, Args, Options) ->
Name = name(Name0),
Debug = debug_options(Name, Options),
case catch Mod:init(Args) of
{ok, State} ->
proc_lib:init_ack(Starter, {ok, self()}), %% 向调用者返回结果
loop(Parent, Name, State, Mod, infinity, Debug);
{ok, State, Timeout} ->
proc_lib:init_ack(Starter, {ok, self()}),
loop(Parent, Name, State, Mod, Timeout, Debug);
…………
gen_server 源码
gen_server 的主体module start :

gen_server 源码
执行gen_server:init_it/6 之后, 会进入loop :

gen_server 源码
触发gen_server terminate 的场景 :

gen_server 源码
long-lived process
优点:
1, 便于维护资源
2, ...
缺点:
1, 资源回收
2, 单进程瓶颈
In general, Erlang processes are expected to short-lived and have small amounts of live data. When there is not enough free memory in the heap for a process, it is garbage collected, and if less memory can be freed than required it grows. Each process’ heap is garbage collected independently, when a process exits, its memory is simply reclaimed.
gen_server 源码
hibernate
主要用于long-lived 进程,最典型的就是gen_server 进程了.
short-lived 进程资源会随其退出轻松被回收.
通过整理进程的stack,回收进程的heap 来达到回收内存节省资源的效果
- 用于某进程可能会在未来短期内空闲时
- hibernate 会清空进程的stack 然后进程GC

gen_server 源码
hibernate on mochiweb
This sounds reasonable - let's try hibernating after every message and see what happens.

gen_server 源码
hibernate 缺点
Note that emptying the call stack means that any surrounding catch is removed and has to be re-inserted after hibernation.
需要CPU, 可能带来不必要的empty-reinsert 操作.
gen_server 源码
hibernate on ejabberd
Ejabberd 对hibernate 的使用比较谨慎, 只有在进程未收到任何信息一段时间后, 才使用hibernate .
handle_call(create_ets_table, _From, State) ->
EtsTable = ets:new(ets_table, [public]),
{reply, EtsTable, State, ?HIBERNATE_TIMEOUT};
handle_info(timeout, State) ->
proc_lib:hibernate(gen_server, enter_loop,
[?MODULE, [], State]),
{noreply, State, ?HIBERNATE_TIMEOUT};
gen_server 源码
hibernate on ejabberd
实现原理:
handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod) ->
Result = try_handle_call(Mod, Msg, From, State),
case Result of
%% ...
{ok, {reply, Reply, NState, Time1}} ->
reply(From, Reply),
loop(Parent, Name, NState, Mod, Time1, []);
%% ...
{ok, {noreply, NState, Time1}} ->
loop(Parent, Name, NState, Mod, Time1, [])
%% ...
end;
loop(Parent, Name, State, Mod, Time, Debug) ->
Msg = receive
Input ->
Input
after Time ->
timeout
end,
decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, false).
gen_server 源码
hibernate on RabbitMQ
使用更为彻底, 更加谨慎
直接在自己实现的gen_server 版本中(gen_server2) 强制使用hibernate, 且与ejabberd的使用方式类似, 都是在进程未收到任何信息一段时间后, 才使用hibernate .
init(Q) ->
process_flag(trap_exit, true),
?store_proc_name(Q#amqqueue.name),
{ok, init_state(Q#amqqueue{pid = self()}), hibernate,
{backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE},
?MODULE}.
提纲
-
gen_server 概述 (X)
-
gen_server 实例 (X)
-
gen_server 源码 (X)
-
gen_server 陷阱
-
gen_server 技巧
gen_server 陷阱
- 资源不易回收 (X)
- 单进程瓶颈

每个Erlang Process 默认获得的资源是相同的
gen_server 陷阱
- 单进程瓶颈 - noblock call

gen_server 陷阱
- 单进程瓶颈 - noblock call - rpc
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 client call via spawn a new process for temporary
gen_server 陷阱
- 单进程瓶颈 - noblock call - rpc
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;
return the result to client by `gen_server:reply/2`
gen_server 陷阱
- 单进程瓶颈 - noblock call - net_kernel
handle_call({spawn_opt,M,F,A,O,L,Gleader},{From,Tag},State) when is_pid(From) ->
do_spawn([L,{From,Tag},M,F,A,Gleader],O,State);
do_spawn(SpawnFuncArgs, SpawnOpts, State) ->
[_,From|_] = SpawnFuncArgs,
case catch spawn_opt(?MODULE, spawn_func, SpawnFuncArgs, SpawnOpts) of
{'EXIT', {Reason,_}} ->
async_reply({reply, {'EXIT', {Reason,[]}}, State}, From);
{'EXIT', Reason} ->
async_reply({reply, {'EXIT', {Reason,[]}}, State}, From);
_ ->
{noreply,State}
end.
spawn_func(link,{From,Tag},M,F,A,Gleader) ->
link(From),
gen_server:reply({From,Tag},self()), %% ahhh
group_leader(Gleader,self()),
apply(M,F,A);
spawn_func(_,{From,Tag},M,F,A,Gleader) ->
gen_server:reply({From,Tag},self()), %% ahhh
group_leader(Gleader,self()),
apply(M,F,A).
gen_server 陷阱
- 单进程瓶颈 - pool

提纲
-
gen_server 概述 (X)
-
gen_server 实例 (X)
-
gen_server 源码 (X)
-
gen_server 陷阱 (X)
-
gen_server 技巧
gen_server 技巧
- 使用system msg debug
- 预分配合适的资源
- 适当干预资源回收
- 使用task process
- gen_server pool
提纲
-
gen_server 概述 (X)
-
gen_server 实例 (X)
-
gen_server 源码 (X)
-
gen_server 陷阱 (X)
-
gen_server 技巧 (X)
参考
Q&A
Erlang/OTP-gen_server
By redink
Erlang/OTP-gen_server
- 6,182