fix(macos): signed rebuild no longer stalls on app shutdown (#103957)

* fix(macos): prevent restart termination hangs

* fix(macos): keep restart failsafe off main queue

* refactor(macos): drop superseded failsafe changes
This commit is contained in:
Peter Steinberger
2026-07-28 12:13:22 -04:00
committed by GitHub
parent bb295df6ea
commit 3506f36555
2 changed files with 27 additions and 6 deletions

View File

@@ -172,7 +172,11 @@ fi
acquire_lock
kill_all_openclaw() {
for _ in {1..10}; do
local max_attempts=20
local poll_seconds=0.3
# The app's signal watcher forces exit after 3s. Keep a scheduling margin, then
# fail closed if a truly stuck process still survives the bounded grace period.
for ((attempt=0; attempt<max_attempts; attempt++)); do
local pids=""
pids="$(openclaw_process_pids)"
if [[ -z "${pids}" ]]; then
@@ -181,7 +185,7 @@ kill_all_openclaw() {
while IFS= read -r pid; do
kill "${pid}" 2>/dev/null || true
done <<< "${pids}"
sleep 0.3
sleep "${poll_seconds}"
done
[[ -z "$(openclaw_process_pids)" ]]
}

View File

@@ -681,28 +681,45 @@ describe("scripts/restart-mac.sh", () => {
);
expect(result.status).toBe(1);
expect(killCalls).toContain("321\n");
expect(killCalls.trim().split(/\r?\n/u)).toHaveLength(20);
expect(result.stdout).toBe("");
expect(result.stderr).toBe("");
});
it("passes restart cleanup when the final kill attempt clears the process", () => {
it("waits beyond the app signal failsafe for scoped processes to exit", () => {
const { killCalls, result } = runCleanupFunction(
[
"#!/usr/bin/env bash",
'kill_count="$(wc -l < "$OPENCLAW_TEST_KILL_CALLS" 2>/dev/null || echo 0)"',
'if [[ "$kill_count" -lt 10 ]]; then',
'if [[ "$kill_count" -lt 11 ]]; then',
" printf '%s\\n' ' 321 /worktree/dist/OpenClaw.app/Contents/MacOS/OpenClaw --attach-only'",
"fi",
].join("\n"),
);
expect(result.status).toBe(0);
expect(killCalls.trim().split(/\r?\n/u)).toHaveLength(10);
expect(killCalls.trim().split(/\r?\n/u)).toHaveLength(11);
expect(result.stdout).toBe("");
expect(result.stderr).toBe("");
});
it("keeps the restart grace period longer than the app signal failsafe", () => {
const script = readFileSync(restartScriptPath, "utf8");
const cleanupBlock = script.slice(
script.indexOf("kill_all_openclaw()"),
script.indexOf("stop_launch_agent()"),
);
const watcher = readFileSync(
"apps/macos/Sources/OpenClaw/TerminationSignalWatcher.swift",
"utf8",
);
const maxAttempts = Number(cleanupBlock.match(/local max_attempts=(\d+)/u)?.[1]);
const pollSeconds = Number(cleanupBlock.match(/local poll_seconds=([\d.]+)/u)?.[1]);
const failsafeSeconds = Number(watcher.match(/signalExitFailsafeSeconds = ([\d.]+)/u)?.[1]);
expect(maxAttempts * pollSeconds).toBeGreaterThan(failsafeSeconds);
});
it("passes restart cleanup when scoped processes are gone", () => {
const { killCalls, result } = runCleanupFunction("#!/usr/bin/env bash\nexit 0\n");