Compare commits

...
Author SHA1 Message Date
Mohamed Boudra e75ee909c9 test(daemon): stabilize restart diagnostics 2026-08-20 07:27:32 +00:00
5 changed files with 63 additions and 22 deletions
@@ -3,14 +3,16 @@ import { invokeDesktopCommand } from "@/desktop/electron/invoke";
import type { AgentSkillSelection } from "@getpaseo/protocol/messages"; import type { AgentSkillSelection } from "@getpaseo/protocol/messages";
export type DesktopDaemonState = "starting" | "running" | "stopped" | "errored"; export type DesktopDaemonState = "starting" | "running" | "stopped" | "errored";
export type DesktopDaemonStopReason = export const DESKTOP_DAEMON_STOP_REASONS = [
| "manual_ipc" "manual_ipc",
| "settings" "settings",
| "host_remove" "host_remove",
| "quit" "quit",
| "app_update" "app_update",
| "version_mismatch" "version_mismatch",
| "restart"; "restart",
] as const;
export type DesktopDaemonStopReason = (typeof DESKTOP_DAEMON_STOP_REASONS)[number];
export interface DesktopDaemonStatus { export interface DesktopDaemonStatus {
serverId: string; serverId: string;
@@ -45,7 +45,7 @@ function readWorkerPid(supervisorPid: number): number | null {
return null; return null;
} }
const result = spawnSync("ps", ["ax", "-o", "pid=,ppid="], { encoding: "utf8" }); const result = spawnSync("ps", ["ax", "-o", "pid=,ppid=,stat="], { encoding: "utf8" });
if (result.status !== 0 || result.error) { if (result.status !== 0 || result.error) {
return null; return null;
} }
@@ -55,12 +55,15 @@ function readWorkerPid(supervisorPid: number): number | null {
if (!trimmed) { if (!trimmed) {
continue; continue;
} }
const [pidToken, ppidToken] = trimmed.split(/\s+/); const [pidToken, ppidToken, statToken] = trimmed.split(/\s+/);
const pid = Number.parseInt(pidToken ?? "", 10); const pid = Number.parseInt(pidToken ?? "", 10);
const ppid = Number.parseInt(ppidToken ?? "", 10); const ppid = Number.parseInt(ppidToken ?? "", 10);
if (!Number.isInteger(pid) || !Number.isInteger(ppid)) { if (!Number.isInteger(pid) || !Number.isInteger(ppid)) {
continue; continue;
} }
if (statToken?.includes("Z")) {
continue;
}
if (ppid === supervisorPid && pid > 0) { if (ppid === supervisorPid && pid > 0) {
return pid; return pid;
} }
@@ -197,16 +200,27 @@ try {
await client?.close().catch(() => undefined); await client?.close().catch(() => undefined);
} }
await waitFor( try {
() => { await waitFor(
const workerPid = readWorkerPid(supervisorPid); () => {
return ( const workerPid = readWorkerPid(supervisorPid);
workerPid !== null && workerPid !== workerPidBeforeRestart && isProcessRunning(workerPid) return (
); workerPid !== null && workerPid !== workerPidBeforeRestart && isProcessRunning(workerPid)
}, );
20000, },
"worker pid did not change after restart request", 30000,
); "worker pid did not change after restart request",
);
} catch (error) {
const capturedSupervisorLogs = await readCapturedSupervisorLogs(
paseoHome,
recentSupervisorLogs,
);
throw new Error(
`worker pid did not change after restart request, logs:\n${capturedSupervisorLogs}`,
{ cause: error },
);
}
const workerPidAfterRestart = readWorkerPid(supervisorPid); const workerPidAfterRestart = readWorkerPid(supervisorPid);
assert(workerPidAfterRestart !== null, "worker process should exist after restart"); assert(workerPidAfterRestart !== null, "worker process should exist after restart");
@@ -216,6 +230,28 @@ try {
"worker pid should change after restart", "worker pid should change after restart",
); );
try {
await waitFor(
async () => {
const status = await readDaemonStatus(paseoHome);
return status.localDaemon === "running" && status.pid === supervisorPid;
},
30000,
"daemon did not return to running after restart",
);
} catch (error) {
const capturedSupervisorLogs = await readCapturedSupervisorLogs(
paseoHome,
recentSupervisorLogs,
);
throw new Error(
`daemon did not return to running after restart, logs:\n${capturedSupervisorLogs}`,
{
cause: error,
},
);
}
const statusAfterRestart = await readDaemonStatus(paseoHome); const statusAfterRestart = await readDaemonStatus(paseoHome);
assert.strictEqual( assert.strictEqual(
statusAfterRestart.localDaemon, statusAfterRestart.localDaemon,
@@ -1,5 +1,6 @@
export const CLIENT_SHUTDOWN_RPC_REASON = "client_shutdown_rpc"; export const CLIENT_SHUTDOWN_RPC_REASON = "client_shutdown_rpc";
export const DEFAULT_CLIENT_RESTART_RPC_REASON = "client_restart_rpc"; export const DEFAULT_CLIENT_RESTART_RPC_REASON = "client_restart_rpc";
export const DAEMON_UPDATE_RPC_REASON = "daemon_update";
export function normalizeClientRestartRpcReason(reason: string | undefined): string { export function normalizeClientRestartRpcReason(reason: string | undefined): string {
return reason?.trim() || DEFAULT_CLIENT_RESTART_RPC_REASON; return reason?.trim() || DEFAULT_CLIENT_RESTART_RPC_REASON;
@@ -7,6 +7,7 @@ import {
type DaemonSelfUpdater, type DaemonSelfUpdater,
} from "./daemon-self-updater.js"; } from "./daemon-self-updater.js";
import { getErrorMessage } from "@getpaseo/protocol/error-utils"; import { getErrorMessage } from "@getpaseo/protocol/error-utils";
import { DAEMON_UPDATE_RPC_REASON } from "../../lifecycle-reasons.js";
type DaemonUpdateRequest = Extract<SessionInboundMessage, { type: "daemon.update.request" }>; type DaemonUpdateRequest = Extract<SessionInboundMessage, { type: "daemon.update.request" }>;
@@ -87,7 +88,7 @@ export class DaemonSelfUpdateSessionController {
type: "restart", type: "restart",
clientId: this.clientId, clientId: this.clientId,
requestId: msg.requestId, requestId: msg.requestId,
reason: "daemon_update", reason: DAEMON_UPDATE_RPC_REASON,
}); });
} catch (error) { } catch (error) {
if (error instanceof DaemonSelfUpdateInProgressError) { if (error instanceof DaemonSelfUpdateInProgressError) {
@@ -87,6 +87,7 @@ import { ProviderUsageService } from "../services/quota-fetcher/service.js";
import { getProcessMemoryDiagnostics, getProcessUptimeSeconds } from "./process-diagnostics.js"; import { getProcessMemoryDiagnostics, getProcessUptimeSeconds } from "./process-diagnostics.js";
import { import {
CLIENT_SHUTDOWN_RPC_REASON, CLIENT_SHUTDOWN_RPC_REASON,
DAEMON_UPDATE_RPC_REASON,
normalizeClientRestartRpcReason, normalizeClientRestartRpcReason,
} from "./lifecycle-reasons.js"; } from "./lifecycle-reasons.js";
import { CLIENT_CAPS } from "@getpaseo/protocol/client-capabilities"; import { CLIENT_CAPS } from "@getpaseo/protocol/client-capabilities";
@@ -2919,7 +2920,7 @@ function getControlRpcLogInfo(
return { return {
requestType: message.type, requestType: message.type,
requestId: message.requestId, requestId: message.requestId,
reason: "daemon_update", reason: DAEMON_UPDATE_RPC_REASON,
}; };
} }
return null; return null;