Skip to content
Merged
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
134 changes: 134 additions & 0 deletions packages/shared/src/relayClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { sha256 } from "@noble/hashes/sha2";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { describe, expect, it } from "@effect/vitest";
import * as ConfigProvider from "effect/ConfigProvider";
import * as Deferred from "effect/Deferred";
import * as Effect from "effect/Effect";
import * as Fiber from "effect/Fiber";
import * as Hex from "effect/encoding/Hex";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
Expand Down Expand Up @@ -92,6 +94,133 @@ const managedPathFor = (baseDir: string, version: string) =>
`${baseDir}/tools/cloudflared/${version}/linux-x64/cloudflared`;

describe("RelayClient", () => {
it.effect("cancels a contended install without removing the other installer's lock", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-cloudflared-test-" });
const directory = `${baseDir}/tools/cloudflared/${RelayClient.CLOUDFLARED_VERSION}/linux-x64`;
const lockPath = `${directory}/cloudflared.lock`;
yield* fileSystem.makeDirectory(directory, { recursive: true });
yield* fileSystem.writeFileString(lockPath, "other-installer");
const contended = yield* Deferred.make<void>();
const manager = yield* RelayClient.makeCloudflaredRelayClient({ baseDir }).pipe(
Effect.provideService(FileSystem.FileSystem, {
...fileSystem,
writeFileString: (path, contents, options) =>
fileSystem
.writeFileString(path, contents, options)
.pipe(
Effect.tapError(() =>
path === lockPath ? Deferred.succeed(contended, undefined) : Effect.void,
),
),
}),
);
const installing = yield* manager.install.pipe(Effect.forkChild);
yield* Deferred.await(contended);
yield* Fiber.interrupt(installing);
expect(yield* fileSystem.readFileString(lockPath)).toBe("other-installer");
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
layerHttpClient(new Uint8Array()),
layerSpawner([]),
layerHostRuntime({ PATH: "" }),
),
),
),
);

it.effect("releases the install lock when a download is cancelled", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-cloudflared-test-" });
const lockPath = `${baseDir}/tools/cloudflared/${RelayClient.CLOUDFLARED_VERSION}/linux-x64/cloudflared.lock`;
const downloading = yield* Deferred.make<void>();
const manager = yield* RelayClient.makeCloudflaredRelayClient({ baseDir });
const installing = yield* manager
.installWithProgress((event) =>
event.type === "progress" && event.stage === "downloading"
? Deferred.succeed(downloading, undefined).pipe(Effect.andThen(Effect.never))
: Effect.void,
)
.pipe(Effect.forkChild);
yield* Deferred.await(downloading);
expect(yield* fileSystem.exists(lockPath)).toBe(true);
yield* Fiber.interrupt(installing);
expect(yield* fileSystem.exists(lockPath)).toBe(false);
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
layerHttpClient(new Uint8Array()),
layerSpawner([]),
layerHostRuntime({ PATH: "" }),
),
),
),
);

it.effect.skipIf(windowsHost)("releases a lock acquired while installation is cancelled", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-cloudflared-test-" });
const lockPath = `${baseDir}/tools/cloudflared/${RelayClient.CLOUDFLARED_VERSION}/linux-x64/cloudflared.lock`;
const acquired = yield* Deferred.make<void>();
const completeWrite = yield* Deferred.make<void>();
let pauseWrite = true;
const manager = yield* RelayClient.makeCloudflaredRelayClient({
baseDir,
releaseAsset: {
url: "https://example.test/cloudflared",
sha256: Hex.encode(sha256(new TextEncoder().encode("test-binary"))),
archive: "binary",
},
}).pipe(
Effect.provideService(FileSystem.FileSystem, {
...fileSystem,
writeFileString: (path, contents, options) =>
fileSystem
.writeFileString(path, contents, options)
.pipe(
Effect.tap(() =>
path === lockPath && pauseWrite
? Deferred.succeed(acquired, undefined).pipe(
Effect.andThen(Deferred.await(completeWrite)),
)
: Effect.void,
),
),
}),
);

const installing = yield* manager.install.pipe(Effect.forkChild);
yield* Deferred.await(acquired);
const cancelling = yield* Fiber.interrupt(installing).pipe(
Effect.forkChild({ startImmediately: true }),
);
yield* Deferred.succeed(completeWrite, undefined);
yield* Fiber.join(cancelling);
expect(yield* fileSystem.exists(lockPath)).toBe(false);

pauseWrite = false;
expect(yield* manager.install).toMatchObject({ status: "available" });
expect(yield* fileSystem.exists(lockPath)).toBe(false);
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
layerHttpClient(new TextEncoder().encode("test-binary")),
layerSpawner([]),
layerHostRuntime({ PATH: "" }),
),
),
),
);
it.effect.skipIf(windowsHost)(
"resolves explicit overrides before managed and PATH executables",
() =>
Expand Down Expand Up @@ -212,6 +341,11 @@ describe("RelayClient", () => {
const error = yield* manager.install.pipe(Effect.flip);
expect(error).toBeInstanceOf(RelayClient.RelayClientInstallError);
expect(error.reason).toBe("invalid_checksum");
expect(
yield* fileSystem.exists(
`${baseDir}/tools/cloudflared/${RelayClient.CLOUDFLARED_VERSION}/linux-x64/cloudflared.lock`,
),
).toBe(false);
}).pipe(
Effect.scoped,
Effect.provide(
Expand Down
8 changes: 5 additions & 3 deletions packages/shared/src/relayClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,10 @@ export const makeCloudflaredRelayClient = Effect.fn("cloudflared.make")(function
lockPath: string,
) {
for (let attempt = 0; attempt < INSTALL_LOCK_RETRY_COUNT; attempt += 1) {
const acquired = yield* fileSystem.writeFileString(lockPath, "", { flag: "wx" }).pipe(
const acquired = yield* Effect.acquireRelease(
fileSystem.writeFileString(lockPath, "", { flag: "wx" }),
() => fileSystem.remove(lockPath, { force: true }).pipe(Effect.ignore),
).pipe(
Effect.as(true),
Effect.catchIf(isAlreadyExists, () => Effect.succeed(false)),
);
Expand Down Expand Up @@ -591,7 +594,6 @@ export const makeCloudflaredRelayClient = Effect.fn("cloudflared.make")(function
} satisfies AvailableRelayClient;
}).pipe(
Effect.scoped,
Effect.ensuring(fileSystem.remove(lockPath, { force: true }).pipe(Effect.ignore)),
Effect.catchIf(
(cause) => !(cause instanceof RelayClientInstallError),
(cause) =>
Expand All @@ -612,7 +614,7 @@ export const makeCloudflaredRelayClient = Effect.fn("cloudflared.make")(function
type: "progress",
stage,
}),
),
).pipe(Effect.scoped),
);
const install = installWithProgress(() => Effect.void);

Expand Down
Loading