mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 08:47:00 +00:00
fix(inspector): drain discovery requests after sibling failure (#9598)
fix(inspector): drain failed discovery requests
This commit is contained in:
1 parent
a874cef9af
commit
897caa814d
2 files changed
+113
-4
No files matched your search
@@ -3,6 +3,7 @@ import { createServer } from "node:http";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { V8InspectorProvider } from "./V8InspectorProvider.js";
|
||||
import { awaitAcknowledgement } from "../../tests/fixtures/lifecycleAcknowledgement.js";
|
||||
import { startFakeV8Inspector } from "../../tests/fixtures/inspector/fakeV8Inspector.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
|
||||
@@ -149,3 +150,97 @@ it("keeps connectable siblings when a target omits its direct socket", async ()
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
}
|
||||
});
|
||||
|
||||
it.each(["/json/version", "/json/list"])(
|
||||
"closes the other discovery request when %s fails",
|
||||
async (failedPath) => {
|
||||
let failedResponse: import("node:http").ServerResponse | undefined;
|
||||
let held = false;
|
||||
let heldClosed!: () => void;
|
||||
const closed = new Promise<void>((resolve) => {
|
||||
heldClosed = resolve;
|
||||
});
|
||||
const server = createServer((request, response) => {
|
||||
if (request.url === failedPath) {
|
||||
failedResponse = response;
|
||||
} else {
|
||||
held = true;
|
||||
request.on("close", heldClosed);
|
||||
}
|
||||
if (held) failedResponse?.writeHead(500).end();
|
||||
});
|
||||
await new Promise<void>((resolve) =>
|
||||
server.listen(0, "127.0.0.1", resolve),
|
||||
);
|
||||
const address = server.address();
|
||||
if (address === null || typeof address === "string")
|
||||
throw new Error("no address");
|
||||
const provider = new V8InspectorProvider();
|
||||
try {
|
||||
const result = await provider.listTargets({
|
||||
inspector_endpoint: `http://127.0.0.1:${address.port}`,
|
||||
});
|
||||
if (result.ok) throw new Error("expected discovery failure");
|
||||
expect(projectAnalysisError(result.error).code).toBe("unreadable_output");
|
||||
await awaitAcknowledgement(
|
||||
closed,
|
||||
new Promise<never>(() => undefined),
|
||||
"pending discovery socket close",
|
||||
);
|
||||
} finally {
|
||||
server.closeAllConnections();
|
||||
await new Promise<void>((resolve, reject) =>
|
||||
server.close((error) => (error ? reject(error) : resolve())),
|
||||
);
|
||||
await provider.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("closes both discovery requests and retains caller cancellation", async () => {
|
||||
let arrived = 0;
|
||||
let released = 0;
|
||||
let acknowledgeStarted!: () => void;
|
||||
let acknowledgeClosed!: () => void;
|
||||
const started = new Promise<void>((resolve) => {
|
||||
acknowledgeStarted = resolve;
|
||||
});
|
||||
const closed = new Promise<void>((resolve) => {
|
||||
acknowledgeClosed = resolve;
|
||||
});
|
||||
const server = createServer((request) => {
|
||||
request.on("close", () => {
|
||||
if (++released === 2) acknowledgeClosed();
|
||||
});
|
||||
if (++arrived === 2) acknowledgeStarted();
|
||||
});
|
||||
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
const address = server.address();
|
||||
if (address === null || typeof address === "string")
|
||||
throw new Error("no address");
|
||||
const provider = new V8InspectorProvider();
|
||||
const controller = new AbortController();
|
||||
const reason = new Error("caller stopped observation");
|
||||
const operation = provider.listTargets(
|
||||
{ inspector_endpoint: `http://127.0.0.1:${address.port}` },
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
try {
|
||||
await awaitAcknowledgement(started, operation, "both discovery requests");
|
||||
controller.abort(reason);
|
||||
const result = await operation;
|
||||
if (result.ok) throw new Error("expected cancellation");
|
||||
expect(result.error.cause).toBe(reason);
|
||||
await awaitAcknowledgement(
|
||||
closed,
|
||||
new Promise<never>(() => undefined),
|
||||
"cancelled discovery sockets",
|
||||
);
|
||||
} finally {
|
||||
server.closeAllConnections();
|
||||
await new Promise<void>((resolve, reject) =>
|
||||
server.close((error) => (error ? reject(error) : resolve())),
|
||||
);
|
||||
await provider.close();
|
||||
}
|
||||
});
|
||||
@@ -39,10 +39,24 @@ export const discoverV8Inspector = async (
|
||||
operation: BrowserObservationOperation,
|
||||
signal?: AbortSignal,
|
||||
): Promise<V8InspectorDiscovery> => {
|
||||
const [versionInput, targetsInput] = await Promise.all([
|
||||
readCdpJson(new URL("/json/version", endpoint), operation, signal),
|
||||
readCdpJson(new URL("/json/list", endpoint), operation, signal),
|
||||
]);
|
||||
const controller = new AbortController();
|
||||
const discoverySignal =
|
||||
signal === undefined
|
||||
? controller.signal
|
||||
: AbortSignal.any([signal, controller.signal]);
|
||||
const reads = [
|
||||
readCdpJson(new URL("/json/version", endpoint), operation, discoverySignal),
|
||||
readCdpJson(new URL("/json/list", endpoint), operation, discoverySignal),
|
||||
] as const;
|
||||
let versionInput: unknown;
|
||||
let targetsInput: unknown;
|
||||
try {
|
||||
[versionInput, targetsInput] = await Promise.all(reads);
|
||||
} catch (cause: unknown) {
|
||||
controller.abort(cause);
|
||||
await Promise.allSettled(reads);
|
||||
throw cause;
|
||||
}
|
||||
const version = parseCdpEndpointValue(versionSchema, versionInput, operation);
|
||||
const { targets, limitations, ambiguousTargetIds } = parseCdpDiscoveryTargets(
|
||||
targetsInput,
|
||||
|
||||
Reference in new issue
Block a user