Skip to content

Commit 9098a3b

Browse files
authored
Remove special handling for 'Failed to start ProxyWorker or InspectorProxyWorker' (#7169)
1 parent 492533f commit 9098a3b

File tree

5 files changed

+110
-32
lines changed

5 files changed

+110
-32
lines changed

.changeset/famous-keys-walk.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Ensure `workerd` processes are cleaned up after address-in-use errors

packages/wrangler/e2e/dev.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import assert from "node:assert";
2+
import childProcess from "node:child_process";
23
import { existsSync } from "node:fs";
34
import * as nodeNet from "node:net";
45
import { setTimeout } from "node:timers/promises";
@@ -211,6 +212,102 @@ describe.each([
211212
});
212213
});
213214

215+
interface Process {
216+
pid: string;
217+
cmd: string;
218+
}
219+
220+
function getProcesses(): Process[] {
221+
return childProcess
222+
.execSync("ps -e | awk '{print $1,$4}'", { encoding: "utf8" })
223+
.trim()
224+
.split("\n")
225+
.map((line) => {
226+
const [pid, cmd] = line.split(" ");
227+
return { pid, cmd };
228+
});
229+
}
230+
231+
function getProcessCwd(pid: string | number) {
232+
return childProcess
233+
.execSync(`lsof -p ${pid} | awk '$4=="cwd" {print $9}'`, {
234+
encoding: "utf8",
235+
})
236+
.trim();
237+
}
238+
function getStartedWorkerdProcesses(cwd: string): Process[] {
239+
return getProcesses()
240+
.filter(({ cmd }) => cmd.includes("workerd"))
241+
.filter((c) => getProcessCwd(c.pid).includes(cwd));
242+
}
243+
244+
// This fails on Windows because of https://github.com/cloudflare/workerd/issues/1664
245+
it.runIf(process.platform !== "win32")(
246+
`leaves no orphaned workerd processes with port conflict`,
247+
async () => {
248+
const initial = new WranglerE2ETestHelper();
249+
await initial.seed({
250+
"wrangler.toml": dedent`
251+
name = "${workerName}"
252+
main = "src/index.ts"
253+
compatibility_date = "2023-01-01"
254+
`,
255+
"src/index.ts": dedent`
256+
export default {
257+
fetch(request) {
258+
return new Response("Hello World!")
259+
}
260+
}`,
261+
"package.json": dedent`
262+
{
263+
"name": "worker",
264+
"version": "0.0.0",
265+
"private": true
266+
}
267+
`,
268+
});
269+
const initialWorker = initial.runLongLived(`wrangler dev`);
270+
271+
const { url: initialWorkerUrl } = await initialWorker.waitForReady();
272+
273+
const port = new URL(initialWorkerUrl).port;
274+
275+
const helper = new WranglerE2ETestHelper();
276+
await helper.seed({
277+
"wrangler.toml": dedent`
278+
name = "${workerName}"
279+
main = "src/index.ts"
280+
compatibility_date = "2023-01-01"
281+
`,
282+
"src/index.ts": dedent`
283+
export default {
284+
fetch(request) {
285+
return new Response("Hello World!")
286+
}
287+
}`,
288+
"package.json": dedent`
289+
{
290+
"name": "worker",
291+
"version": "0.0.0",
292+
"private": true
293+
}
294+
`,
295+
});
296+
const beginProcesses = getStartedWorkerdProcesses(helper.tmpPath);
297+
// If a port isn't specified, Wrangler will start up on a different random port. In this test we want to force an address-in-use error
298+
const worker = helper.runLongLived(`wrangler dev --port ${port}`);
299+
300+
const exitCode = await worker.exitCode;
301+
302+
expect(exitCode).not.toBe(0);
303+
304+
const endProcesses = getStartedWorkerdProcesses(helper.tmpPath);
305+
306+
expect(beginProcesses.length).toBe(0);
307+
expect(endProcesses.length).toBe(0);
308+
}
309+
);
310+
214311
// Skipping remote python tests because they consistently flake with timeouts
215312
// Unskip once remote dev with python workers is more stable
216313
describe.each([

packages/wrangler/e2e/helpers/command.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export function runCommand(
7777
export class LongLivedCommand {
7878
private lines: string[] = [];
7979
private stream: ReadableStream;
80-
private exitPromise: Promise<unknown>;
80+
private exitPromise: Promise<[number, unknown]>;
8181
private commandProcess: ChildProcessWithoutNullStreams;
8282

8383
constructor(
@@ -93,7 +93,9 @@ export class LongLivedCommand {
9393
signal,
9494
});
9595

96-
this.exitPromise = events.once(this.commandProcess, "exit");
96+
this.exitPromise = events.once(this.commandProcess, "exit") as Promise<
97+
[number, unknown]
98+
>;
9799

98100
// Merge the stdout and stderr into a single output stream
99101
const output = new PassThrough();
@@ -143,7 +145,7 @@ export class LongLivedCommand {
143145
}
144146

145147
get exitCode() {
146-
return this.exitPromise;
148+
return this.exitPromise.then((e) => e[0]);
147149
}
148150

149151
async stop() {

packages/wrangler/src/api/startDevWorker/DevEnv.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -110,30 +110,6 @@ export class DevEnv extends EventEmitter {
110110

111111
emitErrorEvent(ev: ErrorEvent) {
112112
if (
113-
ev.source === "ProxyController" &&
114-
ev.reason === "Failed to start ProxyWorker or InspectorProxyWorker"
115-
) {
116-
assert(ev.data.config); // we must already have a `config` if we've already tried (and failed) to instantiate the ProxyWorker(s)
117-
118-
const { config } = ev.data;
119-
const port = config.dev?.server?.port;
120-
const inspectorPort = config.dev?.inspector?.port;
121-
const randomPorts = [0, undefined];
122-
123-
if (!randomPorts.includes(port) || !randomPorts.includes(inspectorPort)) {
124-
// emit the event here while the ConfigController is unimplemented
125-
// this will cause the ProxyController to try reinstantiating the ProxyWorker(s)
126-
// TODO: change this to `this.config.updateOptions({ dev: { server: { port: 0 }, inspector: { port: 0 } } });` when the ConfigController is implemented
127-
this.config.emitConfigUpdateEvent({
128-
...config,
129-
dev: {
130-
...config.dev,
131-
server: { ...config.dev?.server, port: 0 }, // override port
132-
inspector: { ...config.dev?.inspector, port: 0 }, // override port
133-
},
134-
});
135-
}
136-
} else if (
137113
ev.source === "ProxyController" &&
138114
(ev.reason.startsWith("Failed to send message to") ||
139115
ev.reason.startsWith("Could not connect to InspectorProxyWorker"))

packages/wrangler/src/api/startDevWorker/ProxyController.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,9 @@ export class ProxyController extends Controller<ProxyControllerEventMap> {
177177
if (proxyWorkerOptionsChanged) {
178178
logger.debug("ProxyWorker miniflare options changed, reinstantiating...");
179179

180-
void this.proxyWorker.setOptions(proxyWorkerOptions);
180+
void this.proxyWorker.setOptions(proxyWorkerOptions).catch((error) => {
181+
this.emitErrorEvent("Failed to start ProxyWorker", error);
182+
});
181183

182184
// this creates a new .ready promise that will be resolved when both ProxyWorkers are ready
183185
// it also respects any await-ers of the existing .ready promise
@@ -316,8 +318,6 @@ export class ProxyController extends Controller<ProxyControllerEventMap> {
316318
`Failed to send message to ProxyWorker: ${JSON.stringify(message)}`,
317319
error
318320
);
319-
320-
throw error;
321321
}
322322
}
323323
async sendMessageToInspectorProxyWorker(
@@ -351,8 +351,6 @@ export class ProxyController extends Controller<ProxyControllerEventMap> {
351351
)}`,
352352
error
353353
);
354-
355-
throw error;
356354
}
357355
}
358356

0 commit comments

Comments
 (0)