後端進程生命週期
啟動器退出時最容易出的兩個錯誤是:該殺的沒殺(gui.py 泄漏)和不該殺的殺了(模擬器被一起關閉)。這一頁解釋兩者如何用同一套機制區分。
進程樹
alas-launcher(启动器)
└── gui.py(后端)
├── instance worker(调度器)
└── 模拟器(由 worker 启动,但应脱离后端生命周期)目標很明確:
| 進程 | 退出啟動器時 |
|---|---|
gui.py | 必須結束 |
| instance worker | 必須結束 |
| 模擬器 | 保留 |
Windows:Job Object
backend_process.rs 為後端建立一個 Job,設定:
| 限制 | 作用 |
|---|---|
JOB_OBJECT_LIMIT_BREAKAWAY_OK | 允許子進程顯式請求脱離 |
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | 最後一個 Job 句柄關閉時回收普通 worker |
不能用 SILENT_BREAKAWAY
JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK 會讓普通 Python worker 也自動脱離,導致退出後進程泄漏。必須是「顯式請求才能脱離」。
為什麼模擬器會被誤殺
Windows 會把 Job 歸屬傳給後代。即使後端用短命的 cmd /c start 斷開進程樹,模擬器仍在原來的 Job 裏,TerminateJobObject 會連帶關閉它。
解決方案是雙方協議:
- 啟動器向後端注入
ALAS_LAUNCHER_JOB_BREAKAWAY=1,聲明「我支援脱離」。 - 後端的
PlatformWindows.execute(wait=False)檢測到該變數後,給啟動命令加CREATE_BREAKAWAY_FROM_JOB。 - 同時從複製的環境中移除
ALAS_LAUNCHER_PID、ALAS_LAUNCHER_JOB_BREAKAWAY、ALAS_WEBUI_TRUST_SECRET。
這樣模擬器既不屬於後端 Job,也不會被殘留進程掃描誤殺。
協議必須兩邊一起更新
舊版後端不會請求脱離,舊啟動器不允許脱離。只改一邊就宣稱修復,結果是「要麼模擬器被關,要麼 worker 泄漏」。
根進程的建立順序
- 以掛起狀態建立 Python 根進程。
- 登記到 Job。
- 恢復運行。
登記或恢復失敗時終止並回收根進程,避免啟動競態和殘留。Job 句柄由 OwnedHandle 持有,初始化失敗、正常退出、錯誤展開都會釋放。
顯式終止
除等待根進程外,還查詢 Job 的活動進程數,最多等 5 秒確認普通 worker 全部退出。超時或查詢失敗返回錯誤——不能把根進程退出誤報為整組清理完成。
Unix:進程組
Unix 沒有 Job Object,使用進程組:
- 後端在獨立進程組中啟動。
- 退出時向進程組發信號,等待回收。
- 模擬器的脱離依賴啟動方式(例如
setsid),與 Windows 的能力聲明是兩套邏輯。
兜底:環境標記
即便 Job/進程組都不生效,啟動器還會在退出時掃描進程表,查找繼承了 ALAS_LAUNCHER_PID 的進程並回收。
這就是為什麼模擬器啟動前要移除該環境變數——否則它會命中兜底清理。
測試
cargo test --locked backend_process::tests測試程序自己充當後端、worker 和模擬器,檢查三條路徑:
- 顯式終止 → 根進程與普通 worker 退出,脱離的模擬器存活。
- 丟棄 Job 句柄 → 同上。
- 根進程提前退出 → 後代仍被回收。
測試通過已打開的專用進程句柄清理偽模擬器,不操作真實模擬器。
核心倉庫的 tests.test_emulator_process_lifetime 驗證能力判斷、環境清理、同步管理命令與失敗分支。
Windows 本地測試
manifest 要求管理員權限。未提權時完整測試程序會因錯誤 740 無法啟動。可以在臨時 Cargo 測試項目裏通過 #[path] 加載 backend_process.rs 與 window_util.rs 運行同一組測試——但這隻隔離了測試殼,不能當作全部啟動器測試通過。完整編譯另用 cargo check --locked --all-targets。
修改檢查清單
- [ ] 新子進程是否繼承了不該繼承的環境變數
- [ ] 模擬器啟動路徑是否請求了 breakaway
- [ ] 退出路徑是否既等根進程、又確認 worker 退出
- [ ] Job 句柄是否在所有分支釋放
- [ ] Unix 分支是否仍然有效
- [ ] 三平台測試是否都跑過
- [ ] 是否在真機上確認「退出啟動器後模擬器仍在」
參考
- Microsoft Job Objects
- 倉庫內
docs/backend-lifecycle.md