mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix: harden external restart integration
This commit is contained in:
@@ -620,7 +620,7 @@ export async function runDaemonRestart(opts: DaemonLifecycleOptions = {}): Promi
|
||||
const handled = await restartGatewayWithoutServiceManager(unmanagedPort, restartIntent);
|
||||
if (handled) {
|
||||
restartedWithoutServiceManager = true;
|
||||
if ("previousLockIdentity" in handled && handled.previousLockIdentity) {
|
||||
if ("pid" in handled && handled.previousLockIdentity) {
|
||||
unmanagedPreviousLockIdentity = handled.previousLockIdentity;
|
||||
const healthWait = await resolveRestartListenerHealthWait(restartIntent);
|
||||
unmanagedRestartHealthAttempts = healthWait.attempts;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// response payloads returned by gateway.restart.request.
|
||||
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { restartHandlers } from "./restart.js";
|
||||
|
||||
@@ -185,6 +186,49 @@ describe("gateway.restart.request handler", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each([0.5, -1, MAX_TIMER_TIMEOUT_MS + 1, Number.MAX_SAFE_INTEGER])(
|
||||
"rejects an invalid targeted restart wait of %s ms",
|
||||
async (waitMs) => {
|
||||
const respond = await invokeRestartRequest({
|
||||
reason: "operator",
|
||||
target: {
|
||||
pid: process.pid,
|
||||
ownerId: "gateway-owner",
|
||||
port: 18_789,
|
||||
},
|
||||
restartIntent: { waitMs },
|
||||
});
|
||||
|
||||
expect(requestGatewayRestartWithSignalAdmission).not.toHaveBeenCalled();
|
||||
expect(respond).toHaveBeenCalledWith(false, undefined, {
|
||||
code: "INVALID_REQUEST",
|
||||
message: "invalid targeted gateway restart intent",
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("accepts the maximum timer-safe targeted restart wait", async () => {
|
||||
const respond = await invokeRestartRequest({
|
||||
reason: "operator",
|
||||
target: {
|
||||
pid: process.pid,
|
||||
ownerId: "gateway-owner",
|
||||
port: 18_789,
|
||||
},
|
||||
restartIntent: { waitMs: MAX_TIMER_TIMEOUT_MS },
|
||||
});
|
||||
|
||||
expect(requestGatewayRestartWithSignalAdmission).toHaveBeenCalledWith("operator", {
|
||||
reason: "operator",
|
||||
waitMs: MAX_TIMER_TIMEOUT_MS,
|
||||
});
|
||||
expect(respond).toHaveBeenCalledWith(true, {
|
||||
ok: true,
|
||||
status: "emitted",
|
||||
pid: process.pid,
|
||||
});
|
||||
});
|
||||
|
||||
it("backs off before an emoji that crosses the reason limit", async () => {
|
||||
mockScheduledRestart({ safe: true, summary: "safe to restart now" });
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Gateway RPC handlers for safe gateway restart requests and preflight state.
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion";
|
||||
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
|
||||
import { ErrorCodes, errorShape } from "../../../packages/gateway-protocol/src/index.js";
|
||||
import { readActiveGatewayLockIdentity } from "../../infra/gateway-lock.js";
|
||||
@@ -6,10 +7,8 @@ import {
|
||||
createSafeGatewayRestartPreflight,
|
||||
requestSafeGatewayRestart,
|
||||
} from "../../infra/restart-coordinator.js";
|
||||
import {
|
||||
type GatewayRestartIntent,
|
||||
requestGatewayRestartWithSignalAdmission,
|
||||
} from "../../infra/restart.js";
|
||||
import type { GatewayRestartIntent } from "../../infra/restart-intent.js";
|
||||
import { requestGatewayRestartWithSignalAdmission } from "../../infra/restart.js";
|
||||
import type { GatewayRequestHandlers } from "./types.js";
|
||||
|
||||
function isRestartRequestParams(value: unknown): value is Record<string, unknown> {
|
||||
@@ -74,8 +73,11 @@ function parseTargetedRestartIntent(
|
||||
const raw = (value ?? {}) as { force?: unknown; waitMs?: unknown };
|
||||
const force = raw.force === true;
|
||||
const waitMs =
|
||||
typeof raw.waitMs === "number" && Number.isFinite(raw.waitMs) && raw.waitMs >= 0
|
||||
? Math.floor(raw.waitMs)
|
||||
typeof raw.waitMs === "number" &&
|
||||
Number.isSafeInteger(raw.waitMs) &&
|
||||
raw.waitMs >= 0 &&
|
||||
raw.waitMs <= MAX_TIMER_TIMEOUT_MS
|
||||
? raw.waitMs
|
||||
: undefined;
|
||||
if (
|
||||
(raw.force !== undefined && typeof raw.force !== "boolean") ||
|
||||
|
||||
Reference in New Issue
Block a user