---
title: バックエンドプロセスのライフサイクル
description: Windows Job Object、エミュレーターの脱離、環境変数マーカーによるフォールバック、Unix プロセスグループの設計。
updated: 2026-10-20
---

# バックエンドプロセスのライフサイクル

ランチャー終了時によく発生する不具合には、**終了すべきプロセスが残存する**（`gui.py` のプロセスリーク）ことと、**終了してはならないプロセスが巻き添えで終了する**（エミュレーターが道連れ終了される）ことの2つがあります。このページでは、両者を正確に分離制御する仕組みについて解説します。

## <DocIcon name="box" /> プロセスツリー構造

```text
alas-launcher（ランチャー本体）
└── gui.py（バックエンド）
    ├── instance worker（スケジューラー）
    └── エミュレーター（worker から起動されるが、バックエンドとは独立して生存すべきプロセス）
```

達成すべき制御目標：

| プロセス | ランチャー終了時の期待動作 |
| --- | --- |
| `gui.py` | **必ず終了** |
| instance worker | **必ず終了** |
| エミュレーター | **生存・保持** |

## <DocIcon name="monitor" /> Windows：Job Object による制御

`backend_process.rs` はバックエンド向けに Windows Job Object を作成し、以下の制限フラグを設定します：

| 制限フラグ | 役割 |
| --- | --- |
| `JOB_OBJECT_LIMIT_BREAKAWAY_OK` | 子プロセスが明示的に Job からの脱離（Breakaway）を要求することを許可 |
| `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` | 最後の Job ハンドルが閉じられた際、配下の全通常ワーカーを一括終了 |

::: danger SILENT_BREAKAWAY は使用厳禁です
`JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK` を設定すると、通常の Python ワーカープロセスまでもが自動的に脱離してしまい、終了時にプロセスリークが発生します。必ず「明示的に要求した場合のみ脱離可能」とする必要があります。
:::

### なぜエミュレーターが道連れ終了されてしまうのか

Windows の仕様上、Job Object の所属属性は子孫プロセスへ自動継承されます。バックエンドが短命な `cmd /c start` を経由してプロセスツリーを切り離そうとしても、生成されたエミュレーターは依然として元の Job Object に属しているため、`TerminateJobObject` が実行された際に巻き添えで強制終了されます。

これを解決するための相互プロトコル：

1. ランチャーはバックエンド起動時に `ALAS_LAUNCHER_JOB_BREAKAWAY=1` を注入し、「脱離機能に対応している」ことを通知します。
2. バックエンドの `PlatformWindows.execute(wait=False)` はこの環境変数を検知した場合、エミュレーターの起動コマンドに `CREATE_BREAKAWAY_FROM_JOB` フラグを付与します。
3. 同時に、複製される環境変数テーブルから `ALAS_LAUNCHER_PID`、`ALAS_LAUNCHER_JOB_BREAKAWAY`、`ALAS_WEBUI_TRUST_SECRET` を明示的に削除します。

これにより、エミュレーターはバックエンドの Job Object に属さなくなり、かつランチャーのフォールバックプロセススキャンにもヒットしなくなります。

::: warning 双方の更新が不可欠です
古いバックエンドは脱離を要求せず、古いランチャーは脱離を許可しません。片側だけを修正しても「エミュレーターが道連れ終了される」か「ワーカーがリークする」かのどちらかの不具合が再発します。
:::

### ルートプロセスの生成順序

1. Python ルートプロセスをサスペンド（一時停止）状態で生成。
2. プロセスを Job Object に登録。
3. プロセスの実行を再開（Resume）。

登録や再開に失敗した場合はルートプロセスを即時終了・回収し、起動時の競合やゾンビ化を防ぎます。Job ハンドルは `OwnedHandle` によって管理され、正常終了、初期化失敗、パニック発生時のいずれにおいても確実に解放されます。

### 明示的終了処理

ルートプロセスの終了待機に加え、Job Object 内のアクティブなプロセス数を監視し、通常のワーカーがすべて終了するまで最大5秒間待機します。タイムアウトまたは監視失敗時はエラーを返します。**ルートプロセスの終了をもってグループ全体のクリーンアップ完了と誤認してはなりません**。

## <DocIcon name="laptop" /> Unix：プロセスグループによる制御

Unix 環境には Job Object が存在しないため、プロセスグループを利用します：

- バックエンドは独立したプロセスグループ内で起動されます。
- 終了時はプロセスグループ全体に対してシグナルを送信し、回収を待機します。
- エミュレーターの脱離処理は起動方式（`setsid` 等）に依存し、Windows の Breakaway 宣言とは別系統のロジックとなります。

## <DocIcon name="key" /> フォールバック機構：環境変数マーカー

万が一 Job Object やプロセスグループによる回収が機能しなかった場合に備え、ランチャーは終了時にシステム全体のプロセス一覧をスキャンし、`ALAS_LAUNCHER_PID` を継承している残存プロセスを検出して強制終了します。

エミュレーター起動前に当該環境変数を**削除**しなければならないのはこのためです（削除を怠るとフォールバック回収によって強制終了されます）。

## <DocIcon name="flask" /> テスト仕様

```bash
cargo test --locked backend_process::tests
```

テストコードは自身をバックエンド、ワーカー、エミュレーターに見立てて以下の3系統のパスを検証します：

1. 明示的終了 → ルートプロセスと通常ワーカーは終了し、脱離したエミュレーターは生存。
2. Job ハンドルの破棄 → 同上の動作。
3. ルートプロセスの先行異常終了 → 子孫ワーカープロセスが確実に回収されること。

テスト用エミュレータープロセスの終了はテスト専用ハンドルを経由して行われ、ユーザーの実エミュレーターには影響しません。

コアリポジトリ側の `tests.test_emulator_process_lifetime` では、機能判定、環境変数クリーンアップ、同期管理コマンドの挙動が検証されます。

::: tip Windows ローカル環境での単体テスト
マニフェストにより管理者権限が要求されます。非特権環境ではテストコード自体がエラー 740 で起動できません。一時的なテストプロジェクトで `#[path]` を介して `backend_process.rs` と `window_util.rs` を読み込んで実行することも可能ですが、全結合テストの完全な代替にはなりません。
:::

## <DocIcon name="clipboard" /> 改修時チェックリスト

- [ ] 新規子プロセスに意図しない環境変数が継承されていないか
- [ ] エミュレーターの起動パイプラインで breakaway が正しく要求されているか
- [ ] 終了処理においてルートプロセスと子孫ワーカーの双方が待機されているか
- [ ] すべての分岐パスで Job ハンドルが確実に解放されているか
- [ ] Unix 側の分岐ロジックが破損していないか
- [ ] 3プラットフォームすべてでテストが通過しているか
- [ ] 実機上で「ランチャー終了後もエミュレーターが継続稼働すること」を確認したか

## <DocIcon name="file" /> 参考文献

- [Microsoft Job Objects 公式ドキュメント](https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects)
- リポジトリ内 `docs/backend-lifecycle.md`
