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
50 changes: 50 additions & 0 deletions apps/server/src/provider/AntigravityInstallation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as Schema from "effect/Schema";
import * as Scope from "effect/Scope";
import * as Sink from "effect/Sink";
import * as Stream from "effect/Stream";
import * as TestClock from "effect/testing/TestClock";
import { HttpClient, HttpClientResponse } from "effect/http";
import * as ChildProcess from "effect/process/ChildProcess";
import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
Expand Down Expand Up @@ -922,6 +923,55 @@ it.layer(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))("Antigravity inst
}),
);

it.effect("publishes a Windows release once a transient EPERM on the release rename clears", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const archive = Buffer.from(zipFixtures.windows, "base64");
const releaseRenameDenied = yield* Deferred.make<void>();
let releaseRenameDenials = 3;
const { installation } = yield* makeHarness({
platform: "win32",
archive,
fileSystem: FileSystem.FileSystem.of({
...fs,
rename: (source, target) =>
Effect.suspend(() => {
if (
path.basename(path.dirname(target)) !== "versions" ||
releaseRenameDenials === 0
) {
return fs.rename(source, target);
}
releaseRenameDenials -= 1;
return Deferred.succeed(releaseRenameDenied, undefined).pipe(
Effect.andThen(
Effect.fail(
PlatformError.systemError({
_tag: "Unknown",
module: "FileSystem",
method: "rename",
pathOrDescriptor: source,
description: "EPERM: operation not permitted, rename",
}),
),
),
);
}),
}),
});
yield* installation.start;
const terminal = yield* terminalState(installation).pipe(Effect.forkChild);
yield* Deferred.await(releaseRenameDenied);
yield* TestClock.adjust("30 seconds");
expect(yield* Fiber.join(terminal)).toMatchObject({
phase: "succeeded",
installedVersion: "fixture-new",
});
expect((yield* installation.resolve()).version).toBe("fixture-new");
}),
);

it.effect("reports unsupported hosts without downloading or changing state", () =>
Effect.gen(function* () {
const { installation, requests } = yield* makeHarness({ asset: null, platform: "darwin" });
Expand Down
29 changes: 17 additions & 12 deletions apps/server/src/provider/AntigravityInstallation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Path from "effect/Path";
import type { PlatformError } from "effect/PlatformError";
import * as Schema from "effect/Schema";
import * as Schedule from "effect/Schedule";
import * as Scope from "effect/Scope";
Expand Down Expand Up @@ -181,6 +182,18 @@ export const makeAntigravityInstallation = Effect.fn("AntigravityInstallation.ma
);
const versionsDirectory = path.join(managedDirectory, "versions");
const activePath = path.join(managedDirectory, "active.json");
// Windows briefly keeps handles on fresh runtime files after taskkill or an antivirus scan; retry for up to 10 seconds.
const retryWhileWindowsHoldsHandles = <A, R>(effect: Effect.Effect<A, PlatformError, R>) =>
effect.pipe(
Effect.retry({
times: platform === "win32" ? 40 : 0,
schedule: Schedule.spaced("250 millis"),
while: (error) =>
error.reason._tag === "PermissionDenied" ||
error.reason._tag === "Busy" ||
error.reason._tag === "Unknown",
}),
);
const gate = yield* Semaphore.make(1);
const leases = new Map<string, number>();
let running: { readonly operationId: string; readonly fiber: Fiber.Fiber<void> } | undefined;
Expand Down Expand Up @@ -391,18 +404,9 @@ export const makeAntigravityInstallation = Effect.fn("AntigravityInstallation.ma
const profileDirectory = yield* Effect.acquireRelease(
fs.makeTempDirectory({ prefix: "t3-antigravity-validate-" }),
(directory) =>
fs.remove(directory, { recursive: true, force: true }).pipe(
// Windows may retain DLL handles briefly after taskkill returns.
Effect.retry({
times: platform === "win32" ? 10 : 0,
schedule: Schedule.spaced("100 millis"),
while: (error) =>
error.reason._tag === "PermissionDenied" ||
error.reason._tag === "Busy" ||
error.reason._tag === "Unknown",
}),
Effect.orDie,
),
fs
.remove(directory, { recursive: true, force: true })
.pipe(retryWhileWindowsHoldsHandles, Effect.orDie),
);
const profile = yield* prepareAntigravityProfile({
profileDirectory,
Expand Down Expand Up @@ -732,6 +736,7 @@ export const makeAntigravityInstallation = Effect.fn("AntigravityInstallation.ma
{ flag: "wx", mode: 0o600 },
);
yield* fs.rename(pairDirectory, destination).pipe(
retryWhileWindowsHoldsHandles,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Effect.catch((cause) =>
completedRelease(asset.sha256).pipe(
Effect.flatMap((existing) =>
Expand Down
Loading