fix(scan): force-kill stale AE processes before each launch (fresh start)
Build backend images / build content-svc (push) Failing after 54s
Build backend images / build file-svc (push) Failing after 56s
Build backend images / build gateway (push) Failing after 57s
Build backend images / build identity-svc (push) Failing after 58s
Build backend images / build notification-svc (push) Failing after 1m4s
Build backend images / build render-svc (push) Failing after 2m27s
Build backend images / build studio-svc (push) Failing after 55s
Build backend images / build content-svc (push) Failing after 54s
Build backend images / build file-svc (push) Failing after 56s
Build backend images / build gateway (push) Failing after 57s
Build backend images / build identity-svc (push) Failing after 58s
Build backend images / build notification-svc (push) Failing after 1m4s
Build backend images / build render-svc (push) Failing after 2m27s
Build backend images / build studio-svc (push) Failing after 55s
PrepareFreshAE = taskkill AfterFX/aerender/AfterFXLib/dynamiclinkmanager/QT32 + 2s settle + clear crash markers, then launch. A hung/zombie AE from a prior job would otherwise block or corrupt the new run. RunScan now calls it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,27 +4,50 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// aeProcesses are the AE-related executables to force-kill before a fresh launch.
|
||||
var aeProcesses = []string{
|
||||
"AfterFX.exe", // the AE app
|
||||
"aerender.exe", // the headless renderer
|
||||
"AfterFXLib.exe", // AE render engine
|
||||
"dynamiclinkmanager.exe", // Dynamic Link
|
||||
"Adobe QT32 Server.exe", // QuickTime/media server
|
||||
}
|
||||
|
||||
// PrepareFreshAE guarantees a clean AE start before a scan/render: it force-kills
|
||||
// any leftover AE process (a hung/zombie instance from a prior job would otherwise
|
||||
// block or corrupt the new launch), waits for them to release file locks, then
|
||||
// clears the crash/Safe-Mode markers. Call this right before launching afterfx/aerender.
|
||||
func PrepareFreshAE() {
|
||||
if os.Getenv("APPDATA") == "" {
|
||||
return // non-Windows / dev
|
||||
}
|
||||
KillAEProcesses()
|
||||
time.Sleep(2 * time.Second) // let the OS reap processes + release locks
|
||||
ClearAECrashState()
|
||||
}
|
||||
|
||||
// KillAEProcesses force-terminates every AE-related process tree (taskkill is a
|
||||
// Windows built-in — no external dep). Errors (e.g. "process not found") are ignored.
|
||||
func KillAEProcesses() {
|
||||
for _, name := range aeProcesses {
|
||||
_ = exec.Command("taskkill", "/F", "/T", "/IM", name).Run()
|
||||
}
|
||||
}
|
||||
|
||||
// ClearAECrashState removes the markers After Effects uses to decide it crashed,
|
||||
// so the blocking "Crash Repair Options" (Safe Mode) dialog never appears on a
|
||||
// headless launch. Two parts:
|
||||
//
|
||||
// so the blocking "Crash Repair Options" (Safe Mode) dialog never appears:
|
||||
// 1. SCRPriorState.json in each AE prefs version dir (session crash-recovery state).
|
||||
// 2. HKCU\Software\Adobe\After Effects\AppStates — AE writes a per-session GUID
|
||||
// subkey on startup and removes it on a clean exit; a leftover one (after a
|
||||
// kill/crash) triggers Safe Mode. reg.exe is a Windows built-in (no external
|
||||
// dep / cgo), so we shell out to it.
|
||||
//
|
||||
// Targeted (vs. wiping all prefs) so the node keeps its AE preferences. Safe no-op
|
||||
// on non-Windows (APPDATA unset).
|
||||
// 2. HKCU\Software\Adobe\After Effects\AppStates — a leftover per-session GUID
|
||||
// (after a kill/crash) trips Safe Mode. reg.exe is a Windows built-in.
|
||||
// Targeted (vs. wiping all prefs) so the node keeps its AE preferences.
|
||||
func ClearAECrashState() {
|
||||
appData := os.Getenv("APPDATA")
|
||||
if appData == "" {
|
||||
return // non-Windows / dev
|
||||
return
|
||||
}
|
||||
|
||||
// 1. session crash-recovery files
|
||||
base := filepath.Join(appData, "Adobe", "After Effects")
|
||||
if entries, err := os.ReadDir(base); err == nil {
|
||||
for _, e := range entries {
|
||||
@@ -33,7 +56,5 @@ func ClearAECrashState() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. registry session/crash state
|
||||
_ = exec.Command("reg", "delete", `HKCU\Software\Adobe\After Effects\AppStates`, "/f").Run()
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func WriteScanScript(workDir string) (string, error) {
|
||||
// afterfx -r runs the script and the script calls app.quit(); we still poll for the
|
||||
// output file because afterfx can return before the file is flushed.
|
||||
func RunScan(ctx context.Context, afterfxPath, aepPath, workDir, outPath, mode string) ([]byte, error) {
|
||||
ClearAECrashState() // avoid the "Crash Repair Options" dialog hanging a headless launch
|
||||
PrepareFreshAE() // kill any stale AE + clear crash/Safe-Mode markers → guaranteed fresh launch
|
||||
scriptPath, err := WriteScanScript(workDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("write scan script: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user