mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-08 09:24:41 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3b72b1550 | ||
|
|
f42926cffb |
@@ -64,6 +64,99 @@ function makeRelayOnlyHost(serverId: string): HostProfile {
|
||||
}
|
||||
|
||||
describe("DaemonStartService", () => {
|
||||
it("reports a daemon that stops during startup without registering an incomplete host", async () => {
|
||||
const fake = createFakeStore();
|
||||
const service = new DaemonStartService({
|
||||
store: fake.store,
|
||||
startDesktopDaemon: async () =>
|
||||
makeStatus({ status: "starting", serverId: "", listen: null }),
|
||||
getDesktopDaemonStatus: async () =>
|
||||
makeStatus({ status: "stopped", serverId: "", listen: null, pid: null }),
|
||||
});
|
||||
|
||||
expect(await service.start()).toEqual({
|
||||
ok: false,
|
||||
error: "The local daemon stopped before startup completed.",
|
||||
});
|
||||
expect(service.isRunning()).toBe(false);
|
||||
expect(fake.upserts).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps startup pending until a slow daemon becomes ready without restarting it", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const fake = createFakeStore();
|
||||
const starting = makeStatus({ status: "starting", serverId: "", listen: null });
|
||||
const startDesktopDaemon = vi.fn(async () => starting);
|
||||
const getDesktopDaemonStatus = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(starting)
|
||||
.mockResolvedValueOnce(makeStatus());
|
||||
const service = new DaemonStartService({
|
||||
store: fake.store,
|
||||
startDesktopDaemon,
|
||||
getDesktopDaemonStatus,
|
||||
});
|
||||
const result = service.start();
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
expect(service.isRunning()).toBe(true);
|
||||
expect(service.getLastError()).toBeNull();
|
||||
expect(fake.upserts).toEqual([]);
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
expect(await result).toEqual({ ok: true });
|
||||
expect(startDesktopDaemon).toHaveBeenCalledTimes(1);
|
||||
expect(fake.upserts).toEqual([
|
||||
{ listenAddress: "127.0.0.1:6767", serverId: "srv_desktop", hostname: "desktop" },
|
||||
]);
|
||||
expect(service.isRunning()).toBe(false);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("shows the daemon error if it exits while startup is pending", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const fake = createFakeStore();
|
||||
const service = new DaemonStartService({
|
||||
store: fake.store,
|
||||
startDesktopDaemon: async () => makeStatus({ status: "starting", serverId: "" }),
|
||||
getDesktopDaemonStatus: async () =>
|
||||
makeStatus({ status: "errored", error: "provider startup failed" }),
|
||||
});
|
||||
const result = service.start();
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
expect(await result).toEqual({ ok: false, error: "provider startup failed" });
|
||||
expect(fake.upserts).toEqual([]);
|
||||
expect(service.isRunning()).toBe(false);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("bounds the readiness wait and leaves a retryable error", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const fake = createFakeStore();
|
||||
const starting = makeStatus({ status: "starting", serverId: "" });
|
||||
const service = new DaemonStartService({
|
||||
store: fake.store,
|
||||
startDesktopDaemon: async () => starting,
|
||||
getDesktopDaemonStatus: async () => starting,
|
||||
});
|
||||
const result = service.start();
|
||||
await vi.advanceTimersByTimeAsync(120000);
|
||||
expect(await result).toEqual({
|
||||
ok: false,
|
||||
error: "The local daemon is still starting. Wait a moment and retry.",
|
||||
});
|
||||
expect(fake.upserts).toEqual([]);
|
||||
expect(service.isRunning()).toBe(false);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("upserts the connection on a successful daemon start", async () => {
|
||||
const fake = createFakeStore();
|
||||
const service = new DaemonStartService({
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { startDesktopDaemon, type DesktopDaemonStatus } from "@/desktop/daemon/desktop-daemon";
|
||||
import {
|
||||
getDesktopDaemonStatus,
|
||||
startDesktopDaemon,
|
||||
type DesktopDaemonStatus,
|
||||
} from "@/desktop/daemon/desktop-daemon";
|
||||
import { connectionFromListen } from "@/types/host-connection";
|
||||
import type { HostRuntimeStore } from "@/runtime/host-runtime";
|
||||
|
||||
@@ -14,6 +18,7 @@ type DaemonConnectionStore = Pick<HostRuntimeStore, "getHosts" | "upsertConnecti
|
||||
export interface DaemonStartServiceDeps {
|
||||
store: DaemonConnectionStore;
|
||||
startDesktopDaemon?: () => Promise<DesktopDaemonStatus>;
|
||||
getDesktopDaemonStatus?: () => Promise<DesktopDaemonStatus>;
|
||||
}
|
||||
|
||||
export async function upsertDesktopDaemonConnection(
|
||||
@@ -48,6 +53,7 @@ export async function upsertDesktopDaemonConnection(
|
||||
export class DaemonStartService {
|
||||
private readonly store: DaemonConnectionStore;
|
||||
private readonly invokeStartDesktopDaemon: () => Promise<DesktopDaemonStatus>;
|
||||
private readonly invokeGetDesktopDaemonStatus: () => Promise<DesktopDaemonStatus>;
|
||||
private readonly listeners = new Set<() => void>();
|
||||
private lastError: string | null = null;
|
||||
private inFlightCount = 0;
|
||||
@@ -55,6 +61,7 @@ export class DaemonStartService {
|
||||
constructor(deps: DaemonStartServiceDeps) {
|
||||
this.store = deps.store;
|
||||
this.invokeStartDesktopDaemon = deps.startDesktopDaemon ?? startDesktopDaemon;
|
||||
this.invokeGetDesktopDaemonStatus = deps.getDesktopDaemonStatus ?? getDesktopDaemonStatus;
|
||||
}
|
||||
|
||||
async start(): Promise<DaemonStartResult> {
|
||||
@@ -79,7 +86,21 @@ export class DaemonStartService {
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
const daemon = await this.invokeStartDesktopDaemon();
|
||||
let daemon = await this.invokeStartDesktopDaemon();
|
||||
// The desktop start command can return after its readiness deadline while
|
||||
// the owned process keeps initializing. Keep the startup UI pending until
|
||||
// that process can supply its host identity; do not launch it again.
|
||||
const readinessDeadline = Date.now() + 120_000;
|
||||
while (daemon.status === "starting") {
|
||||
if (Date.now() >= readinessDeadline) {
|
||||
return this.fail("The local daemon is still starting. Wait a moment and retry.");
|
||||
}
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
|
||||
daemon = await this.invokeGetDesktopDaemonStatus();
|
||||
}
|
||||
if (daemon.status !== "running") {
|
||||
return this.fail(daemon.error ?? "The local daemon stopped before startup completed.");
|
||||
}
|
||||
const result = await upsertDesktopDaemonConnection(this.store, daemon);
|
||||
return result.ok ? result : this.fail(result.error);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user