mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
fix(gateway): clarify skip-deferral reply drain (#128178)
Co-authored-by: gaoanze <gaoanze@meituan.com>
This commit is contained in:
+1
-1
@@ -37,7 +37,7 @@ openclaw daemon uninstall
|
||||
- `status`: shows service install state (launchd/systemd/schtasks) and probes Gateway health.
|
||||
- `install`: installs the service; `--force` reinstalls/overwrites an existing install.
|
||||
- `restart --safe`: asks the running Gateway to preflight active work and schedule one coalesced restart after work drains, bounded to 5 minutes. When that budget expires, the restart is forced anyway. Plain `restart` uses the service manager directly; `--force` is the immediate override.
|
||||
- `restart --safe --skip-deferral`: bypasses the active-work deferral gate so the Gateway restarts immediately even when blockers are reported. Requires `--safe`.
|
||||
- `restart --safe --skip-deferral`: bypasses only the active-work deferral gate. Shutdown may still wait for pending replies to drain before the Gateway process exits. Requires `--safe`.
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ openclaw gateway restart --wait 30s
|
||||
|
||||
`--safe` asks the running Gateway to preflight active work and schedule one coalesced restart after that work drains. The wait is bounded to 5 minutes; when the budget expires the restart is forced. `--safe` cannot combine with `--force` or `--wait`.
|
||||
|
||||
`--skip-deferral` bypasses the active-work deferral gate on a safe restart, so the Gateway restarts immediately even with reported blockers. It requires `--safe` — use it when a deferral is stuck on a runaway task.
|
||||
`--skip-deferral` bypasses only the safe-restart active-work deferral gate. It can move the Gateway into shutdown even while active-work blockers are reported, but the close-stage pending-reply drain still applies before the process exits. It requires `--safe` — use it when a deferral is stuck on a runaway task and reply delivery can still be allowed to settle.
|
||||
|
||||
`--wait <duration>` overrides the drain budget for a plain (non-safe) restart. Accepts bare milliseconds or unit suffixes `ms`, `s`, `m`, `h`, `d` (e.g. `30s`, `5m`, `1h30m`); `--wait 0` waits indefinitely. Not compatible with `--force` or `--safe`.
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// Safe gateway restart tests cover operator-facing acknowledgement copy.
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const callGatewayCli = vi.hoisted(() => vi.fn());
|
||||
const appendGatewayLifecycleAudit = vi.hoisted(() => vi.fn());
|
||||
const runtimeLog = vi.hoisted(() => vi.fn());
|
||||
const runtimeWriteJson = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("../../gateway/call.js", () => ({
|
||||
callGatewayCli,
|
||||
}));
|
||||
|
||||
vi.mock("../../runtime.js", () => ({
|
||||
defaultRuntime: {
|
||||
log: runtimeLog,
|
||||
writeJson: runtimeWriteJson,
|
||||
},
|
||||
writeRuntimeJson: (_runtime: unknown, payload: unknown) => runtimeWriteJson(payload),
|
||||
}));
|
||||
|
||||
vi.mock("./lifecycle-audit.js", () => ({
|
||||
appendGatewayLifecycleAudit,
|
||||
}));
|
||||
|
||||
describe("runSafeGatewayRestart", () => {
|
||||
beforeEach(() => {
|
||||
callGatewayCli.mockReset();
|
||||
appendGatewayLifecycleAudit.mockReset();
|
||||
runtimeLog.mockReset();
|
||||
runtimeWriteJson.mockReset();
|
||||
});
|
||||
|
||||
it("reports that skip-deferral still allows close-stage reply drain", async () => {
|
||||
const { runSafeGatewayRestart } = await import("./lifecycle-safe-restart.js");
|
||||
callGatewayCli.mockResolvedValueOnce({
|
||||
status: "scheduled",
|
||||
preflight: {
|
||||
safe: false,
|
||||
activeWork: {
|
||||
queueSize: 1,
|
||||
runningTasks: 0,
|
||||
activeRequests: 0,
|
||||
activeAgentRuns: 0,
|
||||
pendingReplies: 2,
|
||||
totalActive: 3,
|
||||
},
|
||||
blockers: [{ kind: "pending-replies", count: 2, message: "2 pending reply(ies)" }],
|
||||
summary: "restart deferred: 2 pending reply(ies)",
|
||||
},
|
||||
restart: { pid: 123 },
|
||||
});
|
||||
|
||||
await expect(
|
||||
runSafeGatewayRestart({ json: true, safe: true, skipDeferral: true }),
|
||||
).resolves.toBe(true);
|
||||
|
||||
expect(callGatewayCli).toHaveBeenCalledWith({
|
||||
method: "gateway.restart.request",
|
||||
params: { reason: "gateway.restart.safe", skipDeferral: true },
|
||||
timeoutMs: 10_000,
|
||||
});
|
||||
expect(runtimeWriteJson).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message:
|
||||
"safe restart requested; gateway bypassing active-work deferral; " +
|
||||
"shutdown may still wait for pending replies to drain",
|
||||
result: "scheduled",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -83,7 +83,8 @@ export async function runSafeGatewayRestart(
|
||||
? "safe restart requested; gateway will restart after active work drains " +
|
||||
"(bounded wait; may force after the timeout expires)"
|
||||
: skipDeferral
|
||||
? "safe restart requested; gateway bypassing active-work deferral"
|
||||
? "safe restart requested; gateway bypassing active-work deferral; " +
|
||||
"shutdown may still wait for pending replies to drain"
|
||||
: "safe restart requested; gateway will restart momentarily";
|
||||
const payload = {
|
||||
ok: true,
|
||||
|
||||
@@ -153,7 +153,11 @@ export function addGatewayServiceCommands(parent: Command, opts?: { statusDescri
|
||||
"(bounded wait; may force after the timeout expires)",
|
||||
false,
|
||||
)
|
||||
.option("--skip-deferral", "Bypass the safe-restart deferral gate; requires --safe", false)
|
||||
.option(
|
||||
"--skip-deferral",
|
||||
"Bypass the safe-restart active-work deferral gate; close-stage reply drain still applies; requires --safe",
|
||||
false,
|
||||
)
|
||||
.option(
|
||||
"--wait <duration>",
|
||||
"Wait duration before restart (ms, 10s, 5m; 0 waits indefinitely). " +
|
||||
|
||||
Reference in New Issue
Block a user