Skip to content

Commit

Permalink
fix: wait for port to be available before creating a dev server (#332)
Browse files Browse the repository at this point in the history
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 `<Remote/>` and `<Local/>` components, and use a single instance throughout. But that requires a deeper refactor, and isn't critical at the moment.)
  • Loading branch information
threepointone committed Jan 29, 2022
1 parent 6320a32 commit a2155c1
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 3 deletions.
9 changes: 9 additions & 0 deletions .changeset/calm-bananas-obey.md
Original file line number Diff line number Diff line change
@@ -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 `<Remote/>` and `<Local/>` components, and use a single instance throughout. But that requires a deeper refactor, and isn't critical at the moment.)
4 changes: 3 additions & 1 deletion packages/wrangler/src/dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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", [
Expand Down
46 changes: 44 additions & 2 deletions packages/wrangler/src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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<void> {
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();
});
}

0 comments on commit a2155c1

Please sign in to comment.