Repository navigation
Expand file tree
/
Copy pathprocessProbe.ts
More file actions
73 lines (71 loc) · 2.43 KB
/
Copy pathprocessProbe.ts
File metadata and controls
73 lines (71 loc) · 2.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// @effect-diagnostics nodeBuiltinImport:off - a socket observes fixture processes after their parent exits.
import * as NodeNet from "node:net";
import * as Deferred from "effect/Deferred";
import * as Effect from "effect/Effect";
import * as TestClock from "effect/testing/TestClock";
export const makeProcessProbe = Effect.gen(function* () {
const started = yield* Deferred.make<number>();
const exited = yield* Deferred.make<void>();
const sockets = new Set<NodeNet.Socket>();
const server = NodeNet.createServer((socket) => {
sockets.add(socket);
let output = "";
socket.on("data", (chunk) => {
output += chunk.toString();
if (output.includes("\n")) {
const pid = Number(output.trim());
if (Number.isSafeInteger(pid) && pid > 0) {
Deferred.doneUnsafe(started, Effect.succeed(pid));
}
}
});
socket.on("close", () => {
sockets.delete(socket);
Deferred.doneUnsafe(exited, Effect.void);
});
});
yield* Effect.acquireRelease(
Effect.callback<void, Error>((resume) => {
server.once("error", (error) => resume(Effect.fail(error)));
server.listen(0, "127.0.0.1", () => resume(Effect.void));
}),
() =>
Effect.sync(() => {
for (const socket of sockets) socket.destroy();
server.close();
}),
);
const address = server.address();
if (address === null || typeof address === "string") {
return yield* Effect.die("Missing process probe port");
}
return {
port: address.port,
started: Deferred.await(started).pipe(
Effect.timeout("4 seconds"),
TestClock.withLive,
Effect.tap((pid) =>
Effect.addFinalizer(() =>
Effect.gen(function* () {
if (yield* Deferred.isDone(exited)) return;
// Only a PID reported by this fixture is eligible for failure cleanup.
yield* Effect.sync(() => {
try {
process.kill(pid, "SIGKILL");
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ESRCH") throw error;
}
});
}),
),
),
),
exited: Deferred.await(exited).pipe(Effect.timeout("4 seconds"), TestClock.withLive),
};
});
export const processProbeSource = (port: number) => `
const { createConnection } = await import("node:net");
const probe = createConnection(${port}, "127.0.0.1", () => {
probe.write(process.pid + "\\n");
});
`;