mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(gateway): harden launchd reload handoff race recovery (#84641)
* fix(gateway): harden launchd reload handoff race recovery * docs(changelog): mention launchd reload handoff race fix --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- 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.
|
||||
- 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)
|
||||
- Agents/providers: honor per-model `api` and `baseUrl` overrides in custom provider auth hooks and transport selection. Fixes #80487. (#80488) Thanks @huveewomg.
|
||||
- Gateway/restart: eager-load the lifecycle runtime before in-place upgrade signal handling so package replacement does not deadlock restart imports. (#84890) Thanks @myps6415.
|
||||
|
||||
@@ -94,7 +94,7 @@ describe("scheduleDetachedLaunchdRestartHandoff", () => {
|
||||
expect(args[1]).not.toContain('basename "$service_target"');
|
||||
});
|
||||
|
||||
it("bootouts and bootstraps for reload mode", () => {
|
||||
it("polls after bootout and falls back to kickstart on bootstrap failure for reload mode", () => {
|
||||
spawnMock.mockReturnValue({ pid: 4242, unref: unrefMock });
|
||||
|
||||
scheduleDetachedLaunchdRestartHandoff({
|
||||
@@ -110,8 +110,12 @@ describe("scheduleDetachedLaunchdRestartHandoff", () => {
|
||||
expect(args[1]).toContain("openclaw restart attempt source=launchd-handoff mode=reload");
|
||||
expect(args[1]).toContain('launchctl enable "$service_target"');
|
||||
expect(args[1]).toContain('launchctl bootout "$service_target"');
|
||||
// polls until launchd finishes the async unload before re-bootstrapping
|
||||
expect(args[1]).toContain("bootout_wait_count=");
|
||||
expect(args[1]).toContain('if ! launchctl print "$service_target" >/dev/null 2>&1; then');
|
||||
expect(args[1]).toContain('if launchctl bootstrap "$domain" "$plist_path"; then');
|
||||
expect(args[1]).not.toContain('launchctl kickstart -k "$service_target"');
|
||||
// fallback: kickstart -k on bootstrap failure so service isn't left deregistered
|
||||
expect(args[1]).toContain('launchctl kickstart -k "$service_target"');
|
||||
});
|
||||
|
||||
it("sanitizes restart helper environment overrides before spawning", () => {
|
||||
|
||||
@@ -153,6 +153,19 @@ exit "$status"
|
||||
if (mode === "reload") {
|
||||
// Reloading is required after plist content changes; kickstart alone keeps
|
||||
// launchd's already-loaded stdout/stderr/stdin paths.
|
||||
// After bootout we poll until launchd finishes the async unload before
|
||||
// re-bootstrapping to avoid EIO (Bootstrap failed: 5) from the race.
|
||||
// If bootstrap still fails, kickstart -k as a fallback to keep the service
|
||||
// alive rather than leaving it deregistered.
|
||||
const bootoutWaitLoop = `bootout_wait_count="${START_AFTER_EXIT_PRINT_RETRY_COUNT}"
|
||||
while [ "$bootout_wait_count" -gt 0 ]; do
|
||||
if ! launchctl print "$service_target" >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
bootout_wait_count=$((bootout_wait_count - 1))
|
||||
sleep ${START_AFTER_EXIT_PRINT_RETRY_DELAY_SECONDS}
|
||||
done
|
||||
`;
|
||||
return `service_target="$1"
|
||||
domain="$2"
|
||||
plist_path="$3"
|
||||
@@ -160,10 +173,13 @@ ${waitForCallerPid}
|
||||
status=0
|
||||
launchctl enable "$service_target"
|
||||
launchctl bootout "$service_target" >/dev/null 2>&1 || true
|
||||
${bootoutWaitLoop}
|
||||
if launchctl bootstrap "$domain" "$plist_path"; then
|
||||
status=0
|
||||
else
|
||||
status=$?
|
||||
launchctl kickstart -k "$service_target"
|
||||
status=$?
|
||||
fi
|
||||
if [ "$status" -eq 0 ]; then
|
||||
printf '[%s] openclaw restart done source=launchd-handoff mode=${mode}\\n' "$(date -u +%FT%TZ)" >&2
|
||||
|
||||
Reference in New Issue
Block a user