エラー処理
エラー処理
フックはそれが観察するリクエストの内部で走るので、その失敗がどう扱われるかが重要になります。このページは正確な方針を示します。
2 つの方針
hooks_raise はフックが例外を投げたときの挙動を制御します。
hooks_raise |
挙動 |
|---|---|
True(既定) |
フックの例外が呼び出しの外へ伝播します。 |
False |
フックは RuntimeWarning とともにスキップされ、呼び出しは続行します。 |
これはインスタンスごとに設定でき、呼び出しごとに上書きできます(hooks_raise= を predict_batch、system_one、Router.route、Router.predict に指定)。呼び出しごとの None は「インスタンスの値を使う」という意味です。
# strict: a broken audit hook fails the request
laya.load("convaiinnovations/laya", on_predict_end=audit, hooks_raise=True)
# lenient: telemetry must never take down a served request
laya.load("convaiinnovations/laya", on_predict_end=metrics, hooks_raise=False)
dispatch は Exception を捕捉します。Exception でないもの(BaseExceptionを参照)は、hooks_raise=False であっても決して握りつぶされません。
何かが失敗したときに何が走るか
predict のライフサイクルは try / except / finally で包まれているので、後始末のフックは失敗時にも走ります。
try:
on_predict_start
inference
except BaseException as exc:
ctx.error = exc
on_error (best effort; cannot mask exc)
raise
finally:
elapsed_ms, usage
on_predict_end (best effort; cannot mask exc on the failure path)
イベントとランタイムごとの失敗行列:
| イベント | ランタイム | 例外を投げたとき |
|---|---|---|
on_predict_start |
Agent / Router | hooks_raise=True:on_error と on_predict_end はそれでも走り、その後で例外が伝播します。False:警告して続行します(例外を投げる前の変更は残ります)。 |
| 推論 | Agent / Router | ctx.error が設定され、on_error が走り、on_predict_end が走り、例外が伝播します。 |
on_error |
Agent / Router | 元の例外を決して覆い隠しません。__context__ として連鎖されます。 |
on_predict_end(成功経路) |
Agent / Router | hooks_raise=True:伝播します(結果は計算済みなのに呼び出しは失敗します)。False:警告します。 |
on_predict_end(失敗経路) |
Agent / Router | 元の例外を決して覆い隠しません。__context__ として連鎖されます。 |
on_route |
Router | 直接伝播します。predict コンテキストはまだありません。 |
on_load |
Router | 直接伝播します。チェックポイントは構築済みのまま常駐します。 |
on_evict |
Router | 直接伝播します。チェックポイントはすでに解放されています。 |
知っておく価値のある帰結:
- 失敗した
on_loadはモデルをキャッシュに残すので、次のloadはon_loadを再度発火せずにそれを返します。 - 成功経路での失敗した
on_predict_endは、推論が成功したにもかかわらず、呼び出し元が結果ではなく例外を受け取ることを意味します。純粋な副作用である end フックにはhooks_raise=Falseを使ってください。
例外チェーン
別の例外がすでに伝播しているときにフックが失敗すると、元の例外が再送出され、フックの例外は __context__ として付加されます。根本原因が失われることはありません。
class BadTelemetry:
def on_error(self, ctx):
raise RuntimeError("telemetry down")
try:
agent.system_one(state, questions, hooks=[BadTelemetry()])
except RuntimeError as exc:
assert exc.__context__ is not None # the telemetry failure
同じ規則が、失敗経路での失敗した on_predict_end にも当てはまります。
BaseException
dispatch は BaseException ではなく Exception を捕捉するので、KeyboardInterrupt と SystemExit は必ず伝播します。それらは predict ライフサイクルの except BaseException 分岐を依然として引き起こすので、プロセスが巻き戻る前に on_error と on_predict_end が走ります。割り込みのレイテンシが気になるなら、これらのフックを速くノンブロッキングに保ってください。
設定エラー
不正な設定は、推論の前に TypeError で即座に失敗します。
| ケース | 発生箇所 | 例 |
|---|---|---|
| インスタンスではなくクラス | 構築時 | hooks=[MyHook] |
| ライフサイクルメソッドがない | 構築時 | hooks=[object()] |
| callable でないイベント | 構築時 | on_predict_start = 5 |
| callable でない便宜フック | 構築時 | on_predict_start=123 |
hooks= に素の callable |
構築時 | hooks=[lambda ctx: None] |
呼び出しごとのフックは呼び出し時点で検証されるので、不正な呼び出しごとのフックは構築時ではなく predict/system_one から TypeError を投げます。
警告
hooks_raise=False では、失敗したフックごとに、フックの型とイベントを名指しする RuntimeWarning が 1 つ出ます。
laya: hook Metrics.on_predict_end failed: connection reset
警告はフック定義ごとではなく失敗ごとに 1 回出るので、負荷時に不安定なフックはうるさくなりえます。それが問題なら、フックの内部で集約するかレート制限してください。
タイムアウト
hooks_timeout は各フック呼び出しを秒単位で制限します。制限後も走っているフックはフックの失敗として扱われます。hooks_raise=True なら TimeoutError、False なら RuntimeWarning です。None(既定)は制限なしを意味します。
laya.load("convaiinnovations/laya", on_predict_end=metrics, hooks_timeout=2.0)
インスタンスごとに設定でき、predict_batch、system_one、Router.route、Router.predict、ONNXAgent.system_one で呼び出しごとに上書きもできます。値は正でなければなりません。0 や負の数は、長さゼロの join で競合するのではなく、設定した時点で ValueError を投げます。
時間制限付きのフックは、呼び出し元の contextvars コンテキストのコピー内の worker スレッドで走るので、呼び出し元が設定したリクエスト id やトレーシング span がフックから見えます。
正直な注意点が 1 つあります。Python はスレッドを中断できないので、タイムアウトしたフックはバックグラウンドで走り続けます。タイムアウトが制限するのはリクエストが待つ時間であり、フックが生きる時間ではありません。配信中のリクエストを応答性よく保つために使い、作業を取り戻すためには使わないでください。ハングしうるフックには、下位の呼び出しにも独自のタイムアウト(ソケットや HTTP のタイムアウト)を与えてください。スレッドは回収できないので、毎回ハングするフックは呼び出しごとにスレッドを増やします。ハングしうるフックには hooks_timeout で止めることを当てにせず、独自の制限を与えてください。
非同期フックでは、コルーチンはイベントループ上で走ります。呼び出し側のタイムアウトは制限後に返りますが、コルーチンはループ上で走り続けます。
タイムアウトは hooks_concurrent=False のロックも解放します。dispatch は制限までしかフックを待たず、その後は進み、タイムアウトしたフックはロックの外で走り続けます。つまりロックが直列化するのは間に合って終わるフックであり、開始されたすべてのフックではありません。超過したフックは後ろのフックをブロックしなくなります。
方針の選び方
| フックの種類 | 推奨 | 理由 |
|---|---|---|
| ポリシー / ガードレール / 秘匿化 | hooks_raise=True |
黙って失敗するポリシーはセキュリティホールです。 |
| 監査 / ログ | テストでは hooks_raise=True、本番ではしばしば False |
監査記録の喪失は大声であるべきですが、必ずしも致命的である必要はありません。 |
| メトリクス / トレーシング | hooks_raise=False |
可観測性がリクエストを失敗させてはいけません。 |
| キャッシュの読み書き | hooks_raise=True |
壊れたキャッシュは表面化すべきで、黙ってミスしてはいけません。 |
混在させられます。ガードレールはインスタンス既定で入れ、テレメトリフックには独自の try/except を与える、あるいは呼び出しごとに別々の hooks_raise を使えます。
関連項目
- ライフサイクル:
try/except/finallyの形を文脈のなかで。 - パターンとアンチパターン:エラー処理に関するよくある間違い。