Docsの目次

Supervisor

std.actorのSupervisorは、同じプロセス内に登録したactorやasync処理の起動・異常終了・再起動・停止を管理します。現在の再起動方式は、失敗した子だけを起動し直すone-for-oneです。ほかの子は処理を続けます。

このページの目次

workerの開始から終了までをまとめて管理したい

PythonのTaskGroupは関連するtaskの終了を待つ足場になります。workerの故障後に初期化からやり直す再起動方針は、別に設計する必要があります。NagiではSupervisorにfactoryと方針を登録します。まず、正常に一度だけ動く完全な例です。

import std.actor as actor

class Context:
    message: str

async def worker(context: shared[Context]) -> Result[unit, Error]:
    return ok(print(context.message))

async def main() -> Result[unit, Error]:
    group = actor.supervisor[Context](Context(message="worker"), actor.default_options())
    try actor.task(view(group), "once", worker, actor.RestartPolicy.TEMPORARY)
    try await actor.run(group)
    return ok(print("完了"))

worker、完了の順に表示します。runが登録した子を起動し、この子の終了と後片付けを確認してから戻ります。登録だけでは実行を始めません。

よくある間違いは、PERMANENTを「故障したときだけ再起動する」と読むことです。正常完了後も再起動します。一度だけ実行するならTEMPORARY、故障時の再起動ならTRANSIENTを選び、次の表で違いを確認してください。

たとえば一度処理して正常終了するworkerを登録する場合、次の設定は文法上有効ですが、正常終了後も繰り返し起動するため意図と合いません。

try actor.task(view(group), "once", worker, actor.RestartPolicy.PERMANENT)

一度だけ実行するならTEMPORARYへ変更します。正常完了では止め、Errorやpanicのときだけ再起動する場合はTRANSIENTを使います。Supervisorが何も起動していないと終了し、runが戻ります。

一言でいうと:Supervisorが子を所有し、方針に従って再起動と停止を管理する。 登録・監視・停止のAPIはリファレンスにあります。

再起動方針

方針 再起動する場合
RestartPolicy.TEMPORARY 再起動しない
RestartPolicy.TRANSIENT 子のErrorやpanic。actorの既定値
RestartPolicy.PERMANENT Error・panicに加え、正常に処理を終えた場合

同じ失敗でも、ハンドラーのどこで返すかで動作が変わります。

ハンドラーの戻り値 動作
Ok(Turn(state, Err(AuthError))) 認証不成立などの業務上の拒否。Turnに渡した次の状態を保存し、再起動しない
Err(Error) worker自体の失敗。再起動方針を適用する

認証不成立を401へ変換するのは、呼び出し側のHTTP mapperの役目です。返信のErr(AuthError)だけでHTTPステータスが決まるわけではありません。

再起動時には初期化関数を呼び直し、新しい状態を作ります。処理中や待機中だったメッセージを再配送しません。状態の復元が必要なら、初期化関数でDBなどから読み込みます。

既定の再起動間隔は10ms、回数はSupervisor全体で10秒間に5回までです。上限を超えると、ほかの子も停止してrunがErrorを返します。明示的な停止や親のキャンセルは再起動の理由になりません。

TEMPORARYの子が失敗すると、その子を停止して失敗を記録します。他の子は動き続けます。実行中の子や再起動待ちがなくなるとSupervisorを終了し、後片付け後のrunは記録したErrorを返します。

所有と停止

runへSupervisorを渡すと登録が締め切られます。Controlは停止と監視のためのハンドルです。Controlを破棄しても、所有されているSupervisorは動き続けます。Supervisorや実行中のrunを破棄すると、子へ停止を要求します。

scope本体が終わって子の終了を待つ段階で、spawnしたrunがErrorを返すと、同じscopeの残りの子をキャンセルして終了を待ちます。HTTPサーバーも同じscopeでspawnしていれば、その対象です。たとえば最後のTEMPORARY workerの失敗は、HTTPの終了につながります。業務上の拒否とworkerの故障を分けて返してください。

これは旧statement spawnの動作です。Task結果handleでrunを起動すると、内側の業務Result Errだけではscopeは故障しません。サービス例は親がmonitorをawaitし、Ok(inner)をtry innerでbody Errへ伝えてHTTPを取り消します。discardへの機械置換はこの接続を失います。正常shutdownはHTTPを継続します。

shutdownの成功は、子と共通データの後片付け完了を表します。Errorには、後片付け完了後に返す記録済みの子の失敗と、期限切れによる未完了の両方があります。現行APIには専用の完了状態型がなく、Errorだけでは後片付けが完了したかを区別できません。

期限内に終わらない場合は終了を追跡する記録を保持します。HTTPやnative処理が共通データを保持している間は、後片付けは完了しません。その解放がSupervisorの終了待ちに依存すると、互いに待つ構成になります。利用者側でも参照と停止の順序を設計する必要があります。Rust連携で別のTokio runtimeを使う場合は、runを開始したruntimeを後片付けまで維持します。

Tokioの停止は協調的です。yieldしないCPU処理、blockingなnative呼び出し、重いDropは強制中断できません。長い計算は分割してawait actor.yield_now()を挟むか、終了を管理できる別プロセスで実行してください。共通データを持たずに始まった外部のblocking jobなどは、このグループの終了保証に含まれません。

監視

next_eventで起動、失敗、再起動、停止を受け取れます。イベントは既定256件、診断文は最大1024 UTF-8 bytesです。読み手が遅れた場合はEventKind.LAGGEDと失われた件数が届きます。監視の遅れで子の処理を止めません。

監視を無期限に待たせたくない場合はnext_event_timeout(view(control), 5000)を使います。Err(WaitError)のkindでTIMEOUTとINVALID_TIMEOUTを区別できます。Ok(None)は列の終了だけを表します。読み取りロック待ちも期限に含まれ、キャンセル後も未読イベントを受け取れます。期限は1..4,294,967,295msかつ時計が表現できる値にします。

taskのSTARTEDはfactory本体の実行前に届きます。接続確立などの準備完了を待つ場合はtask_with_readyで登録し、初期化後にmark_ready(view(signal))を呼びます。監視側へ子の名前と世代番号付きのEventKind.READYが届きます。重複や古い世代の通知は受け付けません。actorの起動確認は既存のreadyを使います。

workerのサンプルで準備完了通知、panicからの再起動、期限付き監視、停止を確認できます。

Tokioを使うnative実装で、BEAMの隔離されたprocessやVMと同じ障害耐性はありません。VM、無停止のコード差し替え、分散配置、永続mailbox、実行中の子の追加・削除、one-for-all/rest-for-oneは未対応です。

actorの書き方 · APIリファレンス · 性能測定 · 実行できるサンプル

旧supervisor.nagiは固定workerの再起動試験です。

実装はSupervisor runtimeとライフサイクル管理にあります。再起動のテストと停止・解放のテストが、業務エラー、子の失敗、待機期限、後片付けの各条件を確認します。

Nagi 0.2.0 · Docsこのページのソース