History

Preserve PortScanner probe defects (#3282)

Julius Marminge committed 4 months ago

Commit a76b7bb1 parent a51691a2 files · +94 −4Browse files

Unified · Split

2 files
  1. apps/server/src/preview/PortScanner.test.ts +70 −2
  2. apps/server/src/preview/PortScanner.ts +24 −2
apps/server/src/preview/PortScanner.test.ts+70−2
@@ -3,14 +3,48 @@ import * as NodeNet from "node:net"; · 2 unmodified lines
33 import { it as effectIt } from "@effect/vitest";
44 import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
55 import * as Net from "@t3tools/shared/Net";
6−import { Effect, Layer } from "effect";
6+import * as Cause from "effect/Cause";
7+import * as Effect from "effect/Effect";
8+import * as Exit from "effect/Exit";
9+import * as Layer from "effect/Layer";
10+import * as PlatformError from "effect/PlatformError";
711 import { expect } from "vite-plus/test";
812
913 import * as ProcessRunner from "../processRunner.ts";
1014 import * as PortScanner from "./PortScanner.ts";
1115 const TestProcessRunner = Layer.succeed(ProcessRunner.ProcessRunner, {
12− run: () => Effect.die("ProcessRunner should not be used by Windows TCP probe tests"),
16+ run: (input) =>
17+ Effect.fail(
18+ new ProcessRunner.ProcessSpawnError({
19+ command: input.command,
20+ argumentCount: input.args.length,
21+ cwd: input.cwd,
22+ cause: PlatformError.systemError({
23+ _tag: "NotFound",
24+ module: "ChildProcess",
25+ method: "spawn",
26+ description: "PowerShell is not installed in the test environment",
27+ }),
28+ }),
29+ ),
1330 });
31+
32+const makeProbeFailureLayer = (run: ProcessRunner.ProcessRunner["Service"]["run"]) =>
33+ PortScanner.layer.pipe(
34+ Layer.provide(
35+ Layer.mergeAll(
36+ Layer.succeed(ProcessRunner.ProcessRunner, { run }),
37+ Layer.succeed(Net.NetService, {
38+ canListenOnHost: () => Effect.succeed(true),
39+ isPortAvailableOnLoopback: () => Effect.succeed(true),
40+ reserveLoopbackPort: () => Effect.succeed(40_000),
41+ findAvailablePort: (preferred) => Effect.succeed(preferred),
42+ }),
43+ Layer.succeed(HostProcessPlatform, "linux"),
44+ ),
45+ ),
46+ );
47+
1448 const TestPortDiscoveryLive = PortScanner.layer.pipe(
1549 Layer.provide(
1650 Layer.mergeAll(TestProcessRunner, Net.layer, Layer.succeed(HostProcessPlatform, "win32")),
@@ -87,3 +121,37 @@ effectIt.layer(TestPortDiscoveryLive)("PortDiscovery integration (TCP probe fall · 70 unmodified lines
87121 }),
88122 );
89123 });
124+
125+effectIt("does not swallow process probe defects", () =>
126+ Effect.gen(function* () {
127+ const defect = new Error("unexpected process probe defect");
128+ const layer = makeProbeFailureLayer(() => Effect.die(defect));
129+
130+ const exit = yield* Effect.flatMap(PortScanner.PortDiscovery, (scanner) => scanner.scan()).pipe(
131+ Effect.provide(layer),
132+ Effect.exit,
133+ );
134+
135+ expect(Exit.isFailure(exit)).toBe(true);
136+ if (Exit.isFailure(exit)) {
137+ expect(Cause.hasDies(exit.cause)).toBe(true);
138+ expect(Cause.squash(exit.cause)).toBe(defect);
139+ }
140+ }),
141+);
142+
143+effectIt("does not swallow process probe interruption", () =>
144+ Effect.gen(function* () {
145+ const layer = makeProbeFailureLayer(() => Effect.interrupt);
146+
147+ const exit = yield* Effect.flatMap(PortScanner.PortDiscovery, (scanner) => scanner.scan()).pipe(
148+ Effect.provide(layer),
149+ Effect.exit,
150+ );
151+
152+ expect(Exit.isFailure(exit)).toBe(true);
153+ if (Exit.isFailure(exit)) {
154+ expect(Cause.hasInterruptsOnly(exit.cause)).toBe(true);
155+ }
156+ }),
157+);
apps/server/src/preview/PortScanner.ts+24−2
@@ -221,6 +221,14 @@ export const make = Effect.gen(function* PortDiscoveryMake() { · 220 unmodified lines
221221 }));
222222 });
223223
224+ const recoverProcessProbeFailure =
225+ (probe: "lsof" | "windows-listeners") => (error: ProcessRunner.ProcessRunError) =>
226+ Effect.logDebug("preview port process probe failed; falling back to common-port probes", {
227+ cause: error,
228+ probe,
229+ platform: hostPlatform,
230+ }).pipe(Effect.as(null));
231+
224232 const scanOnce = Effect.fn("PortDiscovery.scan")(function* () {
225233 const state = yield* Ref.get(stateRef);
226234 const terminalByProcessId = new Map<number, TerminalProcessOwner>();
@@ -230,6 +238,7 @@ export const make = Effect.gen(function* PortDiscoveryMake() { · 3 unmodified lines
230238 }
231239 }
232240 if (hostPlatform === "win32") {
241+ const recoverWindowsProbeFailure = recoverProcessProbeFailure("windows-listeners");
233242 const command =
234243 'Get-NetTCPConnection -State Listen -ErrorAction Stop | ForEach-Object { $processName = (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName; Write-Output "$($_.LocalAddress)|$($_.LocalPort)|$($_.OwningProcess)|$processName" }';
235244 const listeners = yield* processRunner
@@ -242,11 +251,18 @@ export const make = Effect.gen(function* PortDiscoveryMake() { · 6 unmodified lines
242251 })
243252 .pipe(
244253 Effect.map((result) => parseWindowsListenerOutput(result.stdout, terminalByProcessId)),
245− Effect.catchCause(() => Effect.succeed(null)),
254+ Effect.catchTags({
255+ ProcessSpawnError: recoverWindowsProbeFailure,
256+ ProcessStdinError: recoverWindowsProbeFailure,
257+ ProcessOutputLimitError: recoverWindowsProbeFailure,
258+ ProcessReadError: recoverWindowsProbeFailure,
259+ ProcessTimeoutError: recoverWindowsProbeFailure,
260+ }),
246261 );
247262 if (listeners !== null) return listeners;
248263 return yield* probeCommonPorts();
249264 }
265+ const recoverLsofProbeFailure = recoverProcessProbeFailure("lsof");
250266 const lsofResult = yield* processRunner
251267 .run({
252268 command: "lsof",
@@ -257,7 +273,13 @@ export const make = Effect.gen(function* PortDiscoveryMake() { · 4 unmodified lines
257273 })
258274 .pipe(
259275 Effect.map((result) => parseLsofOutput(result.stdout, terminalByProcessId)),
260− Effect.catchCause(() => Effect.succeed(null)),
276+ Effect.catchTags({
277+ ProcessSpawnError: recoverLsofProbeFailure,
278+ ProcessStdinError: recoverLsofProbeFailure,
279+ ProcessOutputLimitError: recoverLsofProbeFailure,
280+ ProcessReadError: recoverLsofProbeFailure,
281+ ProcessTimeoutError: recoverLsofProbeFailure,
282+ }),
261283 );
262284 if (lsofResult !== null) return lsofResult;
263285 return yield* probeCommonPorts();

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t