active: true
derived: false
groups:
- BGIO
header: |
  GC時のデータロスト防止とバックグラウンドループのシャットダウン戦略
level: 1.36
links:
- ARCH006: NQNGQUULw16h1cxj2M5I4pgAr3bNlRze6AMKu_rdo0s=
- REQ006: 3PN_I3D9BWCwGe4h19vSOVkDSeWNY3sM_fbWg0rwOfk=
normative: true
ref: ''
reviewed: 8z80mQCIVu1u_9ntQmwFjfmd2Ur0hwg_wvy6MxliJ8s=
status: accepted
text: |
  # GC時のデータロスト防止とバックグラウンドループのシャットダウン戦略

  ## コンテキストと課題

  `beautyspot` の `Spot` クラスは、非同期でのキャッシュ保存タスクを構造的に直列化するため、内部で専用の `asyncio` ループを実行するバックグラウンドスレッド (`_BackgroundLoop`) を保持しています。インスタンスのライフサイクル終了時、これらのバックグラウンドタスクを安全に終了させる必要がありますが、シャットダウンのトリガー（明示的呼び出し、アプリ終了、GC）によって、メインスレッドへの影響とプロセスの振る舞いが大きく異なるという課題がありました。

  特に、GC は予測不能なタイミングで発生します。ここでタスク完了を待機（スレッドを `join`）してしまうと、メインスレッドがフリーズしたりデッドロックを引き起こす危険性があります。一方で、待機せずにタスクを強制キャンセルすると、保存待ちだったキャッシュデータが失われてしまいます。

  ## 判断基準

  * **Stability**: GC 発生時にメイン処理をブロックせず、アプリの安定性とパフォーマンスを維持すること。
  * **Data Reliability**: インスタンスがスコープを抜けた後でも、投入された保存タスクを可能な限り完了させること。
  * **Safety**: プロセス終了時には確実にデータをドレイン（排出）し、不完全な書き込みを防ぐこと。

  ## 検討した選択肢

  * **Option 1**: 常に同期的にシャットダウンする（GC 時のフリーズリスク）。
  * **Option 2**: 常に非同期でシャットダウンし、タスクをキャンセルする（データロストリスク）。
  * **Option 3**: シャットダウン方式を状況（明示的、GC、終了時）に応じて使い分ける多段的な戦略を導入する。

  ## 決定事項

  採用した選択肢: **Option 3**.

  以下の3つの方式を実装し、状況に応じて使い分けます。

  1. **`stop(wait=True)`** (完全同期シャットダウン)
      * **用途:** 明示的な `shutdown()`、`with` ブロックの終了、`atexit`。
      * **理由:** データの確定を保証するため。プロセス終了時はメインスレッドをブロックしないと OS に強制終了されるため。
  2. **`stop_gracefully_no_wait()`** (非同期ドレイン / スレッド切り離し)
      * **用途:** `weakref.finalize` による GC 時。
      * **理由:** メインスレッドのフリーズを回避しつつ、既存タスクを完了させてから自発的に終了させるため。
  3. **`stop(wait=False)`** (強制停止)
      * **用途:** エラー発生時の緊急停止など。

  ## 影響・結果

  * **ポジティブ**:
      * **堅牢性の向上**: `Spot` が一時的なスコープでインスタンス化され GC された場合でも、データロストのリスクがほぼ解消された。
      * **安全性の担保**: GC 発生時にメイン処理がブロックされない。
  * **ネガティブ**:
      * 実装が複雑になり、バックグラウンドスレッドの状態管理（状態遷移）が重要になる。
