> ## 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.

# Cycle de vie d’une sandbox

> Comprendre les états d’une sandbox, les modèles de création, l’attente et le comportement à l’arrêt.

Ces exemples utilisent un jeton d’accès à l’API CoreWeave défini dans `CWSANDBOX_API_KEY`. Pour l’authentification par clé API Forge, consultez [Premiers pas](/fr/products/sandboxes/get-started). Pour les exigences relatives à CKS, consultez [Modes de placement](/fr/products/sandboxes/placement).

Ce guide décrit les étapes que traverse une sandbox, de sa création à son arrêt : les états par lesquels elle passe, comment la démarrer et attendre qu’elle soit prête, et comment l’arrêter proprement. Consultez-le lorsque vous devez choisir entre plusieurs modèles de création, maîtriser le moment du démarrage ou gérer l’arrêt de manière fiable dans votre code SDK.

<h2 id="lifecycle-states">
  États du cycle de vie
</h2>

Chaque sandbox passe par une succession d’états au cours de son démarrage, de son exécution et de son arrêt. Le SDK représente ces états sous forme de valeurs `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 --> [*]
```

| État | Signification | Terminal ? | Traitement par `wait()` |
| - | - | - | - |
| `PENDING` | Démarrage accepté, en attente d’ordonnancement par le backend | Non | Poursuit l’interrogation |
| `CREATING` | Le backend provisionne le conteneur | Non | Poursuit l’interrogation |
| `RUNNING` | Le conteneur est opérationnel et accepte les opérations | Non | Retour normal |
| `TERMINATING` | La sandbox est en cours de drainage pendant sa période de grâce, avant de s’arrêter | Non | Retour |
| `COMPLETED` | Le processus principal s’est terminé avec le statut 0, ou la sandbox a été arrêtée (une sandbox arrêtée n’a pas de `returncode`) | Oui | Retour normal |
| `FAILED` | Erreur au démarrage ou à l’exécution, ou la sandbox a atteint sa limite de redémarrages ou sa durée de vie maximale | Oui | Lève `SandboxFailedError` |
| `UNSPECIFIED` | Le backend ne suit plus cette sandbox | Oui | Traité comme terminé, retour normal |

`PENDING` et `CREATING` sont transitoires. Le SDK poursuit automatiquement l’interrogation pendant ces états.
`TERMINATING` est un état transitoire du backend que `wait_until_complete()` et `stop().result()`
font évoluer jusqu’à un état terminal. `RUNNING` est l’état opérationnel stable.
`COMPLETED` et `FAILED` sont terminaux. Lors de l’interrogation, le SDK convertit `UNSPECIFIED` en
`COMPLETED`.

<h2 id="creation-patterns">
  Modèles de création
</h2>

Il existe deux façons de créer une sandbox, qui se distinguent par le moment où la RPC de démarrage est déclenchée. Les sections suivantes décrivent chaque modèle et indiquent dans quels cas le choisir.

<h3 id="sandboxrun-immediate-start">
  Sandbox.run() : démarrage immédiat
</h3>

`Sandbox.run()` crée une sandbox et appelle `start().result()` en interne, puis reste bloqué jusqu’à ce que le
backend accepte la requête :

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

# Rend la main dès que le backend accepte la requête de démarrage
sandbox = Sandbox.run("echo", "hello")

# sandbox_id est défini immédiatement
print(sandbox.sandbox_id)  # "sandbox-abc123"

# Mais le statut peut encore être PENDING, et pas nécessairement déjà RUNNING
```

Le premier argument positionnel est la commande ; les suivants sont ses arguments :

```python theme={"system"}
# Ces appels sont équivalents
Sandbox.run("echo", "hello", "world")
Sandbox.run(command="echo", args=["hello", "world"])
```

<h3 id="sessionsandbox-deferred-start">
  session.sandbox() : démarrage différé
</h3>

`session.sandbox()` crée un objet sandbox sans effectuer le moindre appel réseau. Le RPC de démarrage
se déclenche à la première utilisation :

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

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

    # Aucun appel réseau pour l’instant, sandbox_id vaut None
    print(sandbox.sandbox_id)  # None

    # La première opération déclenche automatiquement le démarrage
    result = sandbox.exec(["echo", "hello"]).result()
```

<h3 id="main-command-lifetime">
  Durée de vie de la commande principale
</h3>

La commande transmise à `run()` ou à `session.sandbox()` constitue le processus principal de la sandbox. Ce
qui se passe lorsqu'elle se termine dépend de son statut de sortie :

* **Statut de sortie 0 :** la sandbox passe à l'état `COMPLETED`.
* **Statut de sortie non nul :** la plateforme redémarre le conteneur du processus principal. Une sandbox qui
  a déjà atteint l'état `RUNNING` reste à l'état `RUNNING` pendant les redémarrages ; sinon, elle reste à l'état
  `CREATING`. `wait_until_complete()` continue d'attendre pendant les redémarrages, dans la limite de son `timeout`.
* **Limite de redémarrages atteinte :** si le processus principal continue de se terminer avec un statut non nul, la
  sandbox passe à l'état `FAILED` une fois la limite de redémarrages atteinte (actuellement six redémarrages).

Un processus principal qui se termine avec succès met fin à la sandbox :

```python theme={"system"}
# Ce sandbox se termine presque immédiatement, car echo s’arrête aussitôt
sandbox = Sandbox.run("echo", "hi")
sandbox.wait()  # Peut déjà être à l’état COMPLETED

# Pour les flux de travail basés sur exec, utilisez une commande principale de longue durée
sandbox = Sandbox.run()
# Ou utilisez la valeur par défaut
sandbox = Sandbox.run()
```

Si vous devez exécuter une commande courte et capturer sa sortie, utilisez `exec()` sur une
sandbox de longue durée plutôt que d’en faire le processus principal.

<h2 id="start-a-sandbox">
  Démarrer un sandbox
</h2>

Une fois l’objet sandbox créé, vous pouvez le démarrer explicitement ou laisser le SDK le démarrer automatiquement lors de sa première utilisation. Les sections suivantes décrivent ces deux approches, ainsi que leur interaction avec les gestionnaires de contexte.

<h3 id="explicit-start">
  Démarrage explicite
</h3>

`start()` envoie la RPC de démarrage et renvoie un `OperationRef[None]` :

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

# Démarrage explicite pour contrôler les erreurs
sandbox.start().result()  # Bloquant jusqu'à ce que le backend accepte
print(sandbox.sandbox_id)  # Désormais défini
```

C’est utile lorsque vous souhaitez contrôler le moment du démarrage ou traiter les erreurs de démarrage séparément des
erreurs d’opération.

<h3 id="auto-start">
  Démarrage automatique
</h3>

La plupart des opérations démarrent automatiquement la sandbox si elle n’a pas encore été démarrée. Dans le cas
le plus courant, il vous suffit de créer une sandbox et de commencer à l’utiliser.

| Méthode | Démarrage automatique ? | Notes |
| - | - | - |
| `exec()` | Oui | Attend également l’état RUNNING avant l’exécution |
| `read_file()` | Oui | Attend également l’état RUNNING avant la lecture |
| `write_file()` | Oui | Attend également l’état RUNNING avant l’écriture |
| `wait()` | Oui | Démarre la sandbox, puis interroge son état jusqu’à RUNNING, TERMINATING ou un état terminal |
| `wait_until_complete()` | Oui | Démarre la sandbox, puis interroge son état jusqu’à un état terminal |
| `await sandbox` | Oui | Démarre la sandbox et attend l’état RUNNING |
| `get_status()` | Non | Nécessite sandbox\_id. Appelez d’abord `start()` |
| `stop()` | Non | Rien à arrêter si la sandbox n’a pas été démarrée |

<h3 id="context-managers-and-start">
  Gestionnaires de contexte et démarrage
</h3>

Les gestionnaires de contexte (`with`/`async with`) appellent `start()` à l’entrée, mais n’appellent **pas** `wait()`.
Si la sandbox doit être à l’état RUNNING avant votre première opération, appelez explicitement `wait()` :

```python theme={"system"}
with Sandbox.run() as sandbox:
    # start() a été appelé, mais la sandbox peut encore être à l'état PENDING/CREATING
    sandbox.wait()  # État RUNNING désormais garanti (ou exception levée en cas d'échec)
    result = sandbox.exec(["echo", "ready"]).result()
```

En pratique, cela a rarement d’importance, car `exec()` attend en interne l’état RUNNING. Un appel explicite à
`wait()` est utile lorsque vous souhaitez distinguer les échecs au démarrage des échecs d’opération.

<h2 id="wait-for-a-sandbox">
  Attendre une sandbox
</h2>

Le SDK fournit deux méthodes d’attente, selon que vous avez besoin que la sandbox soit prête à exécuter des opérations ou qu’elle termine sa commande principale. Les sections suivantes décrivent chaque méthode et expliquent le lien entre les délais d’expiration de démarrage et ceux des opérations.

<h3 id="wait-block-until-running">
  wait() : bloquer jusqu’à l’état RUNNING
</h3>

`wait()` interroge l’état de la sandbox jusqu’à ce qu’elle atteigne l’état RUNNING, TERMINATING ou un état terminal :

```python theme={"system"}
sandbox = Sandbox.run()
sandbox.wait()  # Bloque jusqu’à ce que l’état soit RUNNING

# Vérifier le statut : RUNNING ou un état terminal comme COMPLETED
if sandbox.status == SandboxStatus.RUNNING:
    result = sandbox.exec(["echo", "hello"]).result()
```

L’interrogation applique un backoff exponentiel : elle commence avec des intervalles de 0,2 s, multipliés par 1,5 à chaque tentative, avec un plafond
de 2,0 s.

`wait()` renvoie `self`, ce qui permet le chaînage de méthodes :

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

Si la sandbox atteint un état terminal pendant le démarrage, `wait()` le gère ainsi :

| État terminal | Comportement de wait() |
| - | - |
| `COMPLETED` | Renvoie normalement (la sandbox s’est terminée avant RUNNING) |
| `UNSPECIFIED` | Renvoie normalement (traité comme terminé) |
| `FAILED` | Lève `SandboxFailedError` |

<h3 id="wait_until_complete-block-until-terminal">
  wait\_until\_complete() : bloquer jusqu’à un état terminal
</h3>

`wait_until_complete()` bloque jusqu’à ce que la sandbox atteigne un état terminal, en continuant automatiquement d’interroger son état pendant la phase
`TERMINATING`. Utilisez cette méthode pour les Sandbox dont la commande principale effectue le travail :

```python theme={"system"}
sandbox = Sandbox.run("python", "train.py")
sandbox.wait_until_complete(timeout=3600.0).result()
print(sandbox.returncode)  # 0 si l’entraînement a réussi
```

Le paramètre `raise_on_termination`, défini sur `True` par défaut, détermine si
`wait_until_complete()` lève `SandboxTerminatedError` lorsque ce client a appelé `stop()`.
Les arrêts externes apparaissent comme `COMPLETED`, sans erreur distincte, car le backend ne
fournit pas encore de métadonnées sur le motif de l'arrêt. En revanche, une sandbox qui atteint sa durée de vie maximale se termine
à l'état `FAILED`, et `wait_until_complete()` lève alors `SandboxFailedError`.

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

# Par défaut : lève une exception après l’appel de stop() par ce client
try:
    sandbox.wait_until_complete().result()
except SandboxTerminatedError:
    print("This client stopped the sandbox")

# Désactiver l’exception pour une gestion sans erreur
sandbox.wait_until_complete(raise_on_termination=False).result()
print(f"Exit code: {sandbox.returncode}")
```

<h3 id="timeout-phases">
  Phases des délais d’expiration
</h3>

Le temps d’attente au démarrage et les délais d’expiration des opérations sont deux phases distinctes, toutes deux bornées par la durée de vie maximale
de la sandbox :

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

* **Attente de démarrage** : temps passé dans les états PENDING/CREATING avant d’atteindre l’état RUNNING. Contrôlée par le
  paramètre `timeout` de `wait()` ou `wait_until_complete()`. Généralement de 30 à 60 secondes selon
  l’ordonnancement du backend.
* **Délai d’expiration des opérations** : durée allouée à chaque exécution, lecture ou écriture. Contrôlé par `timeout_seconds`
  sur `exec()`, ou par `request_timeout_seconds` dans `SandboxDefaults`. N'inclut pas l’attente de démarrage.
* **Durée de vie maximale** : durée totale (temps réel) pendant laquelle la sandbox peut exister, comptée à partir du démarrage,
  attente de démarrage comprise. Contrôlée par `max_lifetime_seconds`. Si vous ne la définissez pas, la
  valeur par défaut de la politique de l’exécuteur s’applique ; à défaut de valeur par défaut positive, la plateforme applique une valeur de repli de 10 minutes.

L’attente de démarrage et les délais d’expiration des opérations limitent la durée d’attente du client, mais laissent la sandbox
s’exécuter lorsqu’ils expirent. La durée de vie maximale limite la sandbox elle-même : à son expiration, la
plateforme met fin à la sandbox et toute opération en cours échoue en même temps. Attribuez à
`max_lifetime_seconds` une valeur largement supérieure à la durée totale de travail prévue. Vous ne pouvez plus la modifier
une fois la sandbox démarrée. Pour découvrir les quatre paramètres de délai d’expiration et leurs interactions, consultez
[Délais d’expiration](/fr/products/sandboxes/client/guides/sandbox-configuration#timeouts). Pour la limite de 30 jours et
des exemples de configuration pour Serverless et CKS, consultez [Exécuter des Sandbox de longue durée](/fr/products/sandboxes/long-running-sandboxes).

<h2 id="operations-and-lifecycle">
  Opérations et cycle de vie
</h2>

Les opérations telles que `exec()`, `read_file()` et `write_file()` démarrent automatiquement la sandbox si nécessaire,
puis attendent l’état RUNNING avant de continuer :

```python theme={"system"}
# Sandbox de la Session, pas encore démarrée
sandbox = session.sandbox()

# exec() gère tout : démarrage, attente de l’état RUNNING, puis exécution de la commande
result = sandbox.exec(["echo", "hello"]).result()
```

Le délai d’expiration de l’opération (`timeout_seconds`) ne s’applique qu’une fois la sandbox à l’état RUNNING. Le temps
de démarrage n’est pas pris en compte.

<h2 id="stop-a-sandbox-and-end-of-life">
  Arrêter une sandbox et fin de vie
</h2>

Cette section explique comment arrêter une sandbox, ce qui se passe après son arrêt et quand utiliser `stop()` plutôt que `delete()`.

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

`stop()` envoie une requête d'arrêt et renvoie `OperationRef[None]`. Le sandbox passe par l'état
`TERMINATING` (drainage pendant le délai de grâce) avant d'atteindre un état terminal. L'`OperationRef`
renvoyé est résolu lorsque le backend confirme l'état terminal, et non simplement lorsque l'appel RPC
d'arrêt aboutit :

```python theme={"system"}
sandbox.stop().result()  # Bloquant jusqu’à l’état terminal
```

Paramètres :

| Paramètre | Par défaut | Rôle |
| - | - | - |
| `graceful_shutdown_seconds` | 10.0 | Délai de grâce accordé aux processus pour se terminer avant l’arrêt forcé |
| `snapshot_on_stop` | `False` | Capturer l’état du système de fichiers de la sandbox avant l’arrêt |
| `missing_ok` | `False` | Renvoyer normalement si la sandbox n’existe déjà plus (au lieu de lever une exception) |

```python theme={"system"}
# Capturer l’état pour le débogage
sandbox.stop(snapshot_on_stop=True).result()

# Nettoyage idempotent : l’appel est sans risque même si la sandbox n’existe plus
sandbox.stop(missing_ok=True).result()

# Laisser plus de temps aux processus pour s’arrêter
sandbox.stop(graceful_shutdown_seconds=30.0).result()
```

`stop()` gère les démarrages en cours : si un démarrage est encore en cours de traitement, la méthode attend qu'il se termine avant de procéder à l'arrêt. Les appels simultanés ou répétés à `stop()` partagent une seule opération d'arrêt et
n'émettent pas de RPC d'arrêt en double. Vous pouvez donc appeler `stop()` plusieurs fois sans risque et à moindre coût.

<h3 id="post-stop-behavior">
  Comportement après l’arrêt
</h3>

Une fois `stop()` appelé, la sandbox passe par l’état `TERMINATING` (état de drainage pendant le délai de grâce)
puis atteint un état terminal (`COMPLETED` ou `FAILED`). Dès que `stop()` a été appelé,
la sandbox devient inutilisable. Toute opération ultérieure lève `SandboxNotRunningError` :

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

# Ces appels lèvent SandboxNotRunningError
sandbox.exec(["echo", "hello"])  # Lève l'exception
sandbox.read_file("/path")       # Lève l'exception
```

La propriété `status` est mise en cache lors du dernier appel d’API. Pour obtenir des données à jour avant l’arrêt, utilisez
`get_status()` :

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

<h3 id="context-manager-exit">
  Sortie du gestionnaire de contexte
</h3>

Les gestionnaires de contexte appellent automatiquement `stop()` à leur sortie :

```python theme={"system"}
with Sandbox.run() as sandbox:
    result = sandbox.exec(["echo", "hello"]).result()
# stop() est appelé ici, même si une exception a été levée dans le bloc
```

Si une exception est en cours de propagation, le gestionnaire de contexte ignore les erreurs d’arrêt afin de ne pas masquer
l’exception d’origine.

<h3 id="stop-compared-to-delete">
  stop() ou delete()
</h3>

| | `stop()` | `delete()` |
| - | - | - |
| Cible | Instance de sandbox en cours d’exécution | Sandbox par ID (méthode de classe) |
| Objectif | Arrêt en douceur | Suppression définitive ou nettoyage des Sandbox orphelines |
| Instance requise ? | Oui | Non. Utilisez `Sandbox.delete(sandbox_id)` |
| `missing_ok` | Oui | Oui |

Utilisez `stop()` pour les Sandbox que vous êtes en train d’utiliser. Utilisez `delete()` pour nettoyer les Sandbox
trouvées via `Sandbox.list()` ou `Sandbox.from_id()` :

```python theme={"system"}
# Arrêter une sandbox que vous avez créée
sandbox.stop().result()

# Supprimer une sandbox orpheline à partir de son ID
Sandbox.delete("sandbox-abc123", missing_ok=True).result()
```

Voir le [guide des modèles de nettoyage](/fr/products/sandboxes/client/guides/cleanup-patterns) pour connaître les stratégies de gestion des ressources orphelines et de nettoyage par lots.

<h2 id="under-the-hood">
  Fonctionnement interne
</h2>

Le SDK exécute toutes les opérations gRPC sur un thread démon en arrière-plan, doté de sa propre boucle d'événements asyncio.
Cette conception a les conséquences suivantes :

* L'API synchrone (`.result()`) bloque le thread appelant pendant que la boucle en arrière-plan gère
  l'appel réseau.
* L'API asynchrone (`await`) s'appuie sur cette même boucle en arrière-plan : les deux modèles reposent donc sur la même
  implémentation sous-jacente.
* La boucle en arrière-plan démarre de manière différée, lors de la première utilisation. Les canaux gRPC sont eux aussi créés de manière différée.
* Le démarrage automatique consiste à vérifier si `sandbox_id` vaut None avant chaque opération et, le cas échéant, à déclencher
  `start()`.
* À la fin du processus, les handlers de nettoyage (atexit + handlers de signaux) arrêtent tous les Sandbox des
  sessions enregistrées. Un second Ctrl+C pendant le nettoyage force l'arrêt immédiat.

Cette architecture évite les problèmes liés à l'utilisation de plusieurs boucles d'événements et fonctionne dans les notebooks Jupyter sans
`nest_asyncio`. Pour connaître les modèles d'utilisation, consultez le [guide comparant les modèles synchrone et asynchrone](/fr/products/sandboxes/client/guides/sync-vs-async).

<h2 id="common-patterns">
  Schémas courants
</h2>

Les exemples suivants illustrent des schémas de bout en bout qui combinent les étapes du cycle de vie décrites ci-dessus.

<h3 id="quick-one-off">
  Exécution ponctuelle rapide
</h3>

Exécutez une commande et récupérez le résultat. Le gestionnaire de contexte se charge du nettoyage :

```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">
  Démarrage contrôlé
</h3>

Distinguez les erreurs de démarrage des erreurs d’opération :

```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">
  Sandbox de longue durée
</h3>

Attendez la fin de l’exécution de la commande principale :

```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">
  Reconnexion
</h3>

Reconnectez-vous à une sandbox créée lors d’une session ou d’un processus précédent :

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

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

# from_id() récupère le statut actuel, mais ne démarre ni ne vérifie la sandbox
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">
  Lot parallèle avec une session
</h3>

Créez de multiples Sandbox et attendez les résultats :

```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)
```
