私が見た 1 つのアプローチは、タイムアウトinit/1
とhandle_info/2
.
init(Args) ->
{ok, {timeout_init, Args} = _State, 0 = _Timeout}.
...
handle_info( timeout, {timeout_init, Args}) ->
%% do your inicialization
{noreply, ActualServerState}; % this time no need for timeout
handle_info( ....
ほとんどすべての結果は、追加のタイムアウト パラメータを使用して返すことができます。これは基本的に別のメッセージを待つ時間です。時間が経過するhandle_info/2
と、呼び出され、timeout
アトムとサーバーの状態で呼び出されます。私たちの場合、タイムアウトが 0 の場合、gen_server:start
終了前でもタイムアウトが発生するはずです。handle_info
これは、サーバーの pid を他の人に返す前に呼び出す必要があることを意味します。したがって、これtimeout_init
はサーバーへの最初の呼び出しであり、他の処理を行う前に初期化を完了するという保証を与える必要があります。
このアプローチが気に入らない場合 (あまり読みにくい場合) は、init/1
init(Args) ->
self() ! {finish_init, Args},
{ok, no_state_yet}.
...
handle_info({finish_init, Args} = _Message, no_state_yet) ->
%% finish whateva
{noreply, ActualServerState};
handle_info( ... % other clauses
繰り返しますが、初期化を終了するメッセージがこのサーバーにできるだけ早く送信されるようにしています。これは、いくつかのアトムの下に登録する gen_servers の場合に非常に重要です。
EDIT OTPソースコードをさらに注意深く調べた後。
このようなアプローチは、pid を介してサーバーと通信する場合に十分です。init/1
主な理由は、関数が戻った後に pid が返されるためです。ただし、同じ名前でプロセスを自動的に登録する場合や、プロセスを自動的に登録する場合gen_..
は少し異なります。発生する可能性のある競合状態が 1 つあります。これについてもう少し詳しく説明します。start/4
start_link/4
プロセスが登録されている場合、通常、次のようにすべての呼び出しとサーバーへのキャストが簡素化されます。
count() ->
gen_server:cast(?SERVER, count).
は通常、モジュール名 (アトム?SERVER
) であり、この名前の下で登録された (そして生きている) プロセスになるまで問題なく動作します。そしてもちろん、内部では、これcast
は で送信される標準の Erlang のメッセージ!
です。init
with で行うのとほとんど同じですself() ! {finish ...
。
しかし、私たちの場合、もう 1 つ仮定します。登録部分だけでなく、サーバーが初期化を完了したことも確認します。もちろん、メッセージ ボックスを扱っているので、処理にかかる時間はそれほど重要ではありませんが、どのメッセージを最初に受け取るかが重要です。正確には、メッセージを受信finish_init
する前にメッセージを受信したいと考えていcount
ます。
残念ながら、そのようなシナリオが発生する可能性があります。これは、コールバックが呼び出される前gen
に OTP 内の が登録されているためです。 init/1
したがって、理論的には、あるプロセスがstart
登録部分に進む関数を呼び出している間、別のプロセスがサーバーを見つけてcount
メッセージを送信し、その直後にinit/1
関数がfinish_init
メッセージで呼び出されます。可能性は小さい (非常に小さい) ですが、それでも発生する可能性があります。
これには3つの解決策があります。
まずは何もしないことです。このような競合状態の場合、handle_cast
関数句が原因で (状態がnot_state_yet
アトムであるため) 失敗し、スーパーバイザーはすべてを再起動するだけです。
2 番目のケースは、この悪いメッセージ/状態インシデントを無視することです。これは簡単に達成できます
... ;
handle_cast( _, State) ->
{noreply, State}.
あなたの最後の句として。残念ながら、テンプレートを使用するほとんどの人は、そのような不幸な (IMHO) パターンを使用します。
それらの両方で、1 つのメッセージを失う可能性がありcount
ます。それが本当に問題である場合は、最後の句を次のように変更して修正を試みることができます
... ;
handle_cast(Message, no_state_yet) ->
gen_server:cast( ?SERVER, Message),
{noreply, no_state_yet}.
しかし、これには他にも明らかな利点があります。私は「失敗させる」アプローチを好みます。
3 番目のオプションは、少し後でプロセスを登録することです。start/4
自動登録を使用して要求するのではなく、 を使用してstart/3
pid を受け取り、自分で登録します。
start(Args) ->
{ok, Pid} = gen_server:start(?MODULE, Args, []),
register(?SERVER, Pid),
{ok, Pid}.
このようにしてfinish_init
、登録前、および他の人が送信してcount
メッセージを送信する前にメッセージを送信します。
しかし、そのようなアプローチには独自の欠点があり、主に登録自体がいくつかの異なる方法で失敗する可能性があります。OTP がそれをどのように処理するかを常に確認し、このコードを複製することができます。しかし、これは別の話です。
したがって、最終的には、何が必要か、または本番環境でどのような問題が発生するかによって異なります。何が悪いのかを考えておくことは重要ですが、個人的には、そのような競合状態に実際に苦しむまで、それを修正しようとはしません.