假设我有一个gen_server回调模块,g
,代码片段如下所示:
start_link(Args) ->
gen_server:start_link(?MODULE, [Args], []).
process_packet(Ref, Packet) ->
gen_server:call(Ref, MsgPacket={process_packet, Packet}).
init(Args) ->
gen_server:cast(self(), MsgInit={init, Args}), %% delayed initialization
{ok, state_not_initialized}.
handle_call({process_packet, Packet}, #g_state{}=S) ->
{reply, Packet, S}.
handle_cast({init, Args}, _) ->
State = #g_state{} = do_init(Args),
{noreply, State}.
和另一个gen_server,t
,其工作是收听套接字,
如果收到特定的数据包,请启动g
以对数据包执行某些操作,
所以,t
中的一些代码看起来像这样:
handle_info({tcp, _Socket, Packet}, #t_state{}) ->
case g:start_link(WhatEver) of
{ok, Pid} ->
g:process_packet(Pid, Packet);
_ ->
not_interested
end.
让g
的pid为PidG
,t
的pid为PidT
。
我的问题是,MsgPacket
(由PidT
发送到PidG
)是否有可能在PidG
之前到达MsgInit
(由{{1}发送自己)?如果发生这种情况,PidG
会崩溃,因为PidG
与state_not_initialized
#g_state{}
g
中的handle_call
不匹配。
我的猜测是完全有可能,但我没有想出一种方法来产生这种情况。理想情况下,您可以减慢消息传输速度MsgInit
,但我怀疑Erlang允许我做这种事情。知道如何让MsgPacket
在MsgInit
之前到达吗?
修复相对容易,(假设我的猜测是正确的),receive
ack
PidG
do_init
在{{ 1}}在PidT
启动之后,在进行gen调用之前。
更新
假设我的猜测是正确的,为了使问题更具体,如何使g
崩溃启动其中一个进程? (根据zxq9'示例修改)
kickoff_many/1
答案 0 :(得分:0)
是的,有可能。您可以知道相对于进程A和B的消息序列,但是您无法知道相对于任何其他两个进程和 A和B的消息序列(意思是,您可以& “知道各种消息流将被交错的顺序”,这也意味着你不知道从A到B,从B到B的消息的相对顺序。
如何避免这种情况?最直接的方法是让你的进程T 生成进程G,如果我还不存在。或者总是生成G来处理处理作业。或者处理本身的处理。或者使套接字监听器不是gen_server ,而是一个纯粹的Erlang OTP进程(查找proc_lib - 我发现套接字监听器更加顺畅)和gen_server世界之间的奇怪冲突并且可以使初始化套接字处理程序进程的某些方面的必要性消失。
总结一下:
proc_lib
。<强>更新强>
如果您没有刷新邮箱并且选择接收搜索您神奇的第一条消息,可以发生。
这里我们有一个垃圾邮件流程,它会向即将推出的gen_server发送一条bajillion消息:
-module(spawn_spammer).
-export([kickoff/0]).
kickoff() ->
Spammer = start(),
Catcher = spawn_catcher:start(),
{Spammer, Catcher}.
start() ->
ok = io:format("~tp ~tp: Starting up.~n", [self(), ?MODULE]),
spawn(fun() -> loop() end).
loop() ->
try
spawn_catcher ! {self(), test_spam}
catch
_:_ -> io:format("~tp ~tp: Missed.~n", [self(), ?MODULE])
end,
receive
cut_it_out ->
ok;
Unexpected ->
io:format("~tp ~tp: Unexpected message ~tp~n", [self(), ?MODULE, Unexpected]),
loop()
after 0 ->
loop()
end.
这里我们有gen_server,尝试用自己进行(假设)安全的延迟初始化:
-module(spawn_catcher).
-behavior(gen_server).
-export([start/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
start() ->
ok = io:format("~tp ~tp: Starting up.~n", [self(), ?MODULE]),
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
init(_) ->
ok = io:format("~tp ~tp: Blank initialization.~n", [self(), ?MODULE]),
gen_server:cast(self(), get_ready),
{ok, not_ready}.
handle_call(Message, From, State) ->
ok = io:format("~tp ~tp: Unexpected call: ~tp from ~tp~n", [self(), ?MODULE, Message, From]),
{noreply, State}.
handle_cast(get_ready, not_ready) ->
ok = io:format("~tp ~tp: Getting ready~n", [self(), ?MODULE]),
{noreply, ready};
handle_cast(Message, State) ->
ok = io:format("~tp ~tp: Unexpected call: ~tp~n", [self(), ?MODULE, Message]),
{noreply, State}.
handle_info(Message, not_ready) ->
ok = io:format("~tp ~tp: DANGEROUS MESSAGE: ~tp~n", [self(), ?MODULE, Message]),
{noreply, not_ready};
handle_info({Spammer, test_spam}, ready) ->
ok = io:format("~tp ~tp: Got first proper message. Sending reply.~n", [self(), ?MODULE]),
Spammer ! cut_it_out,
{stop, normal, ready};
handle_info(Message, ready) ->
ok = io:format("~tp ~tp: Unexpected message: ~tp~n", [self(), ?MODULE, Message]),
{noreply, ready}.
terminate(_, _) -> ok.
code_change(_, State, _) -> {ok, State}.
以下是shell中的表现:
1> spawn_spammer:kickoff().
<0.33.0> spawn_spammer: Starting up.
<0.33.0> spawn_catcher: Starting up.
<0.99.0> spawn_spammer: Missed.
<0.99.0> spawn_spammer: Missed.
<0.100.0> spawn_catcher: Blank initialization.
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
{<0.99.0>,{ok,<0.100.0>}}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: DANGEROUS MESSAGE: {<0.99.0>,test_spam}
<0.100.0> spawn_catcher: Getting ready
<0.100.0> spawn_catcher: Got first proper message. Sending reply.
这非常不可能但可以发生。
答案 1 :(得分:0)
简短回答。不,这是不可能的。
这种技术有时称为延迟初始化,经常使用。
答案很长。不,如果您使用gen_server而不使用选择性接收,则无法实现。
来自gen_server docs:
gen_server进程调用Module:init / 1进行初始化。为确保同步启动过程,在Module:init / 1返回之前,start_link / 3,4不会返回。
你应该考虑的事情如下。
当g:init / 1正在运行时,谁知道PidG?只有PidG过程本身。启动PidG的进程只有在PidG从g:init / 1返回后才会从gen_server:start / x返回。
确定后,我们知道{init,Args}是PidG消息队列中的第一条消息。由于我们正在使用gen_server(通常)逐个处理消息,因此您可以确保PidG也将处理消息{init,Args}作为第一个消息。
此时PidG和PidT之间没有互动。
编辑:删除有关返回{ok,State,0}和接收'timeout'的提及,因为它会因为创建竞争条件而失败(请参阅gen_server.erl)。只有一种安全的方式:self()!如果您没有在Module:init / x。
中发送pid,请执行以下操作