Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 71 additions & 2 deletions apps/web/src/components/sidebar/updateEverythingRun.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { EnvironmentId } from "@t3tools/contracts";
import { EnvironmentId, type ServerConfig } from "@t3tools/contracts";
import { Atom } from "effect/reactivity";
import { afterEach, describe, expect, it, vi } from "vite-plus/test";

import { appAtomRegistry } from "~/rpc/atomRegistry";
import { serverEnvironment } from "~/state/server";
import { startDesktopUpdateLaunchNotice } from "./updateEverythingRun";
import type { ServerUpdateOutcome, ServerUpdateTarget } from "../ServerUpdateAction";
import type { UpdateEverythingRun } from "./updateEverything.logic";
import {
retryFailedUpdateRunServers,
setUpdateEverythingRun,
startDesktopUpdateLaunchNotice,
} from "./updateEverythingRun";

function relaunchWhileRestarting(environmentId: EnvironmentId) {
const now = 1_800_000_000_000;
Expand Down Expand Up @@ -68,3 +75,65 @@ describe("startDesktopUpdateLaunchNotice", () => {
}
});
});

describe("retryFailedUpdateRunServers", () => {
it("settles a failed server that already restarted on the target and retries only the others", () => {
const targetVersion = "9.9.9";
const serverVersions = new Map([
[EnvironmentId.make("already-updated"), targetVersion],
[EnvironmentId.make("behind"), "9.9.8"],
]);
using _config = vi
.spyOn(serverEnvironment, "configValueAtom")
.mockImplementation((environmentId) =>
Atom.make({
environment: { serverVersion: serverVersions.get(environmentId!) },
} as unknown as ServerConfig),
);
const rejection = "Remote updates must select a newer server version.";
const failedMachine = (environmentId: EnvironmentId) => ({
environmentId,
label: environmentId,
updatesProviders: false,
server: {
environmentId,
serverLabel: environmentId,
selfUpdate: "boot-service" as const,
targetVersion,
},
handedOffVersion: null,
startedAt: 0,
result: "failed" as const,
failure: rejection,
providerFailure: null,
});
setUpdateEverythingRun(() => ({
localVersion: null,
phase: "servers",
restartAt: null,
machines: [...serverVersions.keys()].map(failedMachine),
}));
const update = vi.fn(
(_target: ServerUpdateTarget) => new Promise<ServerUpdateOutcome>(() => {}),
);

retryFailedUpdateRunServers(update);

let run: UpdateEverythingRun | null = null;
setUpdateEverythingRun((current) => {
run = current;
return null;
});
expect(update.mock.calls.map(([target]) => target.environmentId)).toEqual(["behind"]);
expect(
(run as UpdateEverythingRun | null)?.machines.map(({ environmentId, result, server }) => ({
environmentId,
result,
retryable: server !== null,
})),
).toEqual([
{ environmentId: "already-updated", result: "done", retryable: false },
{ environmentId: "behind", result: null, retryable: true },
]);
});
});
36 changes: 27 additions & 9 deletions apps/web/src/components/sidebar/updateEverythingRun.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useAtomValue } from "@effect/atom-react";
import { serverUpdateStateAtom } from "@t3tools/client-runtime/state/server";
import { EnvironmentId } from "@t3tools/contracts";
import { EnvironmentId, type ServerConfig } from "@t3tools/contracts";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import { AsyncResult, Atom } from "effect/reactivity";
Expand Down Expand Up @@ -63,6 +63,13 @@ export function settleUpdateRunMachine(
);
}

function runsVersion(config: ServerConfig | null, targetVersion: string): boolean {
return (
config !== null &&
resolveVersionMismatch(config.environment.serverVersion, targetVersion) === null
);
}

export function retryFailedUpdateRunServers(
update: (
target: ServerUpdateTarget,
Expand All @@ -75,15 +82,31 @@ export function retryFailedUpdateRunServers(
? [{ target: machine.server, providerFailure: machine.providerFailure }]
: [],
);
const targets = failed.map(({ target }) => target);
const pending: typeof failed = [];
for (const entry of failed) {
// The host rejects a same-version update; a server that came back on the target is done.
if (
runsVersion(
appAtomRegistry.get(serverEnvironment.configValueAtom(entry.target.environmentId)),
entry.target.targetVersion,
)
) {
settleUpdateRunMachine(entry.target.environmentId, entry.providerFailure, {
serverUpdated: true,
});
} else {
pending.push(entry);
}
}
const targets = pending.map(({ target }) => target);
const retried = new Set(targets.map((target) => target.environmentId));
const startedAt = Date.now();
updateRunMachines((machine) =>
retried.has(machine.environmentId)
? { ...machine, result: null, failure: null, startedAt }
: machine,
);
for (const { target, providerFailure } of failed) {
for (const { target, providerFailure } of pending) {
void update(target, { quiet: true }).then((outcome) =>
settleUpdateRunMachine(
target.environmentId,
Expand Down Expand Up @@ -309,12 +332,7 @@ function followHandedOffServers(servers: ReadonlyArray<HandedOffServer>): void {
const unsubscribe = appAtomRegistry.subscribe(
serverEnvironment.configValueAtom(server.environmentId),
(config) => {
if (
config !== null &&
resolveVersionMismatch(config.environment.serverVersion, server.targetVersion) === null
) {
settle(null);
}
if (runsVersion(config, server.targetVersion)) settle(null);
},
{ immediate: true },
);
Expand Down
Loading