From a2155c1ec65e271e4a5be1a19717b1aebdd647a5 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Sat, 29 Jan 2022 16:10:03 +0000 Subject: [PATCH] fix: wait for port to be available before creating a dev server (#332) When we run `wrangler dev`, we start a server on a port (defaulting to 8787). We do this separately for both local and edge modes. However, when switching between the two with the `l` hotkey, we don't 'wait' for the previous server to stop before starting the next one. This can crash the process, and we don't want that (of course). So we introduce a helper function `waitForPortToBeAvailable()` that waits for a port to be available before returning. This is used in both the local and edge modes, and prevents the bug right now, where switching between edge - local - edge crashes the process. (This isn't a complete fix, and we can still cause errors by very rapidly switching between the two modes. A proper long term fix for the future would probably be to hoist the proxy server hook above the `` and `` components, and use a single instance throughout. But that requires a deeper refactor, and isn't critical at the moment.) --- .changeset/calm-bananas-obey.md | 9 +++++++ packages/wrangler/src/dev.tsx | 4 ++- packages/wrangler/src/proxy.ts | 46 +++++++++++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 .changeset/calm-bananas-obey.md diff --git a/.changeset/calm-bananas-obey.md b/.changeset/calm-bananas-obey.md new file mode 100644 index 000000000000..680efdb86090 --- /dev/null +++ b/.changeset/calm-bananas-obey.md @@ -0,0 +1,9 @@ +--- +"wrangler": patch +--- + +fix: wait for port to be available before creating a dev server + +When we run `wrangler dev`, we start a server on a port (defaulting to 8787). We do this separately for both local and edge modes. However, when switching between the two with the `l` hotkey, we don't 'wait' for the previous server to stop before starting the next one. This can crash the process, and we don't want that (of course). So we introduce a helper function `waitForPortToBeAvailable()` that waits for a port to be available before returning. This is used in both the local and edge modes, and prevents the bug right now, where switching between edge - local - edge crashes the process. + +(This isn't a complete fix, and we can still cause errors by very rapidly switching between the two modes. A proper long term fix for the future would probably be to hoist the proxy server hook above the `` and `` components, and use a single instance throughout. But that requires a deeper refactor, and isn't critical at the moment.) diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index 321fb8200d17..299e071a7bfb 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -24,7 +24,7 @@ import type { CfWorkerInit } from "./api/worker"; import useInspector from "./inspect"; import makeModuleCollector from "./module-collection"; -import { usePreviewServer } from "./proxy"; +import { usePreviewServer, waitForPortToBeAvailable } from "./proxy"; import type { AssetPaths } from "./sites"; import { syncAssets } from "./sites"; import { getAPIToken } from "./user"; @@ -227,6 +227,8 @@ function useLocalWorker(props: { return; } + await waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 }); + console.log("⎔ Starting a local server..."); // TODO: just use execa for this local.current = spawn("node", [ diff --git a/packages/wrangler/src/proxy.ts b/packages/wrangler/src/proxy.ts index fd3361e3099d..f09d42c00a1d 100644 --- a/packages/wrangler/src/proxy.ts +++ b/packages/wrangler/src/proxy.ts @@ -242,8 +242,14 @@ export function usePreviewServer({ // Start/stop the server whenever the // containing component is mounted/unmounted. useEffect(() => { - proxy.listen(port); - console.log(`⬣ Listening at http://localhost:${port}`); + waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 }) + .then(() => { + proxy.listen(port); + console.log(`⬣ Listening at http://localhost:${port}`); + }) + .catch((err) => { + console.error(`⬣ Failed to start server: ${err}`); + }); return () => { proxy.close(); @@ -326,3 +332,39 @@ function createStreamHandler( }); }; } + +/** + * A helper function that waits for a port to be available. + */ +export async function waitForPortToBeAvailable( + port: number, + options: { retryPeriod: number; timeout: number } +): Promise { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error(`Timed out waiting for port ${port}`)); + }, options.timeout); + + function checkPort() { + // Testing whether a port is 'available' involves simply + // trying to make a server listen on that port, and retrying + // until it succeeds. + const server = createServer(); + server.on("error", (err) => { + // @ts-expect-error non standard property on Error + if (err.code === "EADDRINUSE") { + setTimeout(checkPort, options.retryPeriod); + } else { + reject(err); + } + }); + server.listen(port, () => { + server.close(); + clearTimeout(timeout); + resolve(); + }); + } + + checkPort(); + }); +}