1// @effect-diagnostics nodeBuiltinImport:off - A separate Node process proves filesystem exclusion.
2import * as NodeChildProcess from "node:child_process";
3import * as NodeModule from "node:module";
4import * as NodePath from "node:path";
5import * as NodeUtil from "node:util";
6import { assert, it } from "@effect/vitest";
7import * as NodeServices from "@effect/platform-node/NodeServices";
8import { ProviderInstanceId } from "@t3tools/contracts";
9import * as Deferred from "effect/Deferred";
10import * as Effect from "effect/Effect";
11import * as Fiber from "effect/Fiber";
12import * as FileSystem from "effect/FileSystem";
13import { withChatGptSessionLock } from "./CodexChatGptSessionLock.ts";
14
15const execFile = NodeUtil.promisify(NodeChildProcess.execFile);
16const lockModule = NodeModule.createRequire(import.meta.url).resolve("proper-lockfile");
17const instanceId = ProviderInstanceId.make("cross-process-test");
18const probe = (directory: string) =>
19 Effect.promise(async () => {
20 const { stdout } = await execFile(process.execPath, [
21 "-e",
22 `
23 const { lock } = require(process.argv[1]);
24 lock(process.argv[2], { realpath: false }).then(async release => {
25 await release(); process.stdout.write('acquired');
26 }, error => {
27 if (error.code === 'ELOCKED') process.stdout.write('blocked');
28 else { console.error(error); process.exitCode = 1; }
29 });
30 `,
31 lockModule,
32 NodePath.join(directory, "session.bin"),
33 ]);
34 return stdout;
35 });
36
37it.live("excludes another process and releases after the credential update", () =>
38 Effect.scoped(
39 Effect.gen(function* () {
40 const fs = yield* FileSystem.FileSystem;
41 const directory = yield* fs.makeTempDirectoryScoped();
42 const blocked = yield* withChatGptSessionLock(
43 directory,
44 "session",
45 instanceId,
46 probe(directory),
47 );
48 assert.strictEqual(blocked, "blocked");
49 assert.strictEqual(yield* probe(directory), "acquired");
50 }),
51 ).pipe(Effect.provide(NodeServices.layer)),
52);
53
54it.live("releases the cross-process lease when the operation is interrupted", () =>
55 Effect.scoped(
56 Effect.gen(function* () {
57 const fs = yield* FileSystem.FileSystem;
58 const directory = yield* fs.makeTempDirectoryScoped();
59 const entered = yield* Deferred.make<void>();
60 const operation = yield* withChatGptSessionLock(
61 directory,
62 "session",
63 instanceId,
64 Effect.gen(function* () {
65 yield* Deferred.succeed(entered, undefined);
66 return yield* Effect.never;
67 }),
68 ).pipe(Effect.forkScoped);
69 yield* Deferred.await(entered);
70 assert.strictEqual(yield* probe(directory), "blocked");
71 yield* Fiber.interrupt(operation);
72 assert.strictEqual(yield* probe(directory), "acquired");
73 }),
74 ).pipe(Effect.provide(NodeServices.layer)),
75);