fix(gateway): harden launchd reload handoff race recovery (#84641) · openclaw/openclaw@fc7a531
NianJiuZst
·
2026-05-22
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai
|
35 | 35 | |
36 | 36 | ### Fixes |
37 | 37 | |
| 38 | +- Gateway/LaunchAgent: wait for launchd reload bootout to finish and fall back to kickstart when bootstrap races, so reload handoff does not leave the service deregistered. Fixes #84630. (#84641) Thanks @NianJiuZst. |
38 | 39 | - CLI/update: preserve managed Gateway service environment during package cutovers so macOS LaunchAgent repair/restart reads the pre-update service state instead of caller shell state. (#83026) |
39 | 40 | - Agents/providers: honor per-model `api` and `baseUrl` overrides in custom provider auth hooks and transport selection. Fixes #80487. (#80488) Thanks @huveewomg. |
40 | 41 | - Gateway/restart: eager-load the lifecycle runtime before in-place upgrade signal handling so package replacement does not deadlock restart imports. (#84890) Thanks @myps6415. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -94,7 +94,7 @@ describe("scheduleDetachedLaunchdRestartHandoff", () => {
|
94 | 94 | expect(args[1]).not.toContain('basename "$service_target"'); |
95 | 95 | }); |
96 | 96 | |
97 | | -it("bootouts and bootstraps for reload mode", () => { |
| 97 | +it("polls after bootout and falls back to kickstart on bootstrap failure for reload mode", () => { |
98 | 98 | spawnMock.mockReturnValue({ pid: 4242, unref: unrefMock }); |
99 | 99 | |
100 | 100 | scheduleDetachedLaunchdRestartHandoff({ |
@@ -110,8 +110,12 @@ describe("scheduleDetachedLaunchdRestartHandoff", () => {
|
110 | 110 | expect(args[1]).toContain("openclaw restart attempt source=launchd-handoff mode=reload"); |
111 | 111 | expect(args[1]).toContain('launchctl enable "$service_target"'); |
112 | 112 | expect(args[1]).toContain('launchctl bootout "$service_target"'); |
| 113 | +// polls until launchd finishes the async unload before re-bootstrapping |
| 114 | +expect(args[1]).toContain("bootout_wait_count="); |
| 115 | +expect(args[1]).toContain('if ! launchctl print "$service_target" >/dev/null 2>&1; then'); |
113 | 116 | expect(args[1]).toContain('if launchctl bootstrap "$domain" "$plist_path"; then'); |
114 | | -expect(args[1]).not.toContain('launchctl kickstart -k "$service_target"'); |
| 117 | +// fallback: kickstart -k on bootstrap failure so service isn't left deregistered |
| 118 | +expect(args[1]).toContain('launchctl kickstart -k "$service_target"'); |
115 | 119 | }); |
116 | 120 | |
117 | 121 | it("sanitizes restart helper environment overrides before spawning", () => { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -153,17 +153,33 @@ exit "$status"
|
153 | 153 | if (mode === "reload") { |
154 | 154 | // Reloading is required after plist content changes; kickstart alone keeps |
155 | 155 | // launchd's already-loaded stdout/stderr/stdin paths. |
| 156 | +// After bootout we poll until launchd finishes the async unload before |
| 157 | +// re-bootstrapping to avoid EIO (Bootstrap failed: 5) from the race. |
| 158 | +// If bootstrap still fails, kickstart -k as a fallback to keep the service |
| 159 | +// alive rather than leaving it deregistered. |
| 160 | +const bootoutWaitLoop = `bootout_wait_count="${START_AFTER_EXIT_PRINT_RETRY_COUNT}" |
| 161 | +while [ "$bootout_wait_count" -gt 0 ]; do |
| 162 | + if ! launchctl print "$service_target" >/dev/null 2>&1; then |
| 163 | + break |
| 164 | + fi |
| 165 | + bootout_wait_count=$((bootout_wait_count - 1)) |
| 166 | + sleep ${START_AFTER_EXIT_PRINT_RETRY_DELAY_SECONDS} |
| 167 | +done |
| 168 | +`; |
156 | 169 | return `service_target="$1" |
157 | 170 | domain="$2" |
158 | 171 | plist_path="$3" |
159 | 172 | ${waitForCallerPid} |
160 | 173 | status=0 |
161 | 174 | launchctl enable "$service_target" |
162 | 175 | launchctl bootout "$service_target" >/dev/null 2>&1 || true |
| 176 | +${bootoutWaitLoop} |
163 | 177 | if launchctl bootstrap "$domain" "$plist_path"; then |
164 | 178 | status=0 |
165 | 179 | else |
166 | 180 | status=$? |
| 181 | + launchctl kickstart -k "$service_target" |
| 182 | + status=$? |
167 | 183 | fi |
168 | 184 | if [ "$status" -eq 0 ]; then |
169 | 185 | printf '[%s] openclaw restart done source=launchd-handoff mode=${mode}\\n' "$(date -u +%FT%TZ)" >&2 |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。