この例では、作成者は次のようにしてデッドロック状態を回避します。
self() ! {start_worker_supervisor, Sup, MFA}
彼の gen_server の init 関数で。私は自分のプロジェクトの 1 つで同様のことを行いましたが、この方法は眉をひそめ、代わりにすぐにタイムアウトを発生させる方がよいと言われました。受け入れられたパターンは何ですか?
この例では、作成者は次のようにしてデッドロック状態を回避します。
self() ! {start_worker_supervisor, Sup, MFA}
彼の gen_server の init 関数で。私は自分のプロジェクトの 1 つで同様のことを行いましたが、この方法は眉をひそめ、代わりにすぐにタイムアウトを発生させる方がよいと言われました。受け入れられたパターンは何ですか?
gen_statem
新しい動作の使用を検討してください。この動作は、FSM内部のイベントの生成をサポートします。
状態関数は、action()next_eventを使用してイベントを挿入でき、そのようなイベントは、状態関数に提示する次のイベントとして挿入されます。つまり、それが最も古い着信イベントであるかのようになります。このようなイベントには専用のevent_type()内部を使用できるため、外部イベントと間違えることはありません。
イベントを挿入すると、たとえばgen_fsmで頻繁に使用する必要がある独自の状態処理関数を呼び出して、挿入されたイベントを他のイベントよりも先に強制的に処理するというトリックが置き換えられます。
そのモジュールのアクション機能を使用すると、特に関数でアクションをinit
作成することにより、イベントが外部イベントで生成され、常に外部イベントの前に処理されるようにすることができます。next_event
init
例:
...
callback_mode() -> state_functions.
init(_Args) ->
{ok, my_state, #data{}, [{next_event, internal, do_the_thing}]}
my_state(internal, do_the_thing, Data) ->
the_thing(),
{keep_state, Data);
my_state({call, From}, Call, Data) ->
...
...
を設計するときgen_server
は、通常、次の3つの異なる状態でアクションを実行することを選択できます。
init/1
handle_*
機能でterminate/2
経験則として、イベント(呼び出し、キャスト、メッセージなど)に基づいて動作するときに、処理関数で処理を実行することをお勧めします。initで実行されるものは、イベントを待つべきではありません。それが、ハンドルコールバックの目的です。
したがって、この特定のケースでは、一種の「偽の」イベントが生成されます。gen_server
いつもスーパーバイザーのスタートを始めたいと思っているようです。直接でやってみませんinit/1
か?間に別のメッセージを処理できるようにする必要が本当にありますか(handle_info/2
代わりにそれを実行する効果)?そのウィンドウは非常に小さいため(の開始からgen_server
メッセージの送信までの時間self()
)、発生する可能性はほとんどありません。
デッドロックについては、init関数で自分のスーパーバイザーを呼び出さないようにすることをお勧めします。それは悪い習慣です。ワーカープロセスを開始するための適切なデザインパターンは、1人のトップレベルのスーパーバイザーであり、その下にマネージャーとワーカースーパーバイザーがいます。マネージャーは、ワーカースーパーバイザーを呼び出すことによってワーカーを開始します。
[top_sup]
| \
| \
| \
man [work_sup]
/ | \
/ | \
/ | \
w1 ... wN
init/1
サーバーの初期化を2つの部分に分割することについて既に述べたことを補足するためhandle_cast/2
ですhandle_info/2
。これを行う理由は 1 つだけです。それは、初期化に時間がかかることが予想される場合です。次に、それを分割するとgen_server:start_link
、スーパーバイザーによって起動されたサーバーにとって重要になる可能性があります。これは、子の起動中に「ハング」し、子の起動が遅いと、スーパーバイザーの起動全体が遅れる可能性があるためです。
この場合、サーバーの初期化を分割するのが悪いスタイルだとは思いません。
エラーに注意することが重要です。init/1
2 番目の部分でエラーが発生すると、スーパーバイザーがその子を再起動しようとするため、エラーが発生するとスーパーバイザーが終了します。
個人的には、サーバーが自分自身にメッセージを送信する方が良いスタイルだと!
思いますgen_server:cast
.init_phase_2
タイムアウト。特に、タイムアウトが他の場所でも使用されている場合。
自分の上司に電話するのは確かに悪い考えのように思えますが、私はいつも同じようなことをしています.
init(...) ->
gen_server:cast(self(), startup),
{ok, ...}.
handle_cast(startup, State) ->
slow_initialisation_task_reading_from_disk_fetching_data_from_network_etc(),
{noreply, State}.
これは、timeout と handle_info を使用するよりも明確だと思います。起動メッセージより先にメッセージが送信されないことがほぼ保証されています (そのメッセージを送信するまで、他の誰も pid を持っていません)。他の何かにタイムアウトを使用する必要がある場合。
これは非常に効率的でシンプルな解決策かもしれませんが、良い erlang スタイルではないと思います。私は timer:apply_after を使用しています。これはより優れており、外部モジュール/gen_* とのやり取りの印象を与えません。
最良の方法は、ステート マシン (gen_fsm) を使用することだと思います。私たちの gen_srvers のほとんどは実際にはステート マシンですが、get_fsm をセットアップするための最初の作業のため、gen_srv で終わると思います。
結論として、timer:apply_after を使用してコードを明確かつ効率的にするか、gen_fsm を使用して純粋な Erlang スタイル (さらに高速) にします。
コード スニペットを読んだところですが、例自体が何らかの形で壊れています。スーパーバイザを操作する gen_srv の構造がわかりません。将来の子のプールのマネージャーであっても、これは、プロセスのメールボックス マジックを当てにせずに明示的に行う重要な理由です。これをデバッグすることも、より大きなシステムでは地獄です。
率直に言って、初期化を分割する意味がわかりません。で重労働を行うとinit
スーパーバイザがハングしますが、 を使用したり、すべてのハンドラtimeout/handle_info
にメッセージを送信しself()
たり、追加init_check
したりすると (別の可能性ですが、あまり便利ではありません)、呼び出しプロセスが効果的にハングします。では、なぜ「機能していない」gen_server を持つ「機能している」スーパーバイザーが必要なのですか? クリーンな実装には、おそらく初期化中のメッセージに対して「not_ready」応答を含める必要があります(initから完全な初期化を生成してself()
完了時にメッセージを送り返すのはなぜですか。これにより、「not_ready」ステータスがリセットされます)が、「not_ready」応答は適切に行われる必要があります呼び出し元によって処理され、これにより多くの複雑さが追加されます。返信を一時停止するのは得策ではありません。