✓=レビュー済 ○=未レビュー ⚠=Suspect(複数同時表示あり。IDクリックで詳細へ)
| グループ | REQ | ARCH | SPEC | TST | IMPL | ADR |
|---|---|---|---|---|---|---|
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... | ADR005 ✓ # Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... | ADR005 ✓ # Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... | ADR024 ✓ # Non-blocking Cache Persistence and Task Tracking ## Context and Problem State... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... | ADR033 ✓ # 非同期保存処理におけるエラー可視化とコンテキストの導入 ## Context and Problem Statement / コンテキスト beauty... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... | ADR036 ✓ # バックグラウンドループのライフサイクル管理とGC時のデータロスト対策 ## Context and Problem Statement / コンテキスト ... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... | ADR037 ✓ # GC時のデータロスト防止とバックグラウンドループのシャットダウン戦略 ## Context and Problem Statement / コンテキスト ... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... | ADR040 ✓ # Background Loop Shutdown Strategy and Threading Model ## Context and Problem ... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... | ADR047 ✓ # Background Loop Shutdown Strategy on Garbage Collection ## Context and Proble... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... | ADR049 ✓ # 実行中タスクの強参照セットによる追跡 ## Context and Problem Statement / コンテキスト `Spot` インスタンスは、... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... | ADR053 ✓ # Background Loop and Executor Shutdown Sequence ## Context and Problem Stateme... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC014 ✓ ## インターフェース ```python class _BackgroundLoop: def submit(self, coro: Corouti... | TST014 ✓ ## 目的 `_BackgroundLoop` はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコル... | IMPL014 ✓ ## 実装概要 `_BackgroundLoop` クラスが非同期タスクのバックグラウンド実行を管理する。 初期化時に `asyncio.new_event_... | ADR001 ✓ # Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... | ADR024 ✓ # Non-blocking Cache Persistence and Task Tracking ## Context and Problem State... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... | ADR033 ✓ # 非同期保存処理におけるエラー可視化とコンテキストの導入 ## Context and Problem Statement / コンテキスト beauty... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... | ADR036 ✓ # バックグラウンドループのライフサイクル管理とGC時のデータロスト対策 ## Context and Problem Statement / コンテキスト ... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... | ADR037 ✓ # GC時のデータロスト防止とバックグラウンドループのシャットダウン戦略 ## Context and Problem Statement / コンテキスト ... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... | ADR040 ✓ # Background Loop Shutdown Strategy and Threading Model ## Context and Problem ... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... | ADR047 ✓ # Background Loop Shutdown Strategy on Garbage Collection ## Context and Proble... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... | ADR049 ✓ # 実行中タスクの強参照セットによる追跡 ## Context and Problem Statement / コンテキスト `Spot` インスタンスは、... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... | ADR053 ✓ # Background Loop and Executor Shutdown Sequence ## Context and Problem Stateme... |
| BGIO | REQ006 ✓ キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。 | ARCH006 ✓ ## コンポーネント構成 ```mermaid graph TD A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop... | SPEC015 ✓ ## インターフェース ```python def flush(timeout: float | None = None) -> bool: ... @sp... | TST015 ✓ ## 目的 `save_sync` パラメータと `flush` / `drain` メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユ... | IMPL015 ✓ ## 実装概要 `Spot` クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。 `save_sync=False` の場合、キャッシュへの... | ADR001 ✓ # Manage Executor Lifecycle via Instance Ownership and Weak References ## Conte... |
| リンク方向 | カバー数 | カバー率 | 未カバー |
|---|---|---|---|
| ARCH → REQ | 1 / 1 | 100.0% | — |
| SPEC → ARCH | 1 / 1 | 100.0% | — |
| TST → SPEC | 2 / 2 | 100.0% | — |
| IMPL → SPEC | 2 / 2 | 100.0% | — |
| ADR → REQ | 1 / 1 | 100.0% | — |
キャッシュの保存処理をバックグラウンドで非同期に実行し、関数の応答レイテンシに影響を与えないモードを提供すること。
親: —
子: ADR001, ADR005, ADR024, ADR033, ADR036, ADR037, ADR040, ADR047, ADR049, ADR053, ARCH006
graph TD
A[core.Spot] -->|非同期タスク投入| B[_BackgroundLoop]
B -->|Daemon Thread| C[asyncio.AbstractEventLoop]
C -->|I/O| D[TaskDB / BlobStorage]
A -->|ライフサイクル制御| E[atexit / ContextManager]
| コンポーネント | 責務 | インターフェース |
|---|---|---|
| _BackgroundLoop | デーモンスレッドでのイベントループ管理とタスク投入 | submit(), run_forever() |
| core.Spot | 非同期保存のトリガーとエラーハンドリング | _save_metadata_async() |
| ContextManager | 処理終了時のタスク完了待機(Flush) | __exit__, flush() |
sequenceDiagram
participant Main as メインスレッド
participant BGLoop as _BackgroundLoop
participant Store as ストレージ
Main->>BGLoop: submit(save_coro)
Note over Main: 関数は即座に結果を返す (save_sync=False)
BGLoop->>Store: 書き込み実行 (I/O)
alt 成功
Store-->>BGLoop: OK
else 失敗
BGLoop->>Main: on_background_error(error)
end
| 技術領域 | 選定 | 理由 |
|---|---|---|
| 並行処理 | asyncio in Thread | GILを解放しつつ、多数のI/Oタスクを効率的に多重化 |
| スレッド種類 | Daemon Thread | アプリケーション終了時にプロセスをブロックしない |
| 終了制御 | atexit / drain | 強制終了時も、可能な限り保留中の書き込みを完了させる |
透過性: 保存失敗時もユーザーコードの例外にはせず、コールバック経由で通知
一貫性: flush() により、クリティカルな処理の直後での保存完了を保証
安全性: シャットダウンシーケンス中に新規タスクを受け付けない排他制御を導入
親: REQ006
class _BackgroundLoop:
def submit(self, coro: Coroutine) -> None: ...
def stop(self) -> None: ...
def is_running(self) -> bool: ...
submit 時にデーモンスレッドを作成し、asyncio.run() を開始するrun_coroutine_threadsafe を使用して、メインスレッドからコルーチンをイベントループへ投入| 内部属性 | 型 | 説明 |
|---|---|---|
_loop |
AbstractEventLoop |
スレッド内で稼働するループ本体 |
_thread |
Thread |
daemon=True 設定のスレッド |
on_background_error コールバックで処理される親: ARCH006
def flush(timeout: float | None = None) -> bool: ...
@spot.mark(save_sync=False)
def my_func(): ...
save_sync=True)save_sync=False)BackgroundLoop に委譲し、即座に関数の戻り値を返す。flush)_inflight タスク(保存中)のリストを確認し、それら全てが完了するまで、またはタイムアウトまで待機する。| パラメータ | デフォルト | 説明 |
|---|---|---|
timeout |
None |
flush の最大待機時間(秒)。None は無限待機 |
on_background_error |
logger.error |
非同期保存失敗時のエラーハンドラ |
flush がタイムアウトした場合、False を返し、一部のデータが未保存である可能性を通知する。with spot: 終了時に自動的に flush が呼ばれ、プロセス終了前のデータ保存を確実にする。親: ARCH006
_BackgroundLoop はデーモンスレッドで asyncio イベントループを駆動し、非同期キャッシュ保存を処理する。スレッド間のコルーチン受け渡しは競合状態やデッドロックが発生しやすく、シャットダウン時のタスク消失はデータロスに直結する。
submit(coro) で投入したコルーチンが正しく実行され、結果がDBとストレージに反映されることdrain_timeout 秒以内に完了すること。タイムアウト後は強制終了されることsubmit() 呼び出しが受け付けられないことreferences: tests/integration/core/test_background_loop.py
親: SPEC014
子: —
save_sync パラメータと flush / drain メカニズムは、キャッシュ保存のレイテンシとデータ安全性のトレードオフをユーザーが制御する手段である。save_sync=False 使用時にデータが失われないことと、コンテキストマネージャによる確実なフラッシュを保証する。
spot.flush(timeout) 呼び出しで全ペンディング保存が完了するまで待機すること。タイムアウト時の挙動が明確であることwith spot: ブロック終了時に未完了の保存が自動的に drain されることwith ブロックで再利用でき、各ブロック終了時に適切に drain されることon_background_error コールバックが呼ばれ、メインスレッドには影響しないことreferences: tests/integration/core/test_exit_drain.py
親: SPEC015
子: —
_BackgroundLoop クラスが非同期タスクのバックグラウンド実行を管理する。
初期化時に asyncio.new_event_loop() で新しいイベントループを作成し、
_thread(デーモンスレッド)上で loop.run_forever() を実行する。
外部からは submit() を通じてコルーチンを投入でき、run_coroutine_threadsafe で
スレッドセーフにループへ渡される。
スレッドは daemon=True とし、メインスレッド終了時にプロセスがブロックされないようにしている。
しかし、安全なリソース解放のために drain() メソッドを提供し、
投入された全タスクの完了を drain_timeout の範囲で待機する。
メインスレッドのイベントループと競合しないよう、専用のイベントループを
作成してバックグラウンドスレッドで駆動する。これにより save_sync=False 時の
保存処理などが、呼び出し元の asyncio 環境から完全に隔離される。
submit() は投入されたタスクを _tasks 集合に登録し、add_done_callback で
完了時に自身を削除することで、未完了タスクの追跡を可能にしている_lock による排他制御を行っているatexit ハンドラとしてもシャットダウンが登録されるreferences: src/beautyspot/core.py
親: SPEC014
子: —
Spot クラスにおいて、バックグラウンド書き込みの同期と完了待機を制御する。
save_sync=False の場合、キャッシュへの保存処理は _bg_loop.submit() で
バックグラウンドに投入される。これら未完了のタスクやDBキューを同期するため、
flush() およびコンテキストマネージャによる drain が実装されている。
flush(): DBライタースレッドのキュー (self.db.flush()) を空になるまで待機する__exit__: コンテキストマネージャ終了時に flush() を呼び出した上で、
さらに _bg_loop.drain() を呼び出し、非同期タスクの完了も待つ
これにより、スクリプト終了時やバッチ処理の区切りで、データの完全な永続化を保証する。バックグラウンド保存時のエラーは呼び出し元のメインフローを妨げないよう、
on_background_error コールバックに通知され、ログ出力のみ行う。
例外の再送出は行わない。
flush() にはタイムアウトを設定でき、ハングアップを防止しているon_background_error は SaveErrorContext を引数に取り、失敗時の
キーや関数の詳細情報を提供するreferences: src/beautyspot/core.py
親: SPEC015
子: —
src/beautyspot/core.py において、非同期タスクのIOオフロード用に ThreadPoolExecutor がグローバル変数 _io_executor として定義されている。
この実装には以下の課題がある:
max_workers=4)がハードコードされており、ユーザーが実行環境(AWS Lambda、強力なサーバー等)に合わせて調整できない。代替案として with 文(Context Manager)による管理も検討されたが、beautyspot の「デコレータを付与するだけで動作する」という簡易な利用体験(DX)を損なうため、採用には至らなかった。ユーザーコードを変更させずに、安全にリソースをクリーンアップする仕組みが必要である。
shutdown() の呼び出しや with 文を強制したくない。Project を Context Manager 化し、__exit__ で shutdown() を呼ぶ。atexit モジュールを使い、終了時に shutdown() を呼ぶ。weakref.finalize で自動クリーンアップを行う。Chosen option: Option 4.
Project クラスの設計を以下のように変更する:
Project インスタンスごとに ThreadPoolExecutor を保持する。Executor の注入を許可する。executor 引数あり): Project はそれを借用するのみ。シャットダウンの責任はユーザー(呼び出し元)にあるため、自動クリーンアップは行わない。executor 引数なし): Project がライフサイクルを管理する責任を持つ。weakref.finalize を使用してシャットダウンを自動化する。weakref.finalize instead of atexit?単純な atexit.register(self.shutdown) を採用しなかった理由は、メモリリーク(循環参照)のリスク である。
atexit レジストリにインスタンスメソッド(self.shutdown)を登録すると、atexit が self への強参照(Strong Reference)を保持し続ける。これにより、アプリケーション実行中に Project インスタンスが不要になってもガベージコレクション(GC)されず、メモリリークを引き起こす。これを防ぐには atexit.unregister が必要になるが、管理が複雑になる。weakref.finalize(self, func, *args) を採用する。self がGCされた時点で、即座に func(クリーンアップ処理)が実行される。self が生存していた場合も、atexit 相当のタイミングで func が実行される。_shutdown_executor は staticmethod とし、引数には self ではなく self.executor のみを渡す。これにより、ファイナライザが self を参照し続けて寿命を延ばしてしまう事故を防ぐ。# Implementation Sketch
class Project:
def __init__(self, ...):
# ...
self.executor = ThreadPoolExecutor(...)
# self を参照しないよう、executor オブジェクトだけを渡す
self._finalizer = weakref.finalize(self, self._shutdown_executor, self.executor)
@staticmethod
def _shutdown_executor(executor):
executor.shutdown(wait=True)
Project インスタンスがGCされたタイミングでスレッドプールも回収されるため、リソース効率が良い。core.py の実装において、GCの挙動と weakref の仕様を理解したコーディングが必要になり、内部的な複雑性が若干増す。Project インスタンスを大量に生成・破棄するような特殊な使い方をした場合、スレッドプールの生成コストがオーバーヘッドになる可能性がある(ドキュメントでシングルトン的な利用を推奨することで緩和する)。親: REQ006
子: —
大規模なデータのシリアライズや、S3 等のリモートストレージへの保存処理は、ユーザーのメインロジックの実行を妨げる(ブロッキング)要因となっていました。「インフラとしてのキャッシュ処理がユーザーを待たせない」という設計思想を強化する必要があります。
Spot インスタンスを何度も再利用でき、かつリソースの適切なクリーンアップ(フラッシュ)が行えること。with ブロックによる自動フラッシュの仕組みを導入する。Chosen option: Option 2.
キャッシュの保存処理をバックグラウンドで実行し、かつリソースの整合性を保つためのライフサイクル管理を導入します。
wait=False モードの導入: @spot.mark や cached_run に wait オプションを追加し、保存完了を待たずにメインロジックに結果を返すことを可能にします。with spot: ブロックを抜ける際に、全てのバックグラウンドタスクの完了を待機する(Flush)処理を実行します。Spot インスタンスを繰り返し安全に利用可能にします。with ブロックやプロセス終了時に未完了タスクを必ず待機するため、データの整合性が保たれる。親: REQ006
子: —
beautyspot では、関数の実行結果をキャッシュに保存する際、wait=False を指定することでバックグラウンドスレッドでの非同期保存を行うことができます。しかし、バックグラウンドでの保存処理中にエラーが発生した場合、これまではログが出力されるのみ(サイレントフェイル)でした。
メインスレッドをクラッシュさせないという点では安全ですが、ユーザーからすると「なぜかキャッシュが効かない」という原因究明が困難な状態に陥るリスクがありました。また、監視ツールへのエラー通知や、カスタムのリカバリ処理を行うためのフックが存在しませんでした。
on_background_error) の導入。エラー発生時に任意の処理を実行可能にする。Chosen option: Option 3.
以下の設計方針で実装します。
on_background_error 引数の追加: Spot.__init__ にコールバックを受け付ける引数を追加します。SaveErrorContext) の導入: ハンドラーには、エラー時の詳細情報(対象の関数名、キャッシュキー、戻り値等)を含む専用のデータクラスを渡します。result (評価済みオブジェクト) の包含: デバッグを容易にするため、キャッシュしようとした実際のオブジェクトを含めます。ただしメモリリークのリスクがあるため、Docstring にて警告を記載します。try-except で保護します。SaveErrorContext で巨大なオブジェクトの参照を保持し続けた場合、メモリを圧迫する可能性がある。親: REQ006
子: —
Spot クラスは非同期のキャッシュ保存を構造的に直列化するため、内部で専用の asyncio ループを実行するスレッド (_BackgroundLoop) を保持しています。インスタンスがガベージコレクション (GC) によって破棄される際、未保存のキャッシュデータを厳密に待機してしまうと、メインスレッドを予測不能なタイミングでフリーズさせたり、デッドロックを引き起こす危険性があります。
そのため、これまでは安全を優先し、GC 時にはタスクをキャンセルしてリソースを解放する設計となっていました。しかし、この設計では一時的なスコープで Spot を使用した場合、GC のタイミングによってキャッシュデータが失われるという課題がありました。
atexit によるグローバルな安全網を導入し、プロセス終了時に未完了タスクをドレインする。あわせて明示的な管理を推奨する。Chosen option: Option 3.
atexit によるグローバルな安全網の追加:
weakref.WeakSet を用いて起動中のバックグラウンドループを追跡し、プロセス終了時 (atexit) に wait=True でドレイン(残存タスクの処理)を行うフックを実装します。プロセス終了時であれば、メインスレッドをブロックしてもデッドロックの危険性が低いためです。Spot インスタンスは長寿命なオブジェクトとして扱うか、コンテキストマネージャ (with) または shutdown(wait=True) による明示的な管理を行うべきであることを周知します。Spot を使った場合でも、アプリ終了時まではデータが救済される可能性が高まる。atexit 時に大量の未完了タスクがあると、プロセス終了に時間がかかる場合がある。親: REQ006
子: —
beautyspot の Spot クラスは、非同期でのキャッシュ保存タスクを構造的に直列化するため、内部で専用の asyncio ループを実行するバックグラウンドスレッド (_BackgroundLoop) を保持しています。インスタンスのライフサイクル終了時、これらのバックグラウンドタスクを安全に終了させる必要がありますが、シャットダウンのトリガー(明示的呼び出し、アプリ終了、GC)によって、メインスレッドへの影響とプロセスの振る舞いが大きく異なるという課題がありました。
特に、GC は予測不能なタイミングで発生します。ここでタスク完了を待機(スレッドを join)してしまうと、メインスレッドがフリーズしたりデッドロックを引き起こす危険性があります。一方で、待機せずにタスクを強制キャンセルすると、保存待ちだったキャッシュデータが失われてしまいます。
Chosen option: Option 3.
以下の3つの方式を実装し、状況に応じて使い分けます。
stop(wait=True) (完全同期シャットダウン)shutdown()、with ブロックの終了、atexit。stop_gracefully_no_wait() (非同期ドレイン / スレッド切り離し)weakref.finalize による GC 時。stop(wait=False) (強制停止)Spot が一時的なスコープでインスタンス化され GC された場合でも、データロストのリスクがほぼ解消された。親: REQ006
子: —
バックグラウンドでの非同期 IO タスクを処理するイベントループにおいて、単純に daemon=True スレッドを使用すると、メインスレッド終了時にプロセスが即座に強制終了され、データ破損やキャッシュのロストが発生する危険がありました。一方で、daemon=False にして atexit で待機を試みるアプローチでは、タスクがハングした場合にプロセス全体が永遠に終了しなくなるデッドロックの罠が存在していました。
daemon=False スレッドを使用し、atexit で待機する。デッドロックやハングのリスクが高い。daemon=True スレッドを維持しつつ、明示的なタスク追跡と猶予期間(Grace Period)付きの atexit ハンドラを実装する。Chosen option: Option 2.
真の耐障害性を持つ Graceful Shutdown を実現するため、以下のアーキテクチャを採用します。
daemon=True で起動し、プロセス終了時の無限ハングを OS の力で防ぎます。submit 時と atexit 時の競合状態を完全に排除します。atexit フック内でメインスレッドをブロックし、タイムアウト付き(デフォルト5秒)で _thread.join() を実行することで、IO タスクが完了するための猶予を与えます。親: REQ006
子: —
beautyspot はキャッシュの永続化をバックグラウンドスレッドにオフロードしています。コンテキストマネージャを使用した場合は安全にタスクがドレインされますが、インスタンスをグローバルに生成して使い捨てた場合など、ガベージコレクション (GC) によってインスタンスが破棄される際の振る舞いが課題となっていました。GC のタイミングで未完了の I/O タスクを待機すべきか、それとも破棄すべきかを決定する必要があります。
Chosen option: Option 2.
ガベージコレクション (weakref.finalize) によるインスタンス破棄時には、未完了のタスクを待機せず、即座に破棄する(Fail-fast & Non-blocking) ことを決定しました。
同時に、タスクが破棄された場合は logger.warning および ResourceWarning を発行し、ユーザーに対してコンテキストマネージャや明示的な shutdown() の使用を強く促します。
beautyspot が原因でアプリケーションの GC やシャットダウンがハングアップしないことが保証される。親: REQ006
子: —
Spot インスタンスは、バックグラウンドで実行中の保存タスク(Future)を内部のセットで管理しています。これはシャットダウン時や flush() 呼び出し時に、すべてのタスクが完了したかを確認するために必要です。この追跡において、強参照を使用すべきか、それとも GC を妨げない弱参照を使用すべきかを決定する必要があります。
Future オブジェクトへの参照を即座に破棄した場合でも、タスクが途中で GC されることなく確実に完了まで実行されること。flush() 呼び出し時に、全ての未完了タスクを漏れなく検知し、待機できること。weakref.WeakSet を使用する。参照がなくなると自動的に削除されるが、待機すべきタスクが GC のタイミングで勝手に消えてしまうリスクがある。set(強参照)を使用し、タスク完了のコールバックによって明示的にセットから削除する。Chosen option: Option 2.
タスクの追跡には通常の set を使用し、強参照によってタスクの生存を保証します。
Spot が強参照を保持することで、タスクが確実に完了まで実行されることを保証します。WeakSet で発生しうる「待機すべきタスクが勝手に消える」という不透明な挙動を排除します。flush() の挙動が正確になり、確実なドレインが可能になる。atexit 等のシャットダウン機構によってプロセスレベルでの救済策が講じられているため、許容可能である。親: REQ006
子: —
beautyspot では、バックグラウンドでのI/Oタスク処理に専用のスレッド(_BackgroundLoop)と、ブロッキング操作を委譲する ThreadPoolExecutor の2つを組み合わせて使用しています。
Spot.shutdown(save_sync=False) またはプロセスのシャットダウン時に、これら2つのリソースを破棄する順序やタイミングの不整合が問題となりました。
具体的には、バックグラウンドループ内の非同期タスクが run_in_executor に依存している状態(I/O待ち)で、先に Executor が cancel_futures=True で強制終了されると、タスク側で予期せぬ CancelledError や RuntimeError が発生し、クラッシュやログのノイズ、正常な後処理(一時ファイルの削除やDB切断など)の阻害を引き起こすリスクがありました。
save_sync=False)であっても、システムがクラッシュせず、残存タスクが安全に破棄されること。run_in_executor を呼び出すバックグラウンドタスク内で CancelledError および RuntimeError を明示的に捕捉・ハンドリングする。Chosen option: Option 2.
シャットダウン手順の順序自体(Loopの停止 -> Executorの停止)は論理的に正しいものの、強制停止(save_sync=False または Loop のドレインタイムアウト)時には、Executor内のタスクがキャンセルされることを前提とした防御的プログラミングを採用します。
具体的には、_save_result_async 内で run_in_executor を呼び出す際に try...except (asyncio.CancelledError, RuntimeError) ブロックを設け、シャットダウンによる強制キャンセルを安全に捕捉します。捕捉した場合は、on_background_error コールバックを呼び出すか、適切に警告を記録することで、クラッシュを防ぎつつ通知を行います。
save_sync=False による明示的な要求動作として許容される。親: REQ006
子: —
src/beautyspot/core.py において、非同期タスクのIOオフロード用に ThreadPoolExecutor がグローバル変数 _io_executor として定義されている。
この実装には以下の課題がある:
max_workers=4)がハードコードされており、ユーザーが実行環境(AWS Lambda、強力なサーバー等)に合わせて調整できない。代替案として with 文(Context Manager)による管理も検討されたが、beautyspot の「デコレータを付与するだけで動作する」という簡易な利用体験(DX)を損なうため、採用には至らなかった。ユーザーコードを変更させずに、安全にリソースをクリーンアップする仕組みが必要である。
shutdown() の呼び出しや with 文を強制したくない。Project を Context Manager 化し、__exit__ で shutdown() を呼ぶ。atexit モジュールを使い、終了時に shutdown() を呼ぶ。weakref.finalize で自動クリーンアップを行う。Chosen option: Option 4.
Project クラスの設計を以下のように変更する:
Project インスタンスごとに ThreadPoolExecutor を保持する。Executor の注入を許可する。executor 引数あり): Project はそれを借用するのみ。シャットダウンの責任はユーザー(呼び出し元)にあるため、自動クリーンアップは行わない。executor 引数なし): Project がライフサイクルを管理する責任を持つ。weakref.finalize を使用してシャットダウンを自動化する。weakref.finalize instead of atexit?単純な atexit.register(self.shutdown) を採用しなかった理由は、メモリリーク(循環参照)のリスク である。
atexit レジストリにインスタンスメソッド(self.shutdown)を登録すると、atexit が self への強参照(Strong Reference)を保持し続ける。これにより、アプリケーション実行中に Project インスタンスが不要になってもガベージコレクション(GC)されず、メモリリークを引き起こす。これを防ぐには atexit.unregister が必要になるが、管理が複雑になる。weakref.finalize(self, func, *args) を採用する。self がGCされた時点で、即座に func(クリーンアップ処理)が実行される。self が生存していた場合も、atexit 相当のタイミングで func が実行される。_shutdown_executor は staticmethod とし、引数には self ではなく self.executor のみを渡す。これにより、ファイナライザが self を参照し続けて寿命を延ばしてしまう事故を防ぐ。# Implementation Sketch
class Project:
def __init__(self, ...):
# ...
self.executor = ThreadPoolExecutor(...)
# self を参照しないよう、executor オブジェクトだけを渡す
self._finalizer = weakref.finalize(self, self._shutdown_executor, self.executor)
@staticmethod
def _shutdown_executor(executor):
executor.shutdown(wait=True)
Project インスタンスがGCされたタイミングでスレッドプールも回収されるため、リソース効率が良い。core.py の実装において、GCの挙動と weakref の仕様を理解したコーディングが必要になり、内部的な複雑性が若干増す。Project インスタンスを大量に生成・破棄するような特殊な使い方をした場合、スレッドプールの生成コストがオーバーヘッドになる可能性がある(ドキュメントでシングルトン的な利用を推奨することで緩和する)。親: REQ006
子: —