Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 39 additions & 0 deletions apps/server/src/cloud/bootService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@ it.layer(NodeServices.layer)("boot service install", (it) => {
current: true,
installedVersion: "1.2.3",
});
// A started install that reached the end removes its own marker.
const path = yield* Path.Path;
expect(
yield* fs.exists(path.join(path.dirname(statePath), SERVICE_RESTART_PENDING_FILE)),
).toBe(false);
// @effect-diagnostics-next-line preferSchemaOverJson:off - fixed launcher-owned test document.
const pendingState = JSON.stringify({
protocol: SERVICE_LAUNCHER_PROTOCOL,
Expand Down Expand Up @@ -803,6 +808,40 @@ it.layer(NodeServices.layer)("boot service install", (it) => {
}),
);

it.effect("keeps a launch agent install that failed to start out of the current state", () =>
Effect.gen(function* () {
const { service, fs, statePath, commands, control } = yield* makeHarness("darwin");
const path = yield* Path.Path;
const plan = yield* service.install();
const markerPath = path.join(path.dirname(statePath), SERVICE_RESTART_PENDING_FILE);
expect(yield* fs.exists(markerPath)).toBe(false);
control.failCommand = `launchctl bootstrap gui/501 ${plan.unitPath}`;

expect((yield* service.install().pipe(Effect.flip))._tag).toBe("BootServiceCommandError");

// A failed bootstrap leaves nothing else on disk to tell the difference
// from a healthy install, so without the marker `t3 service install`
// would call itself already installed and never retry.
expect(yield* fs.readFileString(markerPath)).toBe("1.2.3\n");
const failed = yield* service.status;
expect(failed.problems).toEqual(["restart-pending"]);
expect(failed.current).toBe(false);

// What `reconcileService` does with a status that is not current: install again.
control.failCommand = undefined;
commands.length = 0;
yield* service.install();

expect(commands.filter((command) => command.startsWith("launchctl "))).toEqual([
"launchctl bootout --wait gui/501/com.t3tools.t3code.service",
"launchctl enable gui/501/com.t3tools.t3code.service",
`launchctl bootstrap gui/501 ${plan.unitPath}`,
]);
expect(yield* fs.exists(markerPath)).toBe(false);
expect((yield* service.status).current).toBe(true);
}),
);

it.effect("restarts without overwriting a pending remote update on macOS", () =>
Effect.gen(function* () {
const { service, fs, statePath, commands } = yield* makeHarness("darwin");
Expand Down
14 changes: 7 additions & 7 deletions apps/server/src/cloud/bootService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -853,13 +853,13 @@ export const make = Effect.fn("cloud.boot_service.make")(function* (input: {
yield* fs
.makeDirectory(path.dirname(unitPath), { recursive: true })
.pipe(Effect.mapError((cause) => new BootServiceInstallError({ cause })));
if (!start && installed) {
// Written first: once the files below name the new version, the
// running service is behind them, and a failure between the two
// writes must not leave it looking current. The launcher removes the
// marker when it starts, `restart` and a started install do too.
yield* fs.writeFileString(restartPendingPath, `${input.cliVersion}\n`, { mode: 0o600 });
}
// Written first, on every install: once the files below name the new
// version, whatever is running is behind them. A started install removes
// the marker after `activate`, and so do `restart` and the launcher when
// it comes up on that version, so a failed start leaves `status`
// reporting `restart-pending` and the next install repairs it instead
// of calling itself current (#12197).
yield* fs.writeFileString(restartPendingPath, `${input.cliVersion}\n`, { mode: 0o600 });
yield* writeDurably(
statePath,
// @effect-diagnostics-next-line preferSchemaOverJson:off - fixed launcher-owned document.
Expand Down
Loading