> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreweave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# サンドボックスのライフサイクル

> サンドボックスの状態、作成パターン、待機、およびシャットダウンの動作を理解します。

これらの例では、`CWSANDBOX_API_KEY` に設定した CoreWeave API アクセストークンを使用します。Forge のAPIキーによる認証については、[はじめに](/ja/products/sandboxes/get-started)を参照してください。CKS の要件については、[配置モード](/ja/products/sandboxes/placement)を参照してください。

このガイドでは、サンドボックスが作成からシャットダウンまでどのように進行するか、通過する状態、開始と待機の方法、およびクリーンに停止する方法について説明します。作成パターンの選択、起動タイミングの制御、またはSDKコードでの信頼性の高いシャットダウンの処理が必要な場合に、このガイドを使用してください。

<h2 id="lifecycle-states">
  ライフサイクルの状態
</h2>

各サンドボックスは、起動、実行、シャットダウンの過程で一連の状態を経ます。SDK では、これらを `SandboxStatus` 値として表します。

```mermaid theme={"system"}
stateDiagram-v2
    [*] --> PENDING: start()
    PENDING --> CREATING: backend scheduling
    CREATING --> RUNNING: container ready
    CREATING --> FAILED: startup error
    RUNNING --> COMPLETED: main process exits 0
    RUNNING --> RUNNING: main process exits nonzero, container restarts
    RUNNING --> FAILED: restart limit reached / lifetime exceeded / runtime error
    RUNNING --> TERMINATING: stop()
    TERMINATING --> COMPLETED: grace period ends
    TERMINATING --> FAILED: error during drain
    COMPLETED --> [*]
    FAILED --> [*]
```

| 状態 | 意味 | 終端状態か | `wait()` での扱い |
| - | - | - | - |
| `PENDING` | 起動が受け付けられ、バックエンドのスケジューリングを待機中 | いいえ | ポーリングを継続 |
| `CREATING` | バックエンドがコンテナーをプロビジョニング中 | いいえ | ポーリングを継続 |
| `RUNNING` | コンテナーが起動し、操作を受け付け中 | いいえ | 正常に戻る |
| `TERMINATING` | サンドボックスが終了前の猶予期間中にドレイン中 | いいえ | 戻る |
| `COMPLETED` | メインプロセスがステータス 0 で終了したか、サンドボックスが停止された (停止済みのサンドボックスには `returncode` がありません) | はい | 正常に戻る |
| `FAILED` | 起動エラーまたはランタイムエラー、あるいはサンドボックスが再起動の上限または最大ライフタイムに達した | はい | `SandboxFailedError` を送出 |
| `UNSPECIFIED` | バックエンドがこのサンドボックスをトラッキングしなくなった | はい | 完了として扱われ、正常に戻る |

`PENDING` と `CREATING` は一時的な状態です。SDK はこれらの状態の間、自動的にポーリングを継続します。
`TERMINATING` はバックエンドの一時的な状態であり、`wait_until_complete()` と `stop().result()`
によって終端状態まで進められます。`RUNNING` は安定した稼働状態です。
`COMPLETED` と `FAILED` は終端状態です。`UNSPECIFIED` は、ポーリング時に SDK によって
`COMPLETED` にマッピングされます。

<h2 id="creation-patterns">
  作成パターン
</h2>

サンドボックスの作成方法は 2 つあり、起動 RPC が実行されるタイミングが異なります。以下のセクションでは、各パターンとその使い分けについて説明します。

<h3 id="sandboxrun-immediate-start">
  Sandbox.run(): 即時開始
</h3>

`Sandbox.run()` はサンドボックスを作成したうえで内部的に `start().result()` を呼び出し、バックエンドがリクエストを受け入れるまでブロックします。

```python theme={"system"}
from cwsandbox import Sandbox

# バックエンドが起動リクエストを受け入れた後に戻ります
sandbox = Sandbox.run("echo", "hello")

# sandbox_id は即座に設定されます
print(sandbox.sandbox_id)  # "sandbox-abc123"

# ただし、ステータスはまだ PENDING の場合があり、必ずしも RUNNING になっているとは限りません
```

最初の位置引数はコマンドで、残りは引数です：

```python theme={"system"}
# これらは同等です
Sandbox.run("echo", "hello", "world")
Sandbox.run(command="echo", args=["hello", "world"])
```

<h3 id="sessionsandbox-deferred-start">
  session.sandbox(): 遅延起動
</h3>

`session.sandbox()` は、ネットワークコールを行わずにサンドボックスオブジェクトを作成します。起動 RPC は
初回使用時に発火します:

```python theme={"system"}
import cwsandbox
from cwsandbox import SandboxDefaults

with cwsandbox.Session(SandboxDefaults()) as session:
    sandbox = session.sandbox()

    # まだネットワーク呼び出しはありません。sandbox_id は None です
    print(sandbox.sandbox_id)  # None

    # 最初の操作で自動的に開始がトリガーされます
    result = sandbox.exec(["echo", "hello"]).result()
```

<h3 id="main-command-lifetime">
  メインコマンドの有効期間
</h3>

`run()` または `session.sandbox()` に渡されるコマンドは、サンドボックスのメインプロセスです。このプロセスが
終了したときの動作は、終了ステータスによって異なります：

* **終了ステータス 0:** サンドボックスは `COMPLETED` に遷移します。
* **0 以外の終了ステータス:** プラットフォームがメインプロセスのコンテナーを再起動します。すでに `RUNNING` に達しているサンドボックスは
  再起動中も `RUNNING` のままで、まだ達していないサンドボックスは `CREATING` のままです。`wait_until_complete()` は再起動中も待機を続け、`timeout` に達するまで待機します。
* **再起動の上限に到達:** メインプロセスが 0 以外のステータスで終了し続けると、
  再起動の上限 (現在は 6 回) に達した時点でサンドボックスは `FAILED` に遷移します。

メインプロセスが正常に終了すると、サンドボックスも終了します：

```python theme={"system"}
# echo がすぐに終了するため、このサンドボックスはほぼ即座に完了します
sandbox = Sandbox.run("echo", "hi")
sandbox.wait()  # すでに COMPLETED になっている場合があります

# exec ベースのワークフローでは、長時間実行されるメインコマンドを使用します
sandbox = Sandbox.run()
# または、デフォルトを使用します
sandbox = Sandbox.run()
```

短いコマンドを実行してその出力を取得する必要がある場合は、その短いコマンドをメインプロセスにするのではなく、長時間稼働する
サンドボックス上で `exec()` を使用してください。

<h2 id="start-a-sandbox">
  サンドボックスを起動する
</h2>

サンドボックス オブジェクトが存在した後、明示的に起動するか、SDK に任せて初回使用時に起動させるかのいずれかを選択できます。以下のセクションでは、両方のアプローチと、コンテキストマネージャーがそれらとどのように相互作用するかについて説明します。

<h3 id="explicit-start">
  明示的な起動
</h3>

`start()` は起動 RPC を送信し、`OperationRef[None]` を返します：

```python theme={"system"}
sandbox = session.sandbox()

# エラーを制御するために明示的に起動します
sandbox.start().result()  # バックエンドが受け入れるまで待機します
print(sandbox.sandbox_id)  # この時点で設定済みです
```

これは、タイミングを制御したい場合や、起動時のエラーを
オペレーション中のエラーとは別に処理したい場合に便利です。

<h3 id="auto-start">
  自動起動
</h3>

ほとんどのオペレーションは、サンドボックスがまだ起動していない場合に自動的に起動します。一般的なケースでは、サンドボックスを作成してすぐに使用を開始できます。

| メソッド | 自動起動？ | メモ |
| - | - | - |
| `exec()` | はい | 実行前にRUNNINGを待機します |
| `read_file()` | はい | 読み取り前にRUNNINGを待機します |
| `write_file()` | はい | 書き込み前にRUNNINGを待機します |
| `wait()` | はい | 起動後、RUNNING、TERMINATING、または終端状態になるまでポーリングします |
| `wait_until_complete()` | はい | 起動後、終端状態になるまでポーリングします |
| `await sandbox` | はい | 起動してRUNNINGを待機します |
| `get_status()` | いいえ | sandbox\_idが必要です。まず`start()`を呼び出してください |
| `stop()` | いいえ | 起動していない場合、停止するものはありません |

<h3 id="context-managers-and-start">
  コンテキストマネージャーと start
</h3>

コンテキストマネージャー (`with`/`async with`) はエントリ時に `start()` を呼び出しますが、`wait()` は呼び出し**ません**。
最初のオペレーションの前にサンドボックスが RUNNING 状態である必要がある場合は、`wait()` を明示的に呼び出してください：

```python theme={"system"}
with Sandbox.run() as sandbox:
    # start() は呼び出されていますが、サンドボックスはまだ PENDING/CREATING の可能性があります
    sandbox.wait()  # RUNNING が保証されます（失敗した場合は例外が送出されます）
    result = sandbox.exec(["echo", "ready"]).result()
```

実際には、`exec()` が内部で RUNNING 状態になるまで待機するため、これが問題になることはほとんどありません。明示的な
`wait()` は、起動時の失敗とオペレーションの失敗を切り分けたい場合に役立ちます。

<h2 id="wait-for-a-sandbox">
  サンドボックスを待機する
</h2>

SDK は、サンドボックスがオペレーションの準備完了になるのを待つか、メインコマンドを終了するのを待つかによって、2 つの待機メソッドを提供します。以下のセクションでは、各メソッドと、起動およびオペレーションのタイムアウトがどのように関連するかを説明します。

<h3 id="wait-block-until-running">
  wait(): RUNNING になるまでブロックする
</h3>

`wait()` は、サンドボックスが RUNNING、TERMINATING、または終端状態のいずれかになるまでポーリングします。

```python theme={"system"}
sandbox = Sandbox.run()
sandbox.wait()  # RUNNING になるまで待機します

# ステータスを確認します。RUNNING または COMPLETED などの終端状態の可能性があります
if sandbox.status == SandboxStatus.RUNNING:
    result = sandbox.exec(["echo", "hello"]).result()
```

ポーリングには指数バックオフを使用します。間隔は 0.2 秒から開始し、1.5 倍ずつ増加して、
最大 2.0 秒になります。

`wait()` はメソッドチェーンのために自身を返します。

```python theme={"system"}
result = Sandbox.run().wait().exec(["echo", "hello"]).result()
```

起動中にサンドボックスが終端状態に達した場合、`wait()` は次のように処理します。

| 終端状態 | wait() の動作 |
| - | - |
| `COMPLETED` | 正常に戻ります (RUNNING になる前にサンドボックスが終了した場合) |
| `UNSPECIFIED` | 正常に戻ります (完了として扱います) |
| `FAILED` | `SandboxFailedError` を送出します |

<h3 id="wait_until_complete-block-until-terminal">
  wait\_until\_complete(): 終端状態までブロック
</h3>

`wait_until_complete()` は、サンドボックスが終端状態に達するまでブロックし、
`TERMINATING` の間も自動的にポーリングします。メインコマンドが処理を行うサンドボックスで使用してください：

```python theme={"system"}
sandbox = Sandbox.run("python", "train.py")
sandbox.wait_until_complete(timeout=3600.0).result()
print(sandbox.returncode)  # トレーニングが成功した場合は 0
```

`raise_on_termination` パラメーター (デフォルトは `True`) は、このクライアントが `stop()` を呼び出した後に
`wait_until_complete()` が `SandboxTerminatedError` を送出するかどうかを制御します。
外部からの停止は、個別のエラーなしで `COMPLETED` として扱われます。これは、バックエンドがまだ
終了理由のメタデータを提供していないためです。一方、最大ライフタイムに達したサンドボックスは
`FAILED` で終了するため、`wait_until_complete()` は `SandboxFailedError` を送出します。

```python theme={"system"}
from cwsandbox import SandboxTerminatedError

# デフォルト: このクライアントが stop() を呼び出した後は例外を送出します
try:
    sandbox.wait_until_complete().result()
except SandboxTerminatedError:
    print("This client stopped the sandbox")

# 例外を抑制して適切に処理します
sandbox.wait_until_complete(raise_on_termination=False).result()
print(f"Exit code: {sandbox.returncode}")
```

<h3 id="timeout-phases">
  タイムアウトのフェーズ
</h3>

起動時の待機時間と操作のタイムアウトは別々のフェーズですが、どちらもサンドボックスの最大ライフタイム
が上限となります：

```text theme={"system"}
|<--------------------- max_lifetime_seconds --------------------->|
start() --> [startup wait] --> RUNNING --> exec() --> [operation timeout]
```

* **起動待機**: RUNNING に到達するまでに PENDING/CREATING 状態で費やされる時間です。`wait()` または `wait_until_complete()` の
  `timeout` パラメーターで制御します。バックエンドのスケジューリングによって異なりますが、通常は
  30～60 秒です。
* **操作タイムアウト**: 個々の exec、read、write にかかる時間です。`exec()` の `timeout_seconds`、
  または `SandboxDefaults` の `request_timeout_seconds` で制御します。起動待機は含まれません。
* **最大ライフタイム**: サンドボックスが存在できる合計の実時間で、起動時点から数え、
  起動待機も含みます。`max_lifetime_seconds` で制御します。設定しない場合、
  ランナーポリシーのデフォルトが適用されます。正のデフォルト値が設定されていない場合は、プラットフォームのフォールバック値として 10 分が適用されます。

起動待機と操作タイムアウトはクライアントが待機する時間の上限を定めるもので、期限切れになってもサンドボックスは
実行されたままです。一方、最大ライフタイムはサンドボックス自体の上限を定めます。期限切れになると、
プラットフォームがサンドボックスを終了し、実行中の操作もすべて失敗します。
`max_lifetime_seconds` は、想定される作業の合計時間を十分に上回る値に設定してください。この値はサンドボックスの起動後に
変更できません。4 つのタイムアウト設定すべてとそれらの関係については、
[タイムアウト](/ja/products/sandboxes/client/guides/sandbox-configuration#timeouts)を参照してください。30 日間の上限と
Serverless および CKS の設定例については、[長時間稼働するサンドボックスを実行する](/ja/products/sandboxes/long-running-sandboxes)を参照してください。

<h2 id="operations-and-lifecycle">
  オペレーションとライフサイクル
</h2>

`exec()`、`read_file()`、`write_file()` などのオペレーションは、必要に応じてサンドボックスを自動的に起動し、
RUNNING になるまで待機してから処理を続行します。

```python theme={"system"}
# Session のサンドボックスはまだ起動していません
sandbox = session.sandbox()

# exec() がすべて処理します: 起動し、RUNNING になるまで待機してからコマンドを実行します
result = sandbox.exec(["echo", "hello"]).result()
```

オペレーションのタイムアウト (`timeout_seconds`) は、サンドボックスが RUNNING になってから適用されます。起動にかかる
時間は含まれません。

<h2 id="stop-a-sandbox-and-end-of-life">
  サンドボックスの停止とサポート終了
</h2>

このセクションでは、サンドボックスをシャットダウンする方法、停止後の動作、および `stop()` と `delete()` の使い分けについて説明します。

<h3 id="stop">
  stop()
</h3>

`stop()` は停止リクエストを送信し、`OperationRef[None]` を返します。サンドボックスは
`TERMINATING` (猶予期間中のドレイン) を経て、終端状態に遷移します。返された
`OperationRef` は、停止 RPC が成功した時点ではなく、バックエンドが終端状態を
確認した時点で解決されます。

```python theme={"system"}
sandbox.stop().result()  # 終端状態になるまで待機します
```

パラメーター：

| パラメーター | デフォルト | 用途 |
| - | - | - |
| `graceful_shutdown_seconds` | 10.0 | 強制停止までにプロセスが終了するのを待つ猶予期間 (秒) |
| `snapshot_on_stop` | `False` | シャットダウン前にサンドボックスのファイルシステムの状態を取得するかどうか |
| `missing_ok` | `False` | サンドボックスがすでに存在しない場合に、例外を発生させずに正常に戻るかどうか |

```python theme={"system"}
# デバッグ用に状態を取得します
sandbox.stop(snapshot_on_stop=True).result()

# 冪等なクリーンアップ: サンドボックスがすでに存在しない場合でも安全に呼び出せます
sandbox.stop(missing_ok=True).result()

# プロセスが終了するまでの猶予時間を長くします
sandbox.stop(graceful_shutdown_seconds=30.0).result()
```

`stop()` は処理中の起動にも対応しています。起動がまだ処理中の場合は、起動の完了を待ってから停止します。`stop()` を同時に、または繰り返し呼び出した場合は 1 つの停止オペレーションが共有され、
停止 RPC が重複して発行されることはありません。そのため、`stop()` を繰り返し呼び出しても安全で、コストもほとんどかかりません。

<h3 id="post-stop-behavior">
  停止後の動作
</h3>

`stop()` が呼び出されると、サンドボックスは `TERMINATING` (猶予期間中の
ドレイン状態) を経て、終端状態 (`COMPLETED` または `FAILED`) に遷移します。`stop()` が
呼び出された後は、サンドボックスを使用できません。それ以降のオペレーションでは `SandboxNotRunningError` が発生します。

```python theme={"system"}
sandbox.stop().result()

# 以下は SandboxNotRunningError を送出します
sandbox.exec(["echo", "hello"])  # 送出される例外
sandbox.read_file("/path")       # 送出される例外
```

`status` プロパティは、最後の呼び出しの結果からキャッシュされます。停止前に最新のデータを取得するには、
`get_status()` を使用します:

```python theme={"system"}
fresh_status = sandbox.get_status()
if fresh_status == SandboxStatus.RUNNING:
    sandbox.stop().result()
```

<h3 id="context-manager-exit">
  コンテキストマネージャーの終了
</h3>

コンテキストマネージャーは終了時に自動的に `stop()` を呼び出します：

```python theme={"system"}
with Sandbox.run() as sandbox:
    result = sandbox.exec(["echo", "hello"]).result()
# stop() はここで呼び出されます。ブロック内で例外が発生した場合でも同様です。
```

例外が進行中の場合、コンテキストマネージャーは停止エラーを抑制して、
元の例外をマスクしないようにします。

<h3 id="stop-compared-to-delete">
  stop() と delete() の比較
</h3>

| | `stop()` | `delete()` |
| - | - | - |
| 対象 | 稼働中のサンドボックスインスタンス | ID で指定したサンドボックス (クラスメソッド) |
| 目的 | 正常なシャットダウン | 完全な削除または孤立したサンドボックスのクリーンアップ |
| インスタンスが必要ですか？ | はい | いいえ。`Sandbox.delete(sandbox_id)` を使用します |
| `missing_ok` | はい | はい |

現在使用しているサンドボックスには `stop()` を使用します。`Sandbox.list()` または `Sandbox.from_id()` で
見つかったサンドボックスのクリーンアップには `delete()` を使用します：

```python theme={"system"}
# 作成したサンドボックスを停止します
sandbox.stop().result()

# 孤立したサンドボックスを ID で削除します
Sandbox.delete("sandbox-abc123", missing_ok=True).result()
```

孤立したサンドボックスの管理と一括クリーンアップの
方法については、[クリーンアップパターンガイド](/ja/products/sandboxes/client/guides/cleanup-patterns)を参照してください。

<h2 id="under-the-hood">
  Under the hood
</h2>

SDK は、独自の asyncio イベントループを持つバックグラウンドのデーモンスレッド上で、すべての gRPC オペレーションを実行します。
この設計により、以下が実現されます。

* 同期 API (`.result()`) は、バックグラウンドのループがネットワークコールを処理している間、
  呼び出し元のスレッドをブロックします。
* 非同期 API (`await`) は同じバックグラウンドのループに橋渡しされるため、どちらのパターンも同じ
  基盤実装を使用します。
* バックグラウンドのループは、初回使用時に遅延起動されます。gRPC チャンネルも同様に遅延作成されます。
* 自動起動は、各オペレーションの前に `sandbox_id` が None かどうかを確認し、None の場合は
  `start()` をトリガーする仕組みです。
* プロセス終了時には、クリーンアップハンドラ (atexit + シグナルハンドラ) が登録済みセッション内の
  すべてのサンドボックスを停止します。クリーンアップ中にもう一度 Ctrl+C を押すと、即座に強制終了します。

このアーキテクチャにより、異なるイベントループ間で発生する問題を回避でき、
`nest_asyncio` を使用しなくても Jupyter ノートブックで動作します。使用パターンについては、[同期 API と非同期 API のパターンを比較するガイド](/ja/products/sandboxes/client/guides/sync-vs-async) を参照してください。

<h2 id="common-patterns">
  一般的なパターン
</h2>

以下の例は、上記で説明したライフサイクルステップを組み合わせたエンドツーエンドのパターンを示しています。

<h3 id="quick-one-off">
  手軽な単発実行
</h3>

コマンドを実行して結果を取得します。コンテキストマネージャーがクリーンアップを行います：

```python theme={"system"}
from cwsandbox import Sandbox

with Sandbox.run() as sandbox:
    result = sandbox.exec(["echo", "hello"]).result()
    print(result.stdout)
```

<h3 id="controlled-startup">
  起動の制御
</h3>

起動時のエラーとオペレーション中のエラーを区別します：

```python theme={"system"}
from cwsandbox import Sandbox, SandboxFailedError

sandbox = Sandbox.run()
try:
    sandbox.wait()
except SandboxFailedError:
    print("Failed to start. Check container image and resources")
    raise

result = sandbox.exec(["echo", "ready"]).result()
sandbox.stop().result()
```

<h3 id="long-running-sandbox">
  長時間実行するサンドボックス
</h3>

メインコマンドが完了するまで待ちます：

```python theme={"system"}
from cwsandbox import Sandbox

sandbox = Sandbox.run("python", "train.py", max_lifetime_seconds=7200)
sandbox.wait_until_complete(timeout=7200.0).result()

if sandbox.returncode == 0:
    data = sandbox.read_file("/output/model.pt").result()
```

<h3 id="reconnection">
  再接続
</h3>

以前のセッションまたはプロセスで使用していたサンドボックスに再接続します。

```python theme={"system"}
from cwsandbox import Sandbox, SandboxStatus

sandbox = Sandbox.from_id("sandbox-abc123").result()

# from_id() は現在のステータスを取得しますが、サンドボックスの起動や検証は行いません
if sandbox.status == SandboxStatus.RUNNING:
    result = sandbox.exec(["echo", "reconnected"]).result()
else:
    print(f"Sandbox is {sandbox.status}, not RUNNING")
```

<h3 id="parallel-batch-with-session">
  セッションを使用した並列バッチ
</h3>

複数のサンドボックスを作成し、結果を待ちます：

```python theme={"system"}
import cwsandbox
from cwsandbox import SandboxDefaults

with cwsandbox.Session(SandboxDefaults(tags=("batch-job",))) as session:
    sandboxes = [session.sandbox() for _ in range(5)]

    processes = [
        sb.exec(["python", "-c", f"print({i} ** 2)"])
        for i, sb in enumerate(sandboxes)
    ]

    done, pending = cwsandbox.wait(processes)
    for p in done:
        print(p.result().stdout)
```
